///////////////////////////////////////////////////////////////////////////////
//
// File      : Asura_PS3_SingleStorage.cpp
// Author    : Philip Hall
// Created   : 07/12/2007
//
// Copyright (C) 2007 Rebellion. All Rights Reserved
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header
#include "Utility/Asura_PS3_SingleStorage.h"

// Stdlib platform-specific.
#include <sdk_version.h>

// Asura headers
#include "Core/Memory/Asura_Mem.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"

// Asura PS3 headers
#include "FileAccess/Asura_PS3_FileSystem.h" 


////PGH/////////////////////////////////////////
// Useful constants
////////////////////////////////////////////////
const int    STORAGE_PPU_PRIORITY   = 750;
const size_t STORAGE_PPU_STACK_SIZE = (1024 * 16);
const size_t STORAGE_MEMORY_CONTAINER_SIZE = (1024 * 1024 * 4);

static const char s_acSecureFileId[CELL_SAVEDATA_SECUREFILEID_SIZE] = {
	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };


inline int Asura_SizeInKB(int iSize)
{
	return (iSize+ 1023) / 1024;
}

//
//  System files 
//
//	AddDataEntry("MISC/ICON0.PNG", NULL, 0, CELL_SAVEDATA_FILETYPE_CONTENT_ICON0);
//	AddDataEntry("MISC/ICON0.PAM", NULL, 0, CELL_SAVEDATA_FILETYPE_CONTENT_ICON1);
//	AddDataEntry("MISC/PIC1.PNG", NULL, 0, CELL_SAVEDATA_FILETYPE_CONTENT_PIC1);
//	AddDataEntry("MISC/SND0.AT3", NULL, 0, CELL_SAVEDATA_FILETYPE_CONTENT_SND0);


bool Asura_PS3_AllocateFromFile(const char* szFilename, void** pResult, int* piSize = NULL)
{
	Asura_PS3_FileSystem::Handle xHandle;
	if ( !Asura_PS3_FileSystem::OpenForReading( szFilename, &xHandle ) )
	{
		Asura_OutputToDebugger(("[Asura_PS3_AllocateFromFile] - Unable to open %s\n", szFilename));
		return false;
	}
	
	// Free before we alloc
	if ( *pResult != NULL )
	{
		Asura_Deallocate(*pResult);
		*pResult = NULL;
	}
	
	u_int uSize = Asura_PS3_FileSystem::GetFileSize( xHandle );
	*pResult = Asura_Allocate( uSize );
	Asura_PS3_FileSystem::ReadData( xHandle, uSize, *pResult );
	Asura_PS3_FileSystem::Close( &xHandle );
	
	if ( piSize != NULL )
	{
		*piSize = static_cast<int>(uSize);
	}
	
	return true;
}





////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::Asura_PS3_SingleStorage
// Description : Initialise the single (non-list) storage class
//
// Parameters  : 
//   const char *szProductCode - Product code directory prefix
//   Asura_PS3_StorageCallback pfnCompletion - Completion callback
//   void* pUserData - user data for the callback function, via GetUserData()
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
Asura_PS3_SingleStorage::Asura_PS3_SingleStorage(const char *szProductCode, Asura_PS3_StorageCallback pfnCompletion, void* pUserData)
{
	m_bAuto = 0;
	m_xThreadID = 0; 
	m_pDataBuffer = 0;
	m_iTotalEntries = 0;
	m_iTotalDataSizeKB = 0;
	m_iSaveEntryIndex = 0;
	m_iLoadEntryIndex = 0; 
	m_bOverwrite = true;
	m_pUserData = pUserData;
	m_pfnCompletionCallback = pfnCompletion;
	Asura_Mem::Set(m_axFileEntries, 0, sizeof(m_axFileEntries));

	Asura_PrintfToBfr(m_szProductCode, "%s", CELL_SAVEDATA_DIRNAME_SIZE, true, szProductCode);
	// Save information
	Asura_PrintfToBfr(m_szSaveTitle, "Unset Save Title", CELL_SAVEDATA_SYSP_TITLE_SIZE, true);
	Asura_PrintfToBfr(m_szSaveSubTitle, "This save has not had its subtitle set", CELL_SAVEDATA_SYSP_SUBTITLE_SIZE, true);
	Asura_PrintfToBfr(m_szSaveDetail, "This save has not had its detail set", CELL_SAVEDATA_SYSP_DETAIL_SIZE, true);
	Asura_PrintfToBfr(m_szSaveParam, "NULLPARAM", CELL_SAVEDATA_SYSP_LPARAM_SIZE, true);
	m_iParentalLevel = 1;
}


Asura_PS3_SingleStorage::~Asura_PS3_SingleStorage()
{
	Destroy();
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::Destroy
// Description : Destroy this object ready for reuse
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::Destroy()
{
	// Wait for the thread to complete (if it hasn't already)
	if ( m_xThreadID == 0 )
	{
		uint64_t uStat;
		sys_ppu_thread_join(m_xThreadID, &uStat);
	}
	
	// Free the temporary data buffer
	if ( m_pDataBuffer != NULL )
	{
		Asura_Deallocate(m_pDataBuffer);
		m_pDataBuffer = NULL;
	}
	
	ClearDataEntries();
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::Load
// Description : Load data from storage using the save utility
//
// Parameters  : 
//   const char* szPrefix - Directory prefix for load
//   bool bAutoLoad       - Set true to auto-load the data
//   int iIndex           - Save slot index if required, negative to ignore
//
// Returns     : bool - Returns true if load initiated successfully
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_SingleStorage::Load(const char* szPrefix, bool bAutoLoad, int iIndex)
{
	m_bAuto    = bAutoLoad;
	m_bLoading = true;
	if ( iIndex >= 0 )
	{
		Asura_Assert(iIndex < 26, "[Asura_PS3_SingleStorage::Load] - Index too big to convert to slot number\n");
		Asura_PrintfToBfr(m_szDirectory, "%s-%s-%c", CELL_SAVEDATA_DIRNAME_SIZE, true, m_szProductCode, szPrefix, 'A'+iIndex);
	}
	else
	{
		Asura_PrintfToBfr(m_szDirectory, "%s-%s", CELL_SAVEDATA_DIRNAME_SIZE, true, m_szProductCode, szPrefix);
	}

	// Must be uppercase or PS3 gets annoyed
	Asura_StringUtil::MakeUppercase( m_szDirectory ); 

	// Launch load thread
	int iResult = sys_ppu_thread_create(&m_xThreadID, ThreadStorageProxy, 
	                                     reinterpret_cast<uint64_t>(this), STORAGE_PPU_PRIORITY, 
	                                     STORAGE_PPU_STACK_SIZE, 0, "Storage_Load");

	Asura_Assert(iResult >= 0, "[Asura_PS3_SingleStorage::Load] - Unable to create storage thread\n");
	return (iResult == CELL_OK);
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::Save
// Description : Save data to storage using the save utility
//
// Parameters  : 
//   const char* szPrefix - Directory prefix for save
//   bool bAutoSave       - Set true to auto-save the data (doesn't prompt user)
//   int iIndex           - Save slot index if required, negative to ignore
//
// Returns     : bool - Returns true if initiated successfully
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_SingleStorage::Save(const char* szPrefix, bool bAutoSave, int iIndex)
{
	if ( m_iTotalEntries <= 0 )
	{
		Asura_Assert(false, "[Asura_PS3_SingleStorage::Save] - No data entries to save! (call AddDataEntry)\n");
		return false;
	}

	m_bAuto    = bAutoSave;
	m_bLoading = false;
	if ( iIndex >= 0 )
	{
		Asura_Assert(iIndex < 26, "[Asura_PS3_SingleStorage::Save] - Index too big to convert to slot number\n");
		Asura_PrintfToBfr(m_szDirectory, "%s-%s-%c", CELL_SAVEDATA_DIRNAME_SIZE, true, m_szProductCode, szPrefix, 'A'+iIndex);
	}
	else
	{
		Asura_PrintfToBfr(m_szDirectory, "%s-%s", CELL_SAVEDATA_DIRNAME_SIZE, true, m_szProductCode, szPrefix);
	}
	
	// Must be uppercase or PS3 gets annoyed
	Asura_StringUtil::MakeUppercase( m_szDirectory );

	// Launch save thread
	int iResult = sys_ppu_thread_create(&m_xThreadID, &Asura_PS3_SingleStorage::ThreadStorageProxy,
	                                     reinterpret_cast<uint64_t>(this), STORAGE_PPU_PRIORITY, 
	                                     STORAGE_PPU_STACK_SIZE, 0, "Storage_Save");

	Asura_Assert(iResult >= 0, "[Asura_PS3_SingleStorage::Save] - Unable to create storage thread\n"); 
	return (iResult == CELL_OK);
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::WaitComplete
// Description : Wait or poll for the storage thread to complete
//
// Parameters  : 
//   int iTimeoutMS - Duration to wait in milliseconds (zero or less to poll)
//
// Returns     : bool - Returns true when completed, false otherwise
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_SingleStorage::WaitComplete(int iTimeoutMS)
{
	bool bComplete = ( iTimeoutMS <= 0 ) ? m_xCompleted.TryWait() : m_xCompleted.Wait( iTimeoutMS*1000 );
	
	if ( bComplete )
	{
		uint64_t uStat;
		sys_ppu_thread_join(m_xThreadID, &uStat);  // join the worker thread back
		m_xThreadID = 0;
	}

	cellSysutilCheckCallback();	 // This is a teeny-tiny-hack
	return bComplete;
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::AddDataEntry
// Description : Add a new data entry to save to disk
//
// Parameters  : 
//   const char* szName         - Name of entry
//   void* pData                - Buffer to read/write (NULL to load from name)
//   int   iSize                - Size of data
//   CellSaveDataFileType eType - File data type
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::AddDataEntry(const char* szName, void* pData, int iSize, CellSaveDataFileType eType)
{
	Asura_Assert((m_iTotalEntries < ASURA_PS3_MAX_DATA_ENTRIES), "[Asura_PS3_SingleStorage::AddDataEntry] - Too many data entries\n");
	if (m_iTotalEntries < ASURA_PS3_MAX_DATA_ENTRIES)
	{
		strncpy( m_axFileEntries[m_iTotalEntries].szName, szName, ASURA_PS3_SAVE_NAME_LENGTH );
		m_axFileEntries[m_iTotalEntries].pData = pData;
		m_axFileEntries[m_iTotalEntries].iSize = iSize;
		m_axFileEntries[m_iTotalEntries].eType = eType;
		m_axFileEntries[m_iTotalEntries].bDeallocate = false;
		m_iTotalDataSizeKB += Asura_SizeInKB(iSize);
		m_iTotalEntries++;
	}
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::ClearDataEntries
// Description : Clear the data entries array and reset the counter
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::ClearDataEntries()
{
	// Deallocate memory if required
	for (int iLoop=0; iLoop < m_iTotalEntries; iLoop++ )
	{
		if ( m_axFileEntries[iLoop].bDeallocate && (m_axFileEntries[iLoop].pData != NULL) )
		{
			Asura_Deallocate( m_axFileEntries[iLoop].pData );
		}
	}

	// Clear down structures
	m_iTotalEntries = 0;
	m_iTotalDataSizeKB = 0;
	Asura_Mem::Set(m_axFileEntries, 0, sizeof(m_axFileEntries));
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::GetDataEntry
// Description : Get access to the requested data entry (use after loading)
//
// Parameters  : 
//   const char *szName  - Name of entry to locate
//   int& iSize          - Size of entry (0 on failure)
//
// Returns     : void* - Pointer to entry data (NULL if not found)
///////////////////////////////////////////////////////////////////////////////
void* Asura_PS3_SingleStorage::GetDataEntry(const char *szName, int& iSize) const
{
	// Search for the named entry  (case sensitive)
	for (int iLoop=0; iLoop < m_iTotalEntries; iLoop++ )
	{
		if ( strncmp(m_axFileEntries[iLoop].szName, szName, ASURA_PS3_SAVE_NAME_LENGTH) == 0 )
		{
			iSize = m_axFileEntries[iLoop].iSize;
			return m_axFileEntries[iLoop].pData;
		}
	}
	iSize = 0;
	return NULL;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::SetSaveInfo
// Description : Set the save information
//
// Parameters  : 
//   const char *szTitle     - Save title 
//   const char *szSubTitle  - Save subtitle 
//   const char *szDetail    - Save detail text 
//   const char *szParam     - Save parameter
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::SetSaveInfo(const char *szTitle, const char *szSubTitle, const char *szDetail, const char *szParam)
{
	if (szTitle != NULL)
	{
		Asura_PrintfToBfr(m_szSaveTitle, "%s", CELL_SAVEDATA_SYSP_TITLE_SIZE, true, szTitle);
	}
	if (szSubTitle != NULL)
	{
		Asura_PrintfToBfr(m_szSaveSubTitle, "%s", CELL_SAVEDATA_SYSP_SUBTITLE_SIZE, true, szSubTitle);
	}
	if (szDetail != NULL)
	{
		Asura_PrintfToBfr(m_szSaveDetail, "%s", CELL_SAVEDATA_SYSP_DETAIL_SIZE, true, szDetail);
	}
	if (szParam != NULL)
	{
		Asura_PrintfToBfr(m_szSaveParam, "%s", CELL_SAVEDATA_SYSP_LPARAM_SIZE, true, szParam);
		Asura_StringUtil::MakeUppercase( m_szSaveParam ); // Must be uppercase
	}
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::ThreadStorage
// Description : Storage execution thread for single operations
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::ThreadStorage()
{
	CellSaveDataSetList xSetList;
	xSetList.sortType      = CELL_SAVEDATA_SORTTYPE_MODIFIEDTIME;
	xSetList.sortOrder     = CELL_SAVEDATA_SORTORDER_DESCENT;
	xSetList.dirNamePrefix = m_szDirectory;
	xSetList.reserved      = NULL;

	CellSaveDataSetBuf  xSetBuf;
	Asura_Mem::Set(&xSetBuf, 0, sizeof(xSetBuf));
	xSetBuf.dirListMax     = ASURA_PS3_MAX_SINGLE_SAVES;
	xSetBuf.fileListMax    = ASURA_PS3_MAX_DATA_ENTRIES;
	xSetBuf.bufSize        = Asura_Maths::Max( ASURA_PS3_MAX_SINGLE_SAVES * sizeof(CellSaveDataDirList), ASURA_PS3_MAX_DATA_ENTRIES * sizeof(CellSaveDataFileStat) );
	xSetBuf.buf            = Asura_Allocate( xSetBuf.bufSize );
	
	if( xSetBuf.buf == NULL )
	{
		Asura_Assert( false, "[Asura_PS3_SingleStorage::ThreadStorage] - Unable to allocate buffer memory\n" );
		return;
	}

	// Allocate a system memory container for use by the save utility
	sys_memory_container_t xMemoryContainer;
	if ( sys_memory_container_create( &xMemoryContainer, STORAGE_MEMORY_CONTAINER_SIZE) != 0 )
	{
		Asura_Assert( false, "[Asura_PS3_SingleStorage::ThreadStorage] - Unable to allocate system memory container\n" );
		Asura_Deallocate(xSetBuf.buf);
		return;
	}

	int iResult = 0;
	
	// LOADING
	if ( m_bLoading )
	{
		if ( m_bAuto )
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::ThreadStorage] - Autoloading (directory filter '%s')\n", m_szDirectory));
			iResult = cellSaveDataAutoLoad2(CELL_SAVEDATA_VERSION_CURRENT, m_szDirectory, CELL_SAVEDATA_ERRDIALOG_NONE,	&xSetBuf,
			                                &LoadStatusCallback, &LoadFileCallback,
			                                xMemoryContainer, this);
			if ( iResult < CELL_SAVEDATA_RET_OK )
			{
				Asura_Assertf(false, ("[Asura_PS3_SingleStorage::ThreadStorage] - cellSaveDataAutoLoad2 0x%08X\n", iResult));
			}
		}
		else
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::ThreadStorage] - Loading (directory filter '%s')\n", m_szDirectory));
			iResult = cellSaveDataFixedLoad2(CELL_SAVEDATA_VERSION_CURRENT, &xSetList, &xSetBuf,
			                                 &LoadFixedCallback, &LoadStatusCallback, &LoadFileCallback,
			                                 xMemoryContainer, this);
			if ( iResult < CELL_SAVEDATA_RET_OK )
			{
				Asura_Assertf(false, ("[Asura_PS3_SingleStorage::ThreadStorage] - cellSaveDataFixedLoad2 0x%08X\n", iResult));
			}
		}
	}
	// SAVING
	else
	{
		if ( m_bAuto )
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::ThreadStorage] - Autosaving (directory filter '%s')\n", m_szDirectory));
			iResult = cellSaveDataAutoSave2(CELL_SAVEDATA_VERSION_CURRENT, m_szDirectory, CELL_SAVEDATA_ERRDIALOG_NONE, &xSetBuf,
			                                &SaveStatusCallback, &SaveFileCallback,
			                                xMemoryContainer, this);
			if ( iResult < CELL_SAVEDATA_RET_OK )
			{
				Asura_Assertf(false, ("[Asura_PS3_SingleStorage::ThreadStorage] - cellSaveDataAutoSave2 0x%08X\n", iResult));
			}
		}
		else
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::ThreadStorage] - Saving (directory filter '%s')\n", m_szDirectory));
			iResult = cellSaveDataFixedSave2(CELL_SAVEDATA_VERSION_CURRENT, &xSetList, &xSetBuf,
			                                 &SaveFixedCallback, &SaveStatusCallback, &SaveFileCallback,
			                                 xMemoryContainer, this);
			if ( iResult < CELL_SAVEDATA_RET_OK )
			{
				Asura_Assertf(false, ("[Asura_PS3_SingleStorage::ThreadStorage] - cellSaveDataFixedSave2 0x%08X\n", iResult));
			}
		}
	}
	
	iResult = sys_memory_container_destroy( xMemoryContainer );
	if ( iResult < 0 )
	{
		Asura_Assert(false, "[Asura_PS3_SingleStorage::ThreadStorage] - Potential leak in memory container\n");
	}
	
	// Memory cleanup
	Asura_Deallocate(xSetBuf.buf);
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::ThreadStorageProxy
// Description : Thread proxy - this ensures cleanup & shutdown is done 
//               properly, regardless of how the object's thread proc exits.
//
// Parameters  : 
//   uint64_t uParam - Object's this pointer from thread creation call
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::ThreadStorageProxy(uint64_t uParam)
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>(uParam);

	// Delegate to object
	pThis->ThreadStorage();

	bool bDeleteThis = false;
	
	// Completion callback
	if ( pThis->m_pfnCompletionCallback != NULL )
	{
		pThis->m_pfnCompletionCallback(pThis, bDeleteThis);
	}

	// Signal the sync semaphore (in case anybody was waiting for it)
	pThis->m_xCompleted.Signal();

	if ( bDeleteThis )
	{
		delete pThis;
	}

	sys_ppu_thread_exit(0);
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::LoadFixedCallback
// Description : Fixed data loading callback
//
// Parameters  : 
//   CellSaveDataCBResult* pResult  - Result of processing this step
//   CellSaveDataListGet* pGet      - Input from the load system
//   CellSaveDataFixedSet* pSet     - Output from this step
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::LoadFixedCallback( CellSaveDataCBResult* pResult, CellSaveDataListGet* pGet, CellSaveDataFixedSet* pSet )
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>( pResult->userdata );

	if ( pGet->dirNum > pGet->dirListNum )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadFixedCallback] - Too many data directories: expected %d, found %d\n", pGet->dirListNum, pGet->dirNum));
	}

	// Populate return structure
	pSet->dirName = pThis->m_szDirectory;
	pSet->newIcon = NULL;
	pSet->option  = CELL_SAVEDATA_OPTION_NONE;

	// Search for OUR save directory prefix (from the sample)
	u_int uLoop=0;
	while (uLoop < pGet->dirListNum)
	{
		if ( strncmp(pGet->dirList[uLoop].dirName, pThis->m_szDirectory, CELL_SAVEDATA_DIRNAME_SIZE) == 0 )
		{
			break;
		}
		uLoop++;
	}

	// If we found it we can load it
	pResult->result = ( uLoop < pGet->dirListNum ) ? CELL_SAVEDATA_CBRESULT_OK_NEXT : CELL_SAVEDATA_CBRESULT_ERR_NODATA;
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::LoadStatusCallback
// Description : Handle loading status callback
//
// Parameters  : 
//   CellSaveDataCBResult* pResult  - Result of processing this step
//   CellSaveDataStatGet* pGet      - Input from the load system
//   CellSaveDataStatSet* pSet      - Output from this step
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::LoadStatusCallback( CellSaveDataCBResult* pResult, CellSaveDataStatGet* pGet, CellSaveDataStatSet* pSet )
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>( pResult->userdata );

	pSet->reCreateMode = CELL_SAVEDATA_RECREATE_NO_NOBROKEN;
	pSet->setParam = NULL;
#if CELL_SDK_VERSION >= 0x210001
	pSet->indicator = NULL;
#else
	pSet->reserved = NULL;
#endif

	if ( pGet->isNewData )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadStatusCallback] - Data not found\n"));
		pResult->result = CELL_SAVEDATA_CBRESULT_ERR_NODATA;
		return;
	}
	else
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadStatusCallback] - Data found: files %d \n", pGet->fileListNum));

		// Check for borked storage
		if ( (pGet->fileListNum < pGet->fileNum) || (pGet->fileListNum > static_cast<u_int>(ASURA_PS3_MAX_DATA_ENTRIES)) )
		{
			pResult->result = CELL_SAVEDATA_CBRESULT_ERR_BROKEN;
			return;
		}
		
		pThis->m_iTotalEntries = pGet->fileListNum;	  // Max number of entries to load
		
		// Copy entry information into local table
		for ( int iLoop=0; iLoop < pThis->m_iTotalEntries; iLoop++)
		{
			Asura_PS3_SaveDataEntry &rData = pThis->m_axFileEntries[iLoop];
			rData.pData = NULL;
			strncpy( rData.szName, pGet->fileList[iLoop].fileName, ASURA_PS3_SAVE_NAME_LENGTH);   // Name
			rData.iSize = static_cast<int>( pGet->fileList[iLoop].st_size );                      // Size
			rData.eType = static_cast<CellSaveDataFileType>( pGet->fileList[iLoop].fileType );    // Type
		}
	}

	pResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
	pResult->userdata = pThis;
	pThis->m_iLoadEntryIndex = 0;
	return;
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::LoadFileCallback
// Description : Handle the actual file loading operations
//
// Parameters  : 
//   CellSaveDataCBResult* pResult  - Result of processing this step
//   CellSaveDataFileGet* pGet      - Input from the load system
//   CellSaveDataFileSet* pSet      - Output from this step
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::LoadFileCallback( CellSaveDataCBResult* pResult, CellSaveDataFileGet* pGet, CellSaveDataFileSet* pSet )
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>( pResult->userdata );

	if ( pThis->m_iLoadEntryIndex > 0 )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadFileCallback] - Read %d bytes\n", pGet->excSize ));
	}
	
	// This means we've finished!
	if ( pThis->m_iLoadEntryIndex >= pThis->m_iTotalEntries )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadFileCallback] - Done.\n"));
		pResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
		return;
	}

	Asura_PS3_SaveDataEntry& rData = pThis->m_axFileEntries[ pThis->m_iLoadEntryIndex ];

	// Allocate storage for the incoming file
	rData.pData = Asura_Allocate(rData.iSize);	// File size gets stored in the load status callback
	rData.bDeallocate = true;
	if ( rData.pData == NULL )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadFileCallback] - Unable to allocate memory for return storage"));
		pResult->result = CELL_SAVEDATA_CBRESULT_ERR_FAILURE;
		return;
	}

	pSet->fileOperation = CELL_SAVEDATA_FILEOP_READ;
	pSet->fileBuf = rData.pData;
	pSet->fileBufSize = static_cast<u_int>(rData.iSize);
	pSet->fileSize = pSet->fileBufSize;
	pSet->fileName = rData.szName;
	pSet->fileType = rData.eType;
	if (rData.eType == CELL_SAVEDATA_FILETYPE_SECUREFILE )
	{
		Asura_Mem::Copy(pSet->secureFileId, s_acSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE );
	}
	Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::LoadFileCallback] - Reading data entry '%s'\n", rData.szName));  
	
	pThis->m_iLoadEntryIndex++;	 // Increment to the next data entry in the list
	pResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;

	// Update progress bar increment
	pResult->progressBarInc = 100/(pThis->m_iTotalEntries+1);
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::SaveFixedCallback
// Description : Pre-save callback for fixed data
//
// Parameters  : 
//   CellSaveDataCBResult* pResult  - Result of processing this step
//   CellSaveDataListGet* pGet      - Input from the save system
//   CellSaveDataFixedSet* pSet     - Output from this step
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::SaveFixedCallback( CellSaveDataCBResult* pResult, CellSaveDataListGet* pGet, CellSaveDataFixedSet* pSet )
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>( pResult->userdata );

	if ( pGet->dirNum > pGet->dirListNum )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveFixedCallback] - Too many data directories: expected %d, found %d\n", pGet->dirListNum, pGet->dirNum));
	}

	// Search for OUR save directory prefix	(from the sample)
	u_int uLoop=0;
	while (uLoop < pGet->dirListNum)
	{
		if ( strncmp(pGet->dirList[uLoop].dirName, pThis->m_szDirectory, CELL_SAVEDATA_DIRNAME_SIZE) == 0 )
		{
			break;
		}
		uLoop++;
	}

	pSet->dirName = pThis->m_szDirectory;
	pSet->option  = CELL_SAVEDATA_OPTION_NONE;
	
	// If we found it, just update this one
	if( uLoop < pGet->dirListNum )
	{
		pSet->newIcon = NULL;
	}
	else
	{
		int iSize;
		if ( Asura_PS3_AllocateFromFile("/MISC/ICON0.PNG", &(pThis->m_pDataBuffer), &iSize) )
		{
			pThis->m_xIconData.title = "Create New Save";
			pThis->m_xIconData.iconBuf = pThis->m_pDataBuffer;
			pThis->m_xIconData.iconBufSize = static_cast<u_int>(iSize);
			pThis->m_xIconData.reserved = NULL;
		}
		
		pSet->newIcon = pThis->m_bAuto ? NULL : &pThis->m_xIconData;
	}
	
	pResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::SaveStatusCallback
// Description : Save status callback
//
// Parameters  : 
//   CellSaveDataCBResult* pResult   - Result of processing this step
//   CellSaveDataStatGet* pGet       - Input from the save system
//   CellSaveDataStatSet* pSet       - Output from this step
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::SaveStatusCallback( CellSaveDataCBResult* pResult, CellSaveDataStatGet* pGet, CellSaveDataStatSet* pSet )
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>( pResult->userdata );

	pSet->reCreateMode = CELL_SAVEDATA_RECREATE_NO;
	pSet->setParam = &pGet->getParam;
#if CELL_SDK_VERSION >= 0x210001
	pSet->indicator = NULL;
#else
	pSet->reserved = NULL;
#endif

	// Check there's enough free space
	if ( pGet->hddFreeSizeKB - pThis->m_iTotalDataSizeKB < 0 )
	{
		pResult->errNeedSizeKB = pGet->hddFreeSizeKB - pThis->m_iTotalDataSizeKB;
		pResult->result = CELL_SAVEDATA_CBRESULT_ERR_NOSPACE;
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveStatusCallback] - Not enough free space, need %dKb more\n", -pResult->errNeedSizeKB));
		return;
	}

	// If this is a new save then populate new save data
	if ( pGet->isNewData )
	{
		if ( pThis->m_iTotalEntries <= 0 )
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveStatusCallback] - no data entries to save!"));
			pResult->result = CELL_SAVEDATA_CBRESULT_ERR_FAILURE;
			return;
		}

		// Populate the save header	(this is the heading under which all the data entries will be serialised)
		strncpy( pSet->setParam->title, pThis->m_szSaveTitle, CELL_SAVEDATA_SYSP_TITLE_SIZE);
		strncpy( pSet->setParam->subTitle, pThis->m_szSaveSubTitle, CELL_SAVEDATA_SYSP_SUBTITLE_SIZE);
		strncpy( pSet->setParam->detail, pThis->m_szSaveDetail, CELL_SAVEDATA_SYSP_DETAIL_SIZE);
		strncpy( pSet->setParam->listParam, pThis->m_szSaveParam, CELL_SAVEDATA_SYSP_LPARAM_SIZE);
		pSet->setParam->attribute = CELL_SAVEDATA_ATTR_NORMAL;
#if CELL_SDK_VERSION >= 0x210001
        memset( pSet->setParam->reserved2, 0, sizeof(pSet->setParam->reserved2) );
#else
		pSet->setParam->parentalLevel = pThis->m_iParentalLevel;
#endif

        // TODO: shouldn't we be memset'ing pSet->setParam->reserved to 0
        //       here?

		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveStatusCallback] - Creating new save '%s'\n", pSet->setParam->title));
		pThis->m_iSaveEntryIndex = 0;
	}
	else if ( pThis->m_bOverwrite ) // Save already exists, overwrite if required
	{
		strncpy( pSet->setParam->subTitle, pThis->m_szSaveSubTitle, CELL_SAVEDATA_SYSP_SUBTITLE_SIZE);
		strncpy( pSet->setParam->detail, pThis->m_szSaveDetail, CELL_SAVEDATA_SYSP_DETAIL_SIZE);
		strncpy( pSet->setParam->listParam, pThis->m_szSaveParam, CELL_SAVEDATA_SYSP_LPARAM_SIZE);

		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveStatusCallback] - Overwriting existing save '%s'\n", pSet->setParam->title));  
		pThis->m_iSaveEntryIndex = 0;
	}
	else
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveStatusCallback] - Not overwriting save '%s'\n", pSet->setParam->title));  
		pResult->result = CELL_SAVEDATA_CBRESULT_ERR_INVALID;	 // Abort
		return;
	}

	pResult->result   = CELL_SAVEDATA_CBRESULT_OK_NEXT;
	pResult->userdata = pThis;
}




////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_SingleStorage::SaveFileCallback
// Description : Save file operation callback, this is called once for each
//               Entry in the data entries array
//
// Parameters  : 
//   CellSaveDataCBResult* pResult  - Result of processing this step
//   CellSaveDataFileGet* pGet      - Input from the save system
//   CellSaveDataFileSet* pSet      - Output from this step
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_SingleStorage::SaveFileCallback( CellSaveDataCBResult* pResult, CellSaveDataFileGet* pGet, CellSaveDataFileSet* pSet )
{
	Asura_PS3_SingleStorage* pThis = reinterpret_cast<Asura_PS3_SingleStorage*>( pResult->userdata );

	if ( pThis->m_iSaveEntryIndex > 0 )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveFileCallback] - Wrote %d bytes\n", pGet->excSize ));
	}

	// This means we've finished!
	if ( pThis->m_iSaveEntryIndex >= pThis->m_iTotalEntries )
	{
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveFileCallback] - Done.\n"));
		pResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
		return;
	}
	
	Asura_PS3_SaveDataEntry& rData = pThis->m_axFileEntries[ pThis->m_iSaveEntryIndex ];

	// Free the buffer we've been using for file operations so far
	if ( pThis->m_pDataBuffer != NULL )
	{
		Asura_Deallocate(pThis->m_pDataBuffer);
		pThis->m_pDataBuffer = NULL;
	}
	
	// Read in the data if we need to
	if ( rData.pData == NULL )
	{
		Asura_PS3_AllocateFromFile(rData.szName, &rData.pData, &rData.iSize);
		rData.bDeallocate = true;
	}

	// Write the data out
	if ( rData.pData != NULL )
	{
		char *szFilename = rData.szName; // This is aliasing and we modify the string through it!
		
		// Slash removal
		char *szSlash = strchr(rData.szName, '/');
		if ( szSlash != NULL )
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveFileCallback] - Warning: name trimmed at slash\n"));
			szFilename = szSlash + 1;
		}
		
		// Remaining length & 8.3 check
		int iLength = strlen(szFilename);
		if ( ((iLength > 8) && (strchr(rData.szName, '.') == NULL)) || (iLength > 12) )
		{
			Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveFileCallback] - Warning: Entry name '%s' converted to '%s' (must be 8.3)\n"));
			szFilename[8] = '.';
			szFilename[13] = 0;
		}
	
		pSet->fileName      = szFilename;	// use the alias
		pSet->fileOperation = CELL_SAVEDATA_FILEOP_WRITE;
		pSet->fileBuf       = rData.pData;
		pSet->fileBufSize   = static_cast<u_int>(rData.iSize);
		pSet->fileSize      = pSet->fileBufSize;
		pSet->fileType      = rData.eType;
		if (rData.eType == CELL_SAVEDATA_FILETYPE_SECUREFILE )
		{
			Asura_Mem::Copy(pSet->secureFileId, s_acSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE );
		}
		Asura_OutputToDebugger(("[Asura_PS3_SingleStorage::SaveFileCallback] - Writing data entry '%s'\n", szFilename));  
	}
	else
	{
		pResult->result = CELL_SAVEDATA_CBRESULT_ERR_NODATA;
		return;
	}

	// Increment to the next data entry in the list
	pThis->m_iSaveEntryIndex++;
	pResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;

	// Update progress bar increment
	pResult->progressBarInc = 100/(pThis->m_iTotalEntries+1);
}


