//========= Copyright © 2006, Electonic Arts(C) 2006 - All Rights Reserved ============// #include "tier0/platform.h" #ifdef _PS3 #include "FileGroup.h" #include #include #include "memalloc.h" // #include "debug/inc/debug.h" // #include #include "generichash.h" #include #include "ps3/ps3_console.h" #include #include "tier1/strtools.h" #include "tier0/dbg.h" //#include #ifdef _DEBUG int CFileGroupOpenedFile::refCount = 0; int CFileGroupSystem::refCount = 0; int CFileGroup::refCount = 0; #endif const int MAX_OPENED_FILES = 8; const int FILEGROUP_READ_CMD_PRIO = 1000; const int FILEGROUP_READ_CMD_STACKSIZE = 16*1024; #define DebugPrint(fmt, ...) Msg(fmt, ## __VA_ARGS__) static void TidyUpSynchObjects(FileGroupReadBuffer* pBuf) { int ret = sys_lwmutex_destroy(&pBuf->mBufferStartEndPosMutex); if (ret != CELL_OK) { printf("TidyUpSynchObjects sys_lwmutex_destroy failed: %d\n", ret); exit(ret); } ret = sys_cond_destroy(pBuf->mReadThreadSignal); if (ret != CELL_OK) { printf("TidyUpSynchObjects sys_cond_destroy failed: %d\n", ret); exit(ret); } ret = sys_mutex_destroy(pBuf->mReadThreadSignalMutex); if (ret != CELL_OK) { printf("TidyUpSynchObjects sys_mutex_destroy failed: %d\n", ret); exit(ret); } } void FileGroupReadBuffer::WaitOnDiskEjectRecovery() { //Grab mutex int ret = sys_lwmutex_trylock(&mEjectRecoveryMutex); if (ret == CELL_OK) { int fileGroupHandle = 0; CellFsErrno retFs; while(retFs != CELL_FS_SUCCEEDED) { if(fileGroupHandle) { cellFsClose(fileGroupHandle); } retFs = PS3_RetryOpenWhileDiskEjected(mFileName, CELL_FS_O_RDONLY, &fileGroupHandle); if (retFs == CELL_FS_SUCCEEDED) { mFile = fileGroupHandle; uint64_t pos = 0; retFs = cellFsLseek(mFile,(uint64_t)mCurrentPos,CELL_FS_SEEK_SET,&pos); if(retFs == CELL_FS_EIO) { PS3_DisplayDiskErrorDialog(); //Assume dirty disk, rather than disk eject if this point is reached } } else { Msg("FileGroupReadBuffer::WaitOnDiskEjectRecovery: Fatal disk error\n"); exit(-1); } } PS3_ClearDiskErrorDialog(); sys_lwmutex_unlock(&mEjectRecoveryMutex); } else //i.e. another thread is running { Msg("Disk eject waiting on retry\n"); ret = sys_lwmutex_lock(&mEjectRecoveryMutex,0); //Wait for disk eject recovery to complete ret = sys_lwmutex_unlock(&mEjectRecoveryMutex); Msg("Disk eject waiting on retry complete\n"); } } void FileGroupReadBuffer::CloseFile() { if (mFile) { cellFsClose(mFile); mFile = NULL; int ret = sys_lwmutex_destroy(&mEjectRecoveryMutex); if (ret != CELL_OK) { Msg("sys_lwmutex_destroy failed for mEjectRecoveryMutex: %d\n", ret); } ret = sys_lwmutex_destroy(&mBufferMutex); if (ret != CELL_OK) { printf("TidyUpSynchObjects sys_lwmutex_destroy failed: %d\n", ret); } ret = sys_lwmutex_destroy(&mNextConsumeByteMutex); if (ret != CELL_OK) { printf("TidyUpSynchObjects sys_lwmutex_destroy failed: %d\n", ret); } } } void FileGroupReadBuffer::AllocateBuffer(size_t readBufferSize) { MEM_ALLOC_CREDIT(); if (mBuffer) { FreeBuffer(); } mBuffer = new unsigned char[readBufferSize]; mBufferSize = readBufferSize; mCurrentByte = mBuffer; } void FileGroupReadBuffer::FreeBuffer() { if (mBuffer) { delete[] mBuffer; mBuffer = NULL; mBufferSize = 0; mCurrentByte = NULL; mNextConsumePos = 0; mAwaitingConsumer = false; mBufferEndPos = 0; mBufferStartPos = 0; mCurrentPos = 0; } } static void thr_FileGroupRead(uint64_t arg) { FileGroupReadBuffer* pBuf = reinterpret_cast(arg); while (1) { int ret = sys_mutex_lock(pBuf->mReadThreadSignalMutex, 0); while (!pBuf->mAwaitingConsumer && !pBuf->mFileGroupDeleted) // Data ready for read and filegroup not flagged for deletion { ret = sys_lwmutex_lock(&pBuf->mBufferMutex,0); //Read next chunk of data if ((pBuf->mBufferEndPos > 0) && //i.e. we're overwriting existing data (pBuf->mNextConsumePos > 0) && ((pBuf->mCurrentPos + FILEGROUP_READ_CHUNK_SIZE) - pBuf->mNextConsumePos) > (pBuf->mBufferSize - FILEGROUP_READ_STORE_SIZE)) { pBuf->mAwaitingConsumer = true; //Need to wait for consumer to read more data } else { //Do the seek and read #ifdef TIME_FILE_OPERATIONS system_time_t time1 = sys_time_get_system_time(); #endif uint64_t pos; uint64_t bytesRead; ret = cellFsLseek(pBuf->mFile,(int64_t)pBuf->mCurrentPos,CELL_FS_SEEK_SET,&pos); if (ret == CELL_FS_SUCCEEDED) { ret = cellFsRead(pBuf->mFile,pBuf->mCurrentByte,FILEGROUP_READ_CHUNK_SIZE,&bytesRead); } while (ret!= CELL_FS_SUCCEEDED) { cellFsClose(pBuf->mFile); sys_timer_usleep(250000); //Sleep for 0.25 seconds pBuf->WaitOnDiskEjectRecovery(); ret = cellFsLseek(pBuf->mFile,(int64_t)pBuf->mCurrentPos,CELL_FS_SEEK_SET,&pos); if(ret == CELL_FS_SUCCEEDED) { ret = cellFsRead(pBuf->mFile,pBuf->mCurrentByte,FILEGROUP_READ_CHUNK_SIZE,&bytesRead); } if(ret == CELL_FS_EIO) //Assume dirty disk { PS3_DisplayDiskErrorDialog(); } else if (ret == CELL_FS_SUCCEEDED) { PS3_ClearDiskErrorDialog(); } } #ifdef TIME_FILE_OPERATIONS system_time_t time2 = sys_time_get_system_time(); pBuf->m_fReadTime += (time2-time1); pBuf->m_fReadBytes += bytesRead; #endif if (pBuf->mCurrentByte == pBuf->mBuffer) { ret = sys_lwmutex_lock(&pBuf->mBufferStartEndPosMutex,0); pBuf->mBufferStartPos = pBuf->mCurrentPos; ret = sys_lwmutex_unlock(&pBuf->mBufferStartEndPosMutex); } pBuf->mCurrentPos += bytesRead; pBuf->mCurrentByte += bytesRead; if (bytesRead == FILEGROUP_READ_CHUNK_SIZE) { if ((pBuf->mCurrentByte - pBuf->mBuffer) >= pBuf->mBufferSize) { //We've reached the end of the buffer sys_lwmutex_lock(&pBuf->mBufferStartEndPosMutex,0); //Prevent any 'consumes' from calculating available data while buffer is reset pBuf->mBufferEndPos = pBuf->mCurrentPos; pBuf->mCurrentByte = pBuf->mBuffer; if (pBuf->mNextConsumePos == 0) { pBuf->mAwaitingConsumer = true; } sys_lwmutex_unlock(&pBuf->mBufferStartEndPosMutex); } } else { //Assume EOF - await signal from consumer that there is more data to read pBuf->mAwaitingConsumer = true; } } ret = sys_lwmutex_unlock(&pBuf->mBufferMutex); } if (pBuf->mFileGroupDeleted) { int ret = sys_mutex_unlock(pBuf->mReadThreadSignalMutex); TidyUpSynchObjects(pBuf); pBuf->CloseFile(); pBuf->FreeBuffer(); pBuf->mReadThread = NULL; pBuf->mFileGroupDeleted = false; sys_ppu_thread_exit(1); } ret = sys_cond_wait(pBuf->mReadThreadSignal, 0); if (ret == CELL_OK) { pBuf->mAwaitingConsumer = false; } } } CFileGroupSystem::CFileGroupSystem() { m_numFileGroups = 0; m_lastGroupPopulated = -1; m_currentFileGroup = 0; for(int i=0;iClear(); delete m_fileGroups[i]; } } int ret = sys_lwmutex_destroy(&mFileGroupSystemMutex); if (ret != CELL_OK) { printf("CFileGroupSystem::~CFileGroupSystem sys_lwmutex_destroy failed: %d\n", ret); } #ifdef _DEBUG refCount--; #endif } void CFileGroupSystem::Init() { MEM_ALLOC_CREDIT(); //Initialise pool of CFileGroupOpenedFile objects: DebugPrint("Initialising pool of %d FileGroupFilePoolNodes for filegroupsystem\n", MAX_OPENED_FILES); m_openedFilePool = new FileGroupFilePoolNode[MAX_OPENED_FILES]; m_availableFileNodes = m_openedFilePool; for(int i=0;i<(MAX_OPENED_FILES-1);i++) { m_openedFilePool[i].nextNode = &m_openedFilePool[i+1]; } m_openedFilePool[MAX_OPENED_FILES-1].nextNode = NULL; //DebugPrint("Initialising filegroup objects for filegroupsystem\n"); for (int i = 0; i<(MAX_FILE_GROUPS); i++) { m_fileGroups[i] = new CFileGroup(this); m_fileGroups[i]->Clear(); } //int fileSystemMemAllocation = sizeof(FileGroupFilePoolNode)*MAX_OPENED_FILES + sizeof(CFileGroup)*(MAX_FILE_GROUPS); //DebugPrint("Approx filegroupsystem memory usage: %d\n", fileSystemMemAllocation); m_initComplete = true; } int CFileGroupSystem::AddFileGroup(const char* fileGroupName, bool useReadThread /* = true */, int* group_index /* = NULL */, bool usePreload /* = false */, int bufferSize /* = FILEGROUP_READ_CHUNK_SIZE */, int readChunkSize /* = FILEGROUP_READ_CHUNK_SIZE */) { Lock(); //Critical Section int retVal = -1; int fileGroupIndex = -1; if (group_index) { *group_index = -1; } if( bufferSize < FILEGROUP_READ_CHUNK_SIZE * 4 ) { bufferSize = FILEGROUP_READ_CHUNK_SIZE * 4; } Assert(readChunkSize <= bufferSize); if(readChunkSize <= bufferSize) { if (!m_initComplete) //Delay initialisation until this point so that we don't allocate buffer memory unless it's needed { Init(); } CFileGroup* newGroup = NULL; //Get free filegroup object for(int i=0; (iIsPopulated()) { newGroup = m_fileGroups[i]; fileGroupIndex = i; } } if (newGroup) { newGroup->Clear(); int entriesPopulated = newGroup->Populate(fileGroupName, usePreload); if (entriesPopulated > 0) { retVal = entriesPopulated; if (useReadThread) { newGroup->AllocateReadBuffer(bufferSize); newGroup->StartReadThread(); } else { newGroup->AllocateReadBuffer(bufferSize); newGroup->SetReadChunkSize(readChunkSize); } if (fileGroupIndex > -1) { m_numFileGroups++; m_currentFileGroup = fileGroupIndex; if (group_index) { *group_index = fileGroupIndex; //No need to set last group populated - assume caller will specify the index on calls to deletefilegroup } else { m_lastGroupPopulated = fileGroupIndex; } } DebugPrint("CFileGroupSystem: Successfully opened file group named %s as index %d\n", fileGroupName, fileGroupIndex); if(usePreload) { DebugPrint(" using preload\n"); } else { DebugPrint("\n"); } } else { if(entriesPopulated < 0) { DebugPrint("CFileGroupSystem: Error opening file group named %s\n", fileGroupName); } else { DebugPrint("Error opening file group %s, no entries found.\n", fileGroupName); } newGroup->Clear(); } } else { DebugPrint("CFileGroupSystem: Error opening file group %s. All %d file groups are already in use.\n", fileGroupName, MAX_FILE_GROUPS); } } Unlock(); //Critical Section End return retVal; } int CFileGroupSystem::CurrentFileGroup() { return m_lastGroupPopulated; } //Delete most recently added file group void CFileGroupSystem::DeleteFileGroup() { Lock(); if (m_lastGroupPopulated > -1) { DeleteFileGroup(m_lastGroupPopulated); } else { DebugPrint("CFileGroupSystem::deleteFileGroup Error, no filegroups exist\n"); } Unlock(); } void CFileGroupSystem::DeleteFileGroup(int groupIndex) { Lock(); if (groupIndex > -1 && m_fileGroups[groupIndex]) { DeleteFileGroup(m_fileGroups[groupIndex], groupIndex); } else { DebugPrint("CFileGroupSystem::deleteFileGroup Error, filegroup %d does not exist\n", groupIndex); } Unlock(); } void CFileGroupSystem::DeleteFileGroup(CFileGroup* fileGroup) { Lock(); for(int i=0; iGetOpenedCount() == 0) { DebugPrint("Deleting file group %d\n", groupIndex); fileGroup->Clear(); m_numFileGroups--; #ifdef TIME_FILE_OPERATIONS fileGroup->PrintFileGroupStats(); #endif if(fileGroup->UsingReadThread()) { fileGroup->StopReadThread(); } else { fileGroup->CloseFile(); fileGroup->FreeReadBuffer(); } } else { DebugPrint("Flagging file group %d for deletion, %d files still open\n", groupIndex, fileGroup->GetOpenedCount()); fileGroup->FlagForDeletion(); } if (m_currentFileGroup == groupIndex) { m_currentFileGroup = 0; } if (m_lastGroupPopulated == groupIndex) { m_lastGroupPopulated = -1; } Unlock(); } CFileGroupOpenedFile* CFileGroupSystem::FS_fopen( FileIdentifier *filename, const char *options, unsigned int flags, __int64 *size ) { Lock(); //Critical section CFileGroupOpenedFile* retVal = NULL; if ( options[0] == 'r' ) //Filegroup files can only be opened for read { int iFileGroup = m_currentFileGroup; //Start at the current file group for(int i=0; (iIsPopulated()) { retVal = m_fileGroups[iFileGroup]->FS_fopen(filename,options,flags,size); if (retVal) { m_currentFileGroup = iFileGroup; } } } } Unlock(); return retVal; } CFileGroupOpenedFile* CFileGroupSystem::FS_fopen( int filegroup_index, FileIdentifier *filename, const char *options, unsigned int flags, __int64 *size ) { Lock(); //Critical section CFileGroupOpenedFile* retVal = NULL; if ( options[0] == 'r' ) //Filegroup files can only be opened for read { if (filegroup_index >= 0 && filegroup_index < MAX_FILE_GROUPS) { if (m_fileGroups[filegroup_index] && m_fileGroups[filegroup_index]->IsPopulated()) { retVal = m_fileGroups[filegroup_index]->FS_fopen(filename,options,flags,size); } } } Unlock(); return retVal; } int CFileGroupSystem::FS_stat( FileIdentifier *path, CellFsStat *buf ) { Lock(); //Critical section int retVal = -1; int iFileGroup = m_currentFileGroup; //Start at the current file group for(int i=0; (iIsPopulated()) { retVal = m_fileGroups[i]->FS_stat(path,buf); } } Unlock(); return retVal; } int CFileGroupSystem::FS_stat( int filegroup_index, FileIdentifier *path, CellFsStat *buf ) { Lock(); //Critical section int retVal = -1; if (filegroup_index >= 0 && filegroup_index < MAX_FILE_GROUPS) { if (m_fileGroups[filegroup_index] && m_fileGroups[filegroup_index]->IsPopulated()) { retVal = m_fileGroups[filegroup_index]->FS_stat(path,buf); } } Unlock(); return retVal; } int CFileGroupSystem::FS_stat( FileIdentifier *path, struct stat *buf ) { int ret; CellFsStat cellBuf; CellFsErrno retFs = FS_stat(path, &cellBuf); if(retFs == CELL_FS_SUCCEEDED) { buf->st_atime = cellBuf.st_atime; buf->st_blksize = cellBuf.st_blksize; buf->st_ctime = cellBuf.st_ctime; buf->st_gid = cellBuf.st_gid; buf->st_mode = cellBuf.st_mode; buf->st_mtime = cellBuf.st_mtime; buf->st_size = cellBuf.st_size; buf->st_uid = cellBuf.st_uid; buf->st_dev = 0; buf->st_ino = 0; buf->st_nlink = 0; buf->st_rdev = 0; buf->st_blocks = 0; ret = 0; } else { ret = -1; //TBD: SET ERRNO } return ret; } int CFileGroupSystem::FS_stat( int filegroup_index, FileIdentifier *path, struct stat *buf ) { int ret; CellFsStat cellBuf; CellFsErrno retFs = FS_stat(filegroup_index, path, &cellBuf); if(retFs == CELL_FS_SUCCEEDED) { buf->st_atime = cellBuf.st_atime; buf->st_blksize = cellBuf.st_blksize; buf->st_ctime = cellBuf.st_ctime; buf->st_gid = cellBuf.st_gid; buf->st_mode = cellBuf.st_mode; buf->st_mtime = cellBuf.st_mtime; buf->st_size = cellBuf.st_size; buf->st_uid = cellBuf.st_uid; buf->st_dev = 0; buf->st_ino = 0; buf->st_nlink = 0; buf->st_rdev = 0; buf->st_blocks = 0; ret = 0; } else { ret = -1; //TBD: SET ERRNO } return ret; } CFileGroupOpenedFile* CFileGroupSystem::GetOpenedFile() //Get next available CFileGroupOpenedFile from pool { CFileGroupOpenedFile* retVal = NULL; Assert(m_availableFileNodes); if (m_availableFileNodes) //Take the next available node from the head of the list { FileGroupFilePoolNode* thisNode = m_availableFileNodes; retVal = &thisNode->thisFile; //Remove from list of available nodes m_availableFileNodes = thisNode->nextNode; //Add to list of opened nodes thisNode->nextNode = m_openedFileNodes; m_openedFileNodes = thisNode; m_totalOpenedFiles++; } return retVal; } void CFileGroupSystem::FreeOpenedFile(CFileGroupOpenedFile* freeFile) //Get next available CFileGroupOpenedFile from pool { FileGroupFilePoolNode* thisNode = m_openedFileNodes; FileGroupFilePoolNode* prevNode = NULL; //Find the node object in the list of opened files while (thisNode && (&thisNode->thisFile != freeFile)) { prevNode = thisNode; thisNode = thisNode->nextNode; } Assert(thisNode); //Check that the node object was found if (thisNode) { //Remove from list of opened nodes if (prevNode) { prevNode->nextNode = thisNode->nextNode; } else { m_openedFileNodes = thisNode->nextNode; } m_totalOpenedFiles--; //Add to list of available nodes thisNode->nextNode = m_availableFileNodes; m_availableFileNodes = thisNode; } } CFileGroup::CFileGroup(CFileGroupSystem* pFs): mDirectoryEntries(NULL), mFs(pFs), mOpenedCount(0), mPreloadSection(NULL), mPreloadEntries(0) { #ifdef _DEBUG refCount++; #endif mReadBuffer.mFile = NULL; mReadBuffer.mFileBlockSize = 0; mReadBuffer.mFileSectorSize = 0; mReadBuffer.mReadThread = NULL; mReadBuffer.mAwaitingConsumer = false; mReadBuffer.mFileGroupDeleted = false; mReadBuffer.mNextConsumePos = 0; mReadBuffer.mBuffer = NULL; mReadBuffer.mBufferSize = 0; mReadBuffer.mCurrentByte = NULL; mReadBuffer.mBufferEndPos = 0; mReadBuffer.mBufferStartPos = 0; mReadBuffer.mCurrentPos = 0; mReadBuffer.mReadChunkSize = FILEGROUP_READ_CHUNK_SIZE; mFlaggedForDeletion = false; #ifdef TIME_FILE_OPERATIONS ResetFileTimings(); #endif #ifdef MEMCMP_FILE_OPERATIONS mDirectoryExtraInfo = NULL; #endif #ifdef FILEGROUP_USE_HASH_DIRECTORY mHashDirectory = NULL; #endif } CFileGroup::~CFileGroup() { if (mDirectoryEntries) { delete[] mDirectoryEntries; } if (mReadBuffer.mFile) { cellFsClose(mReadBuffer.mFile); } #ifdef _DEBUG refCount--; #endif #ifdef MEMCMP_FILE_OPERATIONS if (mDirectoryExtraInfo) { delete[] mDirectoryExtraInfo; } #endif } unsigned int CFileGroup::HashFileName(const char * fName) { return HashStringCaselessConventional(fName); } #ifdef TIME_FILE_OPERATIONS void CFileGroup::ResetFileTimings() { m_memCopyTime = 0; mReadBuffer.m_fReadTime = 0; mReadBuffer.m_fReadBytes = 0; m_uncompressTime = 0; m_fsReads = 0; m_fgets = 0; m_uncompressCalls = 0; m_rewinds = 0; m_fileJumps = 0; m_bytesReadFromFileByConsumer = 0; m_bytesReadFromBuffer = 0; } #endif void CFileGroup::StartReadThread() { sys_lwmutex_attribute_initialize(mReadBuffer.mBufferStartEndPosMutexAttr); mReadBuffer.mBufferStartEndPosMutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; int ret = sys_lwmutex_create(&mReadBuffer.mBufferStartEndPosMutex, &mReadBuffer.mBufferStartEndPosMutexAttr); if (ret != CELL_OK) { printf("CFileGroup::StartReadThread sys_lwmutex_create failed: %d\n", ret); exit(1); } //Create the condition object sys_mutex_attribute_initialize(mReadBuffer.mReadThreadSignalMutexAttr); ret = sys_mutex_create(&mReadBuffer.mReadThreadSignalMutex, &mReadBuffer.mReadThreadSignalMutexAttr); sys_cond_attribute_initialize(mReadBuffer.mReadThreadSignalAttr); ret = sys_cond_create(&mReadBuffer.mReadThreadSignal, mReadBuffer.mReadThreadSignalMutex, &mReadBuffer.mReadThreadSignalAttr); mReadBuffer.mCurrentPos = mPosOffset; mReadBuffer.mFileGroupDeleted = false; sys_ppu_thread_create(&mReadBuffer.mReadThread, thr_FileGroupRead, reinterpret_cast(&mReadBuffer), FILEGROUP_READ_CMD_PRIO, FILEGROUP_READ_CMD_STACKSIZE, NULL, "Filegroup_ReadThread"); } void CFileGroup::StopReadThread() { mReadBuffer.mFileGroupDeleted = true; sys_cond_signal_to(mReadBuffer.mReadThreadSignal,mReadBuffer.mReadThread); } void CFileGroup::Clear() { Lock(); if (mOpenedCount > 0) { DebugPrint("WARNING: Clearing filegroup while %d entries are still open\n", mOpenedCount); } if (mDirectoryEntries) { delete[] mDirectoryEntries; mDirectoryEntries = NULL; } #ifdef FILEGROUP_USE_HASH_DIRECTORY if (mHashDirectory) { delete[] mHashDirectory; mHashDirectory = NULL; } memset(mHashBuckets,0,FILEGROUP_DIRECTORY_BUCKETS*sizeof(HashBucket)); Assert((FILEGROUP_DIRECTORY_BUCKETS & FILEGROUP_BUCKET_MOD) == 0); //Confirm FILEGROUP_DIRECTORY_BUCKETS is power of two #endif #ifdef MEMCMP_FILE_OPERATIONS if (mDirectoryExtraInfo) { delete[] mDirectoryExtraInfo; mDirectoryExtraInfo = NULL; } #endif if (mPreloadSection) { free(mPreloadSection); mPreloadSection=NULL; } mPreloadEntries = 0; mLastFailedId = 0; mNumDirectoryEntries = 0; mLastOpenedDirectoryIndex = 0; mFlaggedForDeletion = false; mOpenedCount = 0; mReadBuffer.mReadChunkSize = FILEGROUP_READ_CHUNK_SIZE; Unlock(); } char* CFileGroup::ReformatFileName(char* inputFileName) { //Sort out filename... //Fix slashes char* fname = inputFileName; char correctSeparator = '/'; char incorrectSeparator = '\\'; while ( *fname ) { if ( *fname == incorrectSeparator || *fname == correctSeparator) { *fname = correctSeparator; if(fname[1] == incorrectSeparator || fname[1] == correctSeparator) { strcpy(fname+1,fname+2); //Remove multiple slashes from filenames } } fname++; } //Adjust filename to remove the initial "/...path.../game/...gamemode.../" fname = inputFileName; char* ptr = NULL; while ((ptr = strstr(fname,"/game/")) || (ptr = strstr(fname,"/GAME/"))) { fname = ptr + strlen("/game"); } if (fname != inputFileName) //i.e. we located a game directory { if (ptr=strchr(fname+1,'/')) { fname = ptr+1; //Ignore subdirectory } } else { //instead search for stdshaders directory (to handle the special case where the shaders filegroup is generated within the src directory) while ((ptr = strstr(fname,"stdshaders/")) || (ptr = strstr(fname,"STDSHADERS/"))) { fname = ptr + strlen("stdshaders/"); } } //Convert to upper case char *str = fname; while( str && *str ) { *str = (char)toupper(*str); str++; } return fname; } CellFsErrno CFileGroup::PopulateDirectory(DirectoryHeader* hdr, int* entriesPopulated) { DirectoryEntry fullDirEntry; uint64_t bytesRead; mDirectoryEntries = new SmallDirectoryEntry[hdr->mNumEntries]; mNumDirectoryEntries = hdr->mNumEntries; #ifdef FILEGROUP_USE_HASH_DIRECTORY mHashDirectory = new unsigned int[hdr->mNumEntries]; #endif #ifdef MEMCMP_FILE_OPERATIONS mDirectoryExtraInfo = new DirectoryEntryExtraInfo[hdr->mNumEntries]; #endif CellFsErrno retFs = CELL_FS_SUCCEEDED; *entriesPopulated = 0; if(hdr->mVersion == FILEGROUP_FORMAT_VERSION_COMPILED_DIRECTORY) { retFs = cellFsRead(mReadBuffer.mFile,mDirectoryEntries,(uint64_t)(sizeof(SmallDirectoryEntry)*hdr->mNumEntries),&bytesRead); mPosOffset += bytesRead; #ifdef FILEGROUP_USE_HASH_DIRECTORY if(retFs == CELL_FS_SUCCEEDED) { for(int i=0;imNumEntries;i++) { int bucketId = mDirectoryEntries[i].mNameId & FILEGROUP_BUCKET_MOD; ++(mHashBuckets[bucketId].mCount); } } #endif } else { for(int i=0;(imNumEntries)&&(mNumDirectoryEntries>0)&&(retFs == CELL_FS_SUCCEEDED);i++) { //bytesRead = fread(&fullDirEntry,1,sizeof(DirectoryEntry),mReadBuffer.mFile); retFs = cellFsRead(mReadBuffer.mFile,&fullDirEntry,(uint64_t)sizeof(DirectoryEntry),&bytesRead); if(retFs == CELL_FS_SUCCEEDED) { #ifdef MEMCMP_FILE_OPERATIONS strcpy(mDirectoryExtraInfo[i].mFullFileName,fullDirEntry.mName); #endif if (bytesRead != sizeof(DirectoryEntry)) { //ERROR: abort at this point DebugPrint("ERROR: FilegroupSystem error reading directory entry\n"); delete[] mDirectoryEntries; mDirectoryEntries = NULL; mNumDirectoryEntries = 0; return retFs; } else { mPosOffset += bytesRead; mDirectoryEntries[i].mCompressedLength = fullDirEntry.mCompressedLength; mDirectoryEntries[i].mLength = fullDirEntry.mLength; mDirectoryEntries[i].mPosition = fullDirEntry.mPosition; char* fname = ReformatFileName(fullDirEntry.mName); mDirectoryEntries[i].mNameId = HashFileName(fname); #ifdef FILEGROUP_USE_HASH_DIRECTORY int bucketId = mDirectoryEntries[i].mNameId & FILEGROUP_BUCKET_MOD; ++(mHashBuckets[bucketId].mCount); #endif } //if (bytesRead != sizeof(DirectoryEntry)) } //if (retFs == CELL_FS_SUCCEEDED) } //for loop } if(retFs == CELL_FS_SUCCEEDED) { #ifdef FILEGROUP_USE_HASH_DIRECTORY //populate hash directory buckets int currentPos = 0; for(int j = 0; j0) { mHashBuckets[j].mPosition = currentPos; currentPos += mHashBuckets[j].mCount; mHashBuckets[j].mCount=0; //Reset for use as a counter in the next section } } for(int i = 0; i= FILEGROUP_FORMAT_VERSION) && (hdr.mNumEntries > 0) && (!mDirectoryEntries)) //Check that filegroup is not already populated, and that there are entries available to read { retFs = PopulateDirectory(&hdr,&entriesPopulated); if(retFs == CELL_FS_SUCCEEDED && entriesPopulated > 0) { if (hdr.mNumPreloadEntries > 0) { if(mPreloadSection != NULL) { free(mPreloadSection); } mPreloadSection = malloc(mDirectoryEntries[hdr.mNumPreloadEntries].mPosition); retFs = cellFsRead(mReadBuffer.mFile,mPreloadSection,(uint64_t)mDirectoryEntries[hdr.mNumPreloadEntries].mPosition,&bytesRead); if ((retFs == CELL_FS_SUCCEEDED) && (bytesRead==mDirectoryEntries[hdr.mNumPreloadEntries].mPosition)) { mPreloadEntries = hdr.mNumPreloadEntries; } else { printf("ERROR: Unable to read %d bytes of filegroup preload section. Preload disabled\n", mDirectoryEntries[hdr.mNumPreloadEntries].mPosition); } } } //if(retFs == CELL_FS_SUCCEEDED && entriesPopulated > 0) }//if ((hdr.mVersion == FILEGROUP_FORMAT_VERSION) && (hdr.mNumEntries > 0) && (!mDirectoryEntries)) } //if (retFs == CELL_FS_SUCCEEDED) cellfsread hdr } //if (retFs == CELL_FS_SUCCEEDED) cellFsLseek } //if (retFs == CELL_FS_SUCCEEDED) cellFsFGetBlockSize } //while(retFs != CELL_FS_SUCCEEDED) PS3_ClearDiskErrorDialog(); if(entriesPopulated > 0) { //Initialise mutex used for synchronising recovery from disk eject sys_lwmutex_attribute_initialize(mReadBuffer.mEjectRecoveryMutexAttr); int ret = sys_lwmutex_create(&mReadBuffer.mEjectRecoveryMutex, &mReadBuffer.mEjectRecoveryMutexAttr); if (ret != CELL_OK) { Msg("CFileGroup::Populate sys_lwmutex_create of mEjectRecoveryMutex failed: %d\n", ret); } //Initialise read buffer mutex sys_lwmutex_attribute_initialize(mReadBuffer.mBufferMutexAttr); mReadBuffer.mBufferMutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; ret = sys_lwmutex_create(&mReadBuffer.mBufferMutex, &mReadBuffer.mBufferMutexAttr); if (ret != CELL_OK) { printf("CFileGroup::Populate sys_lwmutex_create failed: %d\n", ret); exit(1); } //Initialise read consumer mutex sys_lwmutex_attribute_initialize(mReadBuffer.mNextConsumeByteMutexAttr); mReadBuffer.mNextConsumeByteMutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; ret = sys_lwmutex_create(&mReadBuffer.mNextConsumeByteMutex, &mReadBuffer.mNextConsumeByteMutexAttr); if (ret != CELL_OK) { printf("CFileGroup::Populate sys_lwmutex_create failed: %d\n", ret); exit(1); } mLastOpenedDirectoryIndex = 0; #ifdef TIME_FILE_OPERATIONS ResetFileTimings(); #endif } xit: Unlock(); return entriesPopulated; } int CFileGroup::FindFile(FileIdentifier *pFileName) { int foundIndex = -1; if (!mFlaggedForDeletion && !mReadBuffer.mFileGroupDeleted) { if(!pFileName->mNameIdSet) { pFileName->mNameId = HashFileName(pFileName->mName); pFileName->mNameIdSet = true; } unsigned int index = mLastOpenedDirectoryIndex; //Don't repeat the search if we already know this file doesn't exist if (!mLastFailedId || (pFileName->mNameId != mLastFailedId)) { //First check the current and next positions in the ordered list if ((mDirectoryEntries[index].mNameId == pFileName->mNameId) || //current ((++index < mNumDirectoryEntries) && (mDirectoryEntries[index].mNameId == pFileName->mNameId))) //next { foundIndex = index; } else { #ifdef FILEGROUP_USE_HASH_DIRECTORY //At this point resort to the hash directory int bucketId = pFileName->mNameId & FILEGROUP_BUCKET_MOD; //printf("Using hash table to search %d entries for filename %d in bucket %d\n", mHashBuckets[bucketId].mCount, fileNameId, bucketId); //Search through the relevant bucket for (int i=mHashBuckets[bucketId].mPosition; i< (mHashBuckets[bucketId].mPosition + mHashBuckets[bucketId].mCount) && foundIndex < 0; i++) { if (mDirectoryEntries[mHashDirectory[i]].mNameId == pFileName->mNameId) { foundIndex = mHashDirectory[i]; } } #else //Loop through the remaining directory entries, starting with the current entry for(int i=index, j=0; j<(mNumDirectoryEntries-2) && foundIndex < 0; i++,j++) { //At the end of the list, loop back to the beginning: if (i==mNumDirectoryEntries) { i=0; } if (mDirectoryEntries[i].mNameId == fileNameId) { foundIndex = i; } } #endif } } if(foundIndex < 0) { mLastFailedId = pFileName->mNameId; } } return foundIndex; } CFileGroupOpenedFile* CFileGroup::FS_fopen( FileIdentifier *pFileName, const char *pOptions, unsigned int flags, __int64 *size ) { Lock(); CFileGroupOpenedFile* retVal = NULL; int foundIndex = FindFile(pFileName); if (foundIndex>=0) { CFileGroupOpenedFile* openedFile = mFs->GetOpenedFile(); Assert(openedFile); #ifdef MEMCMP_FILE_OPERATIONS openedFile->Init(mDirectoryEntries[foundIndex],this,&mReadBuffer,mDirectoryExtraInfo[foundIndex].mFullFileName), (foundIndexInit(mDirectoryEntries[foundIndex],this,&mReadBuffer, (foundIndexIsPreloaded()) { #ifdef TIME_FILE_OPERATIONS if (!((foundIndex == mLastOpenedDirectoryIndex) || (foundIndex == mLastOpenedDirectoryIndex+1))) { ++m_fileJumps; // if(mPreloadEntries>0) // { // DebugPrint("Jump from level load filegroup index %d to index %d\n", mLastOpenedDirectoryIndex, foundIndex ); // } } #endif mLastOpenedDirectoryIndex = foundIndex; } if (size) { *size = mDirectoryEntries[foundIndex].mLength; } retVal = openedFile; } Unlock(); return retVal; } int CFileGroup::FS_stat( FileIdentifier *path, CellFsStat *buf ) { Lock(); int retVal = -1; int foundIndex = FindFile(path); if (foundIndex > -1) { //Default the stat details to those of the parent group file *buf = mFileStat; buf->st_size = mDirectoryEntries[foundIndex].mLength; //Set the correct filesize buf->st_mode = S_IFREG | S_IRUSR ; //Set the bitmask to indicate a read only file //CM:TODO Set Block count???? retVal = 0; } Unlock(); return retVal; } void CFileGroup::DecrementOpenedCount() { Lock(); mOpenedCount--; if (mFlaggedForDeletion && (mOpenedCount == 0)) { mFs->DeleteFileGroup(this); } Unlock(); } CFileGroupOpenedFile::CFileGroupOpenedFile() : mEof(false), mSeekPosIndicator(-1), mActualPosIndicator(-1), mParentFileGroup(NULL), mDirEntry(NULL), mPreloaded(false) { #ifdef _DEBUG refCount++; #endif } CFileGroupOpenedFile::~CFileGroupOpenedFile() { #ifdef _DEBUG refCount--; #endif } #ifdef MEMCMP_FILE_OPERATIONS void CFileGroupOpenedFile::Init(const SmallDirectoryEntry& dirEntry, CFileGroup* parentFileGroup, FileGroupReadBuffer* parentBuffer, char* fullFileName, bool preloaded) #else void CFileGroupOpenedFile::Init(const SmallDirectoryEntry& dirEntry, CFileGroup* parentFileGroup, FileGroupReadBuffer* parentBuffer, bool preloaded) #endif { mPreloaded=preloaded; mDirEntry = &dirEntry; mParentFileGroup = parentFileGroup; mParentReadBuffer = parentBuffer; mSeekPosIndicator = 0; mActualPosIndicator = 0; mCompressedPosIndicator = 0; mUncompressedBufferStartPos = -1; if (mDirEntry->mCompressedLength > 0) { //Zlib initialisation mStrm.zalloc = Z_NULL; mStrm.zfree = Z_NULL; mStrm.opaque = Z_NULL; mStrm.avail_in = 0; mStrm.next_in = Z_NULL; Assert( 0 ); //int InfRet = inflateInit(&mStrm); } #ifdef MEMCMP_FILE_OPERATIONS if (!(strstr(fullFileName,"/Resource/"))) { mOrdinaryFile = fopen(fullFileName,"rb"); } else { mOrdinaryFile = NULL; } #endif mParentFileGroup->IncrementOpenedCount(); } void CFileGroupOpenedFile::FS_fclose() { if (mDirEntry->mCompressedLength > 0) { Assert( 0 ); //inflateEnd(&mStrm); } mParentFileGroup->Lock(); mParentFileGroup->DecrementOpenedCount(); mParentFileGroup->FreeOpenedFile(this); #ifdef MEMCMP_FILE_OPERATIONS if (mOrdinaryFile) { fclose(mOrdinaryFile); mOrdinaryFile = NULL; } #endif mParentFileGroup->Unlock(); } void CFileGroupOpenedFile::FS_fseek( __int64 pos, int seekType ) { //mParentFileGroup->Lock(); //CM:TODO Possibly change this so that seek isn't actually done until read???? __int64 absPosition; switch (seekType) { case SEEK_CUR: absPosition = mSeekPosIndicator + pos; break; case SEEK_END: absPosition = mDirEntry->mLength + pos; break; default: absPosition = pos; } bool eofSeek = false; //Record eof setting at this point, but only set mEof following a successful seek if(absPosition > mDirEntry->mLength) { absPosition = mDirEntry->mLength; eofSeek = true; //CM:TODO Raise error condition? } //Don't actually do the seek at this point, wait until next read mSeekPosIndicator = absPosition; mEof = eofSeek; #ifdef MEMCMP_FILE_OPERATIONS if (mOrdinaryFile) { fseek(mOrdinaryFile,pos,seekType); } #endif //if (!isCurrent) //{ // mParentFileGroup->MakeCurrentEntry(this); //} //mParentFileGroup->Unlock(); } long CFileGroupOpenedFile::FS_ftell() { return mSeekPosIndicator; //CM:TODO If an error occurs, -1L is returned, and the global variable errno is set to a positive value. } int CFileGroupOpenedFile::FS_feof() { return mEof; } void CFileGroupOpenedFile::Rewind() { #ifdef TIME_FILE_OPERATIONS mParentFileGroup->m_rewinds++; #endif Assert( 0 ); //inflateEnd(&mStrm); mStrm.zalloc = Z_NULL; mStrm.zfree = Z_NULL; mStrm.opaque = Z_NULL; mStrm.avail_in = 0; mStrm.next_in = Z_NULL; Assert( 0 ); //int InfRet = inflateInit(&mStrm); mCompressedPosIndicator = 0; mActualPosIndicator = 0; mUncompressedBufferStartPos = -1; } void CFileGroupOpenedFile::TraceMemCopy(void* pDest, const void* pSource, size_t nBytes) { #ifdef TIME_FILE_OPERATIONS system_time_t time1 = sys_time_get_system_time(); #endif memcpy(pDest,pSource,nBytes); #ifdef TIME_FILE_OPERATIONS system_time_t time2 = sys_time_get_system_time(); mParentFileGroup->m_memCopyTime += (time2-time1); #endif } //ASSUMPTIONS: destination has readSize bytes available size_t CFileGroupOpenedFile::ReadFromCompressedData( void *dest, size_t readSize) { uint64_t bytesRead = 0; bool dataCopied = false; size_t destBytesWritten = 0; size_t bytesToIgnore = 0; if (mSeekPosIndicator < mActualPosIndicator) //Check for Rewind { //First check if there is any data left over from a previous inflate if ((mUncompressedBufferStartPos > -1) && (mSeekPosIndicator >= mUncompressedBufferStartPos)) { unsigned char* seekPosUncompressedBuffer = mUncompressedBuffer + (mSeekPosIndicator - mUncompressedBufferStartPos); int bytesToCopy = MIN(mActualPosIndicator - mSeekPosIndicator, readSize); TraceMemCopy(dest,seekPosUncompressedBuffer,bytesToCopy); destBytesWritten += bytesToCopy; } else { Rewind(); } } else { bytesToIgnore = mSeekPosIndicator - mActualPosIndicator; } size_t parentCompressedPos = mParentFileGroup->GetPosOffset() + mDirEntry->mPosition + mCompressedPosIndicator; size_t remainingBytesToIgnore; size_t avail_in_before_inflate; size_t remainingCompressedData = mDirEntry->mCompressedLength - mCompressedPosIndicator; while((destBytesWritten < readSize) && (remainingCompressedData > 0)) { bool compressedDataFound = false; if (mPreloaded) { compressedDataFound=true; mStrm.avail_in = remainingCompressedData; mStrm.next_in = (Bytef*)mParentFileGroup->GetPreloadData() + mDirEntry->mPosition + mCompressedPosIndicator; } else { sys_lwmutex_lock(&mParentReadBuffer->mNextConsumeByteMutex,0); //Ensure that no other consumer updates the 'next consume byte' if (parentCompressedPos < mParentReadBuffer->mCurrentPos) { if (mParentFileGroup->UsingReadThread()) { size_t availablePos = 0; sys_lwmutex_lock(&mParentReadBuffer->mBufferStartEndPosMutex,0); //Don't want read thread to 'wrap around' while we calculate the available data //Compressed data may exist in read thread buffer //First find the start of the section of the buffer which won't be overwritten: if ((mParentReadBuffer->mBufferEndPos > 0) && (mParentReadBuffer->mBufferEndPos == mParentReadBuffer->mBufferStartPos)) //Include data from previous buffer cycle { if (mParentReadBuffer->mNextConsumePos > 0) //Read thread won't overwrite FILEGROUP_READ_STORE_SIZE bytes up to NextConsumeByte { if (mParentReadBuffer->mNextConsumePos > (mParentFileGroup->GetPosOffset() + FILEGROUP_READ_STORE_SIZE)) { availablePos = mParentReadBuffer->mNextConsumePos - FILEGROUP_READ_STORE_SIZE; } else { availablePos = mParentFileGroup->GetPosOffset(); } } else { //This should only occur when read thread has filled buffer and no bytes have been read Assert(mParentReadBuffer->mCurrentByte == mParentReadBuffer->mBuffer); Assert(mParentReadBuffer->mAwaitingConsumer); availablePos = mParentReadBuffer->mBufferStartPos; } } else //No data available from previous cycle { if (mParentReadBuffer->mNextConsumePos > 0) { Assert(mParentReadBuffer->mNextConsumePos <= mParentReadBuffer->mCurrentPos); if ((mParentReadBuffer->mNextConsumePos - mParentReadBuffer->mBufferStartPos) > FILEGROUP_READ_STORE_SIZE) { availablePos = mParentReadBuffer->mNextConsumePos - FILEGROUP_READ_STORE_SIZE; } else { availablePos = mParentReadBuffer->mBufferStartPos; } } else { availablePos = mParentReadBuffer->mBufferStartPos; } } //Now check if compressed data exists within the 'safe' portion of the buffer: if (parentCompressedPos >= availablePos) { compressedDataFound = true; if ((mParentReadBuffer->mBufferEndPos > 0) && (mParentReadBuffer->mBufferEndPos == mParentReadBuffer->mBufferStartPos) && (parentCompressedPos < mParentReadBuffer->mBufferEndPos)) //Data wraps round end of buffer { mStrm.avail_in = MIN((mParentReadBuffer->mBufferEndPos - parentCompressedPos),remainingCompressedData); mStrm.next_in = (mParentReadBuffer->mBuffer + mParentReadBuffer->mBufferSize) - (mParentReadBuffer->mBufferEndPos - parentCompressedPos); } else { mStrm.avail_in = MIN((mParentReadBuffer->mCurrentPos - parentCompressedPos),remainingCompressedData); mStrm.next_in = mParentReadBuffer->mBuffer + (parentCompressedPos - mParentReadBuffer->mBufferStartPos); } } sys_lwmutex_unlock(&mParentReadBuffer->mBufferStartEndPosMutex); //Happy for read thread to 'wrap around' from this point on } else { //If not using read thread, we don't need to worry about data being overwritten, check whether the data exists anywhere in the buffer if (parentCompressedPos >= mParentReadBuffer->mBufferStartPos) { compressedDataFound = true; mStrm.avail_in = MIN((mParentReadBuffer->mCurrentPos - parentCompressedPos),remainingCompressedData); mStrm.next_in = mParentReadBuffer->mBuffer + (parentCompressedPos - mParentReadBuffer->mBufferStartPos); } } } } if (!compressedDataFound) { //Read compressed data from file sys_lwmutex_lock(&mParentReadBuffer->mBufferMutex,0); //Lock buffer for write #ifdef TIME_FILE_OPERATIONS system_time_t time1 = sys_time_get_system_time(); #endif uint64_t pos; uint64_t alignedSeekPos = parentCompressedPos - parentCompressedPos%mParentReadBuffer->mFileBlockSize; CellFsErrno retFs = cellFsLseek(mParentReadBuffer->mFile,alignedSeekPos,CELL_FS_SEEK_SET,&pos); if(retFs == CELL_FS_SUCCEEDED) { retFs = cellFsRead(mParentReadBuffer->mFile,mParentReadBuffer->mBuffer,(uint64_t)FILEGROUP_READ_CHUNK_SIZE,&bytesRead); } while(retFs != CELL_FS_SUCCEEDED) { cellFsClose(mParentReadBuffer->mFile); sys_timer_usleep(250000); //Sleep for 0.25 seconds mParentReadBuffer->WaitOnDiskEjectRecovery(); retFs = cellFsLseek(mParentReadBuffer->mFile,(int64_t)alignedSeekPos,CELL_FS_SEEK_SET,&pos); if(retFs == CELL_FS_SUCCEEDED) { retFs = cellFsRead(mParentReadBuffer->mFile,mParentReadBuffer->mBuffer,(uint64_t)FILEGROUP_READ_CHUNK_SIZE,&bytesRead); } if(retFs == CELL_FS_EIO) { PS3_DisplayDiskErrorDialog(); //Assume dirty disk, rather than disk eject if this point is reached } else if(retFs == CELL_FS_SUCCEEDED) { PS3_ClearDiskErrorDialog(); } } if (bytesRead > 0) { //Reset buffer data so that read thread continues from this point mParentReadBuffer->mBufferEndPos = 0; mParentReadBuffer->mBufferStartPos = alignedSeekPos; mParentReadBuffer->mCurrentByte = mParentReadBuffer->mBuffer + (int)bytesRead; mParentReadBuffer->mCurrentPos = alignedSeekPos + (int)bytesRead; mParentReadBuffer->mNextConsumePos = 0; if (mParentFileGroup->UsingReadThread() && mParentReadBuffer->mAwaitingConsumer && mParentReadBuffer->mCurrentPos < mParentFileGroup->Size()) { int ret = sys_cond_signal_to(mParentReadBuffer->mReadThreadSignal,mParentFileGroup->GetReadThread()); } } #ifdef TIME_FILE_OPERATIONS system_time_t time2 = sys_time_get_system_time(); mParentFileGroup->IncrReadTime(time2-time1); mParentFileGroup->m_bytesReadFromFileByConsumer += (int)bytesRead; mParentFileGroup->IncrReadBytes(bytesRead); #endif mStrm.avail_in = MIN((int)(bytesRead - (parentCompressedPos-alignedSeekPos)),remainingCompressedData); mStrm.next_in = mParentReadBuffer->mBuffer+(parentCompressedPos-alignedSeekPos); sys_lwmutex_unlock(&mParentReadBuffer->mBufferMutex); //Now safe for other threads to continue updating buffer } //mParentReadBuffer->mNextConsumeByte = mStrm.next_in; //This ensures bytes aren't overwritten during inflate SHOULD ONLY EVER INCREASE NEXT CONSUME BYTE //Assert(((mParentReadBuffer->mNextConsumeByte - mParentReadBuffer->mBuffer) < FILEGROUP_READ_THREAD_BUFFER_SIZE)); // mStrm.avail_out = FILEGROUP_UNCOMPRESSED_BUFFER_SIZE; // bool directInflate = ((bytesToIgnore <= 0) && (readSize - destBytesWritten) >= FILEGROUP_UNCOMPRESSED_BUFFER_SIZE); // if (directInflate) // { // mStrm.next_out = (unsigned char*)dest + destBytesWritten; // } // else // { // mStrm.next_out = mUncompressedBuffer; // } // while((mStrm.avail_in > 0)&&(destBytesWritten < readSize)) // { #ifdef TIME_FILE_OPERATIONS system_time_t time1 = sys_time_get_system_time(); #endif mStrm.avail_out = FILEGROUP_UNCOMPRESSED_BUFFER_SIZE; mStrm.next_out = mUncompressedBuffer; avail_in_before_inflate = mStrm.avail_in; Assert( 0 ); int ret = 0;//inflate(&mStrm, Z_NO_FLUSH); Assert(ret != Z_STREAM_ERROR); /* state not clobbered */ switch (ret) {case Z_NEED_DICT: ret = Z_DATA_ERROR; /* and fall through */ case Z_DATA_ERROR: case Z_MEM_ERROR: //(void)inflateEnd(&mStrm); Assert(0); printf("Error %d decompressing data, returning %d\n", ret, destBytesWritten); return destBytesWritten; } #ifdef TIME_FILE_OPERATIONS system_time_t time2 = sys_time_get_system_time(); mParentFileGroup->m_uncompressCalls++; mParentFileGroup->m_bytesReadFromBuffer += (avail_in_before_inflate - mStrm.avail_in); mParentFileGroup->m_uncompressTime += (time2-time1); #endif int bytesInflated = FILEGROUP_UNCOMPRESSED_BUFFER_SIZE - mStrm.avail_out; // if (directInflate) // { mUncompressedBufferStartPos = mActualPosIndicator; // } // else // { // mUncompressedBufferStartPos = -1; // } mActualPosIndicator += bytesInflated; if (bytesToIgnore > 0) { if (bytesInflated > bytesToIgnore) { int bytesOfInterest = MIN(readSize,(bytesInflated - bytesToIgnore)); // if (!directInflate) // { TraceMemCopy(dest, mUncompressedBuffer + bytesToIgnore, bytesOfInterest); // } destBytesWritten += bytesOfInterest; bytesToIgnore = 0; } else { bytesToIgnore -= bytesInflated; } } else { int bytesOfInterest = MIN((readSize-destBytesWritten),bytesInflated); // if (!directInflate) // { TraceMemCopy((unsigned char*)dest + destBytesWritten, mUncompressedBuffer, bytesOfInterest); // } destBytesWritten += bytesOfInterest; } mCompressedPosIndicator += (avail_in_before_inflate - mStrm.avail_in); parentCompressedPos += (avail_in_before_inflate - mStrm.avail_in); remainingCompressedData -= (avail_in_before_inflate - mStrm.avail_in); Assert(mStrm.next_in); // } //while inflate loop //Wait until this point before updating NextConsumePos, otherwise data may be overwritten during inflate. //Only update "NextConsumePos" if it has increased, otherwise it is possible to get into a situation // where some of the FILEGROUP_READ_STORE_SIZE bytes leading up to NextConsumePos are overwritten by the read thread. if ((!mPreloaded) && (parentCompressedPos > mParentReadBuffer->mNextConsumePos)) { mParentReadBuffer->mNextConsumePos = parentCompressedPos; //Check if need to signal to read thread if (mParentFileGroup->UsingReadThread() && mParentReadBuffer->mAwaitingConsumer && mParentReadBuffer->mCurrentPos < mParentFileGroup->Size() && ((mParentReadBuffer->mCurrentPos + FILEGROUP_READ_CHUNK_SIZE) - mParentReadBuffer->mNextConsumePos) <= (mParentReadBuffer->mBufferSize - FILEGROUP_READ_SIGNAL_SIZE)) { int ret = sys_cond_signal_to(mParentReadBuffer->mReadThreadSignal,mParentFileGroup->GetReadThread()); } } if (!mPreloaded) { sys_lwmutex_unlock(&mParentReadBuffer->mNextConsumeByteMutex); //Release consumer lock } } //while check available data loop return destBytesWritten; } size_t CFileGroupOpenedFile::ReadFromUncompressedData( void *dest, size_t readSize) { uint64_t bytesRead = 0; bool dataCopied = false; size_t dataSeekPos = mDirEntry->mPosition + mSeekPosIndicator; size_t parentSeekPos = mParentFileGroup->GetPosOffset() + dataSeekPos; size_t destBytesWritten = 0; int ret; if(mPreloaded) { TraceMemCopy((unsigned char*)dest, (Bytef*)mParentFileGroup->GetPreloadData() + dataSeekPos, readSize); destBytesWritten = readSize; mSeekPosIndicator += readSize; } else { sys_lwmutex_lock(&mParentReadBuffer->mNextConsumeByteMutex,0); //Ensure that no other consumer updates the 'next consume byte' size_t dataAvailable = 0; void* dataLocation = NULL; size_t remainingData = mDirEntry->mLength - mSeekPosIndicator; while(destBytesWritten < readSize) { bool dataFound = false; if (parentSeekPos < mParentReadBuffer->mCurrentPos && remainingData > 0) { if (mParentFileGroup->UsingReadThread()) { size_t availablePos = 0; sys_lwmutex_lock(&mParentReadBuffer->mBufferStartEndPosMutex,0); //Don't want read thread to 'wrap around' while we calculate the available data //Data may exist in read thread buffer //First find the start of the section of the buffer which won't be overwritten: if ((mParentReadBuffer->mBufferEndPos > 0) && (mParentReadBuffer->mBufferEndPos == mParentReadBuffer->mBufferStartPos)) //Include data from previous buffer cycle { if (mParentReadBuffer->mNextConsumePos > 0) //Read thread won't overwrite FILEGROUP_READ_STORE_SIZE bytes up to NextConsumeByte { if (mParentReadBuffer->mNextConsumePos > (mParentFileGroup->GetPosOffset() + FILEGROUP_READ_STORE_SIZE)) { availablePos = mParentReadBuffer->mNextConsumePos - FILEGROUP_READ_STORE_SIZE; } else { availablePos = mParentFileGroup->GetPosOffset(); } } else { //This should only occur when read thread has filled buffer and no bytes have been read Assert(mParentReadBuffer->mCurrentByte == mParentReadBuffer->mBuffer); Assert(mParentReadBuffer->mAwaitingConsumer); availablePos = mParentReadBuffer->mBufferStartPos; } } else //No data available from previous cycle { if (mParentReadBuffer->mNextConsumePos > 0) { Assert(mParentReadBuffer->mNextConsumePos <= mParentReadBuffer->mCurrentPos); if ((mParentReadBuffer->mNextConsumePos - mParentReadBuffer->mBufferStartPos) > FILEGROUP_READ_STORE_SIZE) { availablePos = mParentReadBuffer->mNextConsumePos - FILEGROUP_READ_STORE_SIZE; } else { availablePos = mParentReadBuffer->mBufferStartPos; } } else { availablePos = mParentReadBuffer->mBufferStartPos; } } //Now check if compressed data exists within the 'safe' portion of the buffer: if (parentSeekPos >= availablePos) { dataFound = true; if ((mParentReadBuffer->mBufferEndPos > 0) && (mParentReadBuffer->mBufferEndPos == mParentReadBuffer->mBufferStartPos) && (parentSeekPos < mParentReadBuffer->mBufferEndPos)) //Data wraps round end of buffer { dataAvailable = MIN((mParentReadBuffer->mBufferEndPos - parentSeekPos),remainingData); dataLocation = (mParentReadBuffer->mBuffer + mParentReadBuffer->mBufferSize) - (mParentReadBuffer->mBufferEndPos - parentSeekPos); } else { dataAvailable = MIN((mParentReadBuffer->mCurrentPos - parentSeekPos),remainingData); dataLocation = mParentReadBuffer->mBuffer + (parentSeekPos - mParentReadBuffer->mBufferStartPos); } } sys_lwmutex_unlock(&mParentReadBuffer->mBufferStartEndPosMutex); //Happy for read thread to 'wrap around' from this point on } else { //Don't need to worry about read thread overwriting the buffer, check whether the data exists anywhere in the buffer if (parentSeekPos >= mParentReadBuffer->mBufferStartPos) { dataFound = true; dataAvailable = MIN((mParentReadBuffer->mCurrentPos - parentSeekPos),remainingData); dataLocation = mParentReadBuffer->mBuffer + (parentSeekPos - mParentReadBuffer->mBufferStartPos); } } } if (!dataFound) { //Read data from file sys_lwmutex_lock(&mParentReadBuffer->mBufferMutex,0); //Lock buffer for write #ifdef TIME_FILE_OPERATIONS system_time_t time1 = sys_time_get_system_time(); #endif uint64_t pos; uint64_t alignedSeekPos = parentSeekPos - parentSeekPos%mParentReadBuffer->mFileBlockSize; uint64_t alignedReadSize = FILEGROUP_READ_CHUNK_SIZE; if(mParentReadBuffer->mReadChunkSize>FILEGROUP_READ_CHUNK_SIZE) { uint64_t alignedRemainingData = (parentSeekPos + remainingData) - alignedSeekPos; //Locate the remaining data from the alignedseekpos up to the end of the file if(alignedRemainingData > FILEGROUP_READ_CHUNK_SIZE) { alignedReadSize = MIN(mParentReadBuffer->mReadChunkSize,((((parentSeekPos + remainingData) - alignedSeekPos)/FILEGROUP_READ_CHUNK_SIZE)+1)*FILEGROUP_READ_CHUNK_SIZE); } } CellFsErrno retFs = cellFsLseek(mParentReadBuffer->mFile,(int64_t)alignedSeekPos,CELL_FS_SEEK_SET,&pos); if (retFs == CELL_FS_SUCCEEDED) { //snStopMarker(3); //char tempText[100]; //snprintf(tempText, 100, "cellfsread aligned pos %d, seek pos %d, read size %d", alignedSeekPos, parentSeekPos, alignedReadSize); //snStartMarker((unsigned int)mParentReadBuffer->mFile, tempText); retFs = cellFsRead(mParentReadBuffer->mFile,mParentReadBuffer->mBuffer,alignedReadSize,&bytesRead); //snStopMarker((unsigned int)mParentReadBuffer->mFile); //snStartMarker(3, "SyncRead continue"); //DebugPrint("ReadFromUncompressedData: pos %d\n", parentSeekPos); } while(retFs != CELL_FS_SUCCEEDED) { cellFsClose(mParentReadBuffer->mFile); sys_timer_usleep(250000); //Sleep for 0.25 seconds mParentReadBuffer->WaitOnDiskEjectRecovery(); retFs = cellFsLseek(mParentReadBuffer->mFile,(int64_t)alignedSeekPos,CELL_FS_SEEK_SET,&pos); if (retFs == CELL_FS_SUCCEEDED) { retFs = cellFsRead(mParentReadBuffer->mFile,mParentReadBuffer->mBuffer,alignedReadSize,&bytesRead); } if(retFs == CELL_FS_EIO) { PS3_DisplayDiskErrorDialog(); } else if(retFs == CELL_FS_SUCCEEDED) { PS3_ClearDiskErrorDialog(); } } if (bytesRead > 0) { //Reset buffer data so that read thread continues from this point mParentReadBuffer->mBufferEndPos = 0; mParentReadBuffer->mBufferStartPos = alignedSeekPos; mParentReadBuffer->mCurrentByte = mParentReadBuffer->mBuffer + (int)bytesRead; mParentReadBuffer->mCurrentPos = alignedSeekPos + (int)bytesRead; mParentReadBuffer->mNextConsumePos = 0; if (mParentFileGroup->UsingReadThread() && mParentReadBuffer->mAwaitingConsumer && mParentReadBuffer->mCurrentPos < mParentFileGroup->Size()) { int ret = sys_cond_signal_to(mParentReadBuffer->mReadThreadSignal,mParentFileGroup->GetReadThread()); } } #ifdef TIME_FILE_OPERATIONS system_time_t time2 = sys_time_get_system_time(); mParentFileGroup->IncrReadTime(time2-time1); mParentFileGroup->m_bytesReadFromFileByConsumer += (int)bytesRead; mParentFileGroup->IncrReadBytes((int)bytesRead); #endif dataAvailable = MIN((int)(bytesRead-(parentSeekPos-alignedSeekPos)),remainingData); dataLocation = mParentReadBuffer->mBuffer + (parentSeekPos-alignedSeekPos); sys_lwmutex_unlock(&mParentReadBuffer->mBufferMutex); //Now safe for other threads to continue updating buffer } int bytesOfInterest = MIN((readSize-destBytesWritten),dataAvailable); //snStartMarker(1, "MemCpy"); TraceMemCopy((unsigned char*)dest + destBytesWritten, dataLocation, bytesOfInterest); //snStopMarker(1); destBytesWritten += bytesOfInterest; mSeekPosIndicator += bytesOfInterest; parentSeekPos += bytesOfInterest; remainingData = mDirEntry->mLength - mSeekPosIndicator; #ifdef TIME_FILE_OPERATIONS mParentFileGroup->m_bytesReadFromBuffer += bytesOfInterest; #endif //Wait until this point before updating NextConsumePos, otherwise data may be overwritten during inflate. //Only update "NextConsumePos" if it has increased, otherwise it is possible to get into a situation // where some of the FILEGROUP_READ_STORE_SIZE bytes leading up to NextConsumePos are overwritten by the read thread. if (parentSeekPos > mParentReadBuffer->mNextConsumePos) { mParentReadBuffer->mNextConsumePos = parentSeekPos; //Check if need to signal to read thread if (mParentFileGroup->UsingReadThread() && mParentReadBuffer->mAwaitingConsumer && mParentReadBuffer->mCurrentPos < mParentFileGroup->Size() && ((mParentReadBuffer->mCurrentPos + FILEGROUP_READ_CHUNK_SIZE) - mParentReadBuffer->mNextConsumePos) <= (mParentReadBuffer->mBufferSize - FILEGROUP_READ_SIGNAL_SIZE)) { int ret = sys_cond_signal_to(mParentReadBuffer->mReadThreadSignal,mParentFileGroup->GetReadThread()); } } } //while check available data loop sys_lwmutex_unlock(&mParentReadBuffer->mNextConsumeByteMutex); //Release consumer lock } return destBytesWritten; } size_t CFileGroupOpenedFile::FS_fread( void *dest, size_t destSize, size_t size) { MEM_ALLOC_CREDIT(); // Assert(V_stristr(mDirEntry.mName,"bars001c") == NULL); // mParentFileGroup->Lock(); // int readSize = MIN(destSize,size);//Ensure that we don't read more than destSize if (mParentFileGroup->HasBeenDeleted()) { printf("ERROR: Attempting to read from file after filegroup has been deleted\n"); } int readSize = size; //Need to ignore destsize to ensure that we get the same behaviour from filegroups that we get from the ordinary filesystem bool eofRead = false; //Record eof setting at this point, but only set mEof following a successful read if(readSize > (mDirEntry->mLength - mSeekPosIndicator)) { readSize = (mDirEntry->mLength - mSeekPosIndicator); eofRead = true; //CM:TODO Raise error condition? } size_t bytesRead; if (mDirEntry->mCompressedLength > 0) { bytesRead = ReadFromCompressedData(dest,readSize); mSeekPosIndicator = mSeekPosIndicator + bytesRead; } else { bytesRead = ReadFromUncompressedData(dest,readSize); mActualPosIndicator = mSeekPosIndicator + bytesRead; } //CM:TODO Error handling //CM:TODO If this number differs from the bytes requested, set ferror and feof accordingly. //otherwise... //mActualPosIndicator = mSeekPosIndicator + bytesRead; mEof = eofRead; //if (!mParentFileGroup->IsCurrentFile(this)) //{ // mParentFileGroup->MakeCurrentFile(this); //} //mParentFileGroup->Unlock(); #ifdef MEMCMP_FILE_OPERATIONS if (mOrdinaryFile) { unsigned char* tmpDest = new unsigned char[size]; fread(tmpDest,1,size,mOrdinaryFile); Assert((memcmp(tmpDest,dest,size)==0)); delete[] tmpDest; } #endif #ifdef TIME_FILE_OPERATIONS mParentFileGroup->m_fsReads++; #endif return bytesRead; } char * CFileGroupOpenedFile::FS_fgets( char *dest, int destSize ) { //mParentFileGroup->Lock(); // char* retVal = NULL; // int destLimit = MIN(destSize,((mDirEntry->mLength - mSeekPosIndicator) + 1));//Ensure that we don't read past the end of the file // mEof = (destLimit<= 1); // //fgets reads characters until (destLimit-1) characters have been read or either a newline or End-of-File is reached, whichever comes first. // if (mEof) // { // //CM:TODO Set error conditions // } // else // { // if (mDirEntry->mCompressedLength > 0) // { // retVal = FgetsFromCompressedData(dest,destLimit); // } // else // { // retVal = FgetsFromUncompressedData(dest,destLimit); // } // } // if (retVal) // { // mActualPosIndicator+=strlen(retVal); // mSeekPosIndicator = mActualPosIndicator; // //if (!mParentFileGroup->IsCurrentFile(this)) // //{ // // mParentFileGroup->MakeCurrentFile(this); // //} // } // //CM:TODO Error handlng, do ferror and feof need to be set?????. ////mParentFileGroup->Unlock(); //mParentFileGroup->m_fgets++; Assert(0); //fgets not implemented return NULL; } #endif