diff --git a/engine/shared/library/CMakeLists.txt b/engine/shared/library/CMakeLists.txt index d7f56c12..eb93db24 100644 --- a/engine/shared/library/CMakeLists.txt +++ b/engine/shared/library/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(sharedDebug) +add_subdirectory(sharedFile) add_subdirectory(sharedFoundation) add_subdirectory(sharedFoundationTypes) add_subdirectory(sharedMemoryManager) diff --git a/engine/shared/library/sharedFile/CMakeLists.txt b/engine/shared/library/sharedFile/CMakeLists.txt new file mode 100644 index 00000000..544183b9 --- /dev/null +++ b/engine/shared/library/sharedFile/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8) + +project(sharedFile) + +if(WIN32) + add_definitions(/D_CRT_SECURE_NO_WARNINGS) +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/public) + +add_subdirectory(src) diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/AsynchronousLoader.h b/engine/shared/library/sharedFile/include/public/sharedFile/AsynchronousLoader.h new file mode 100644 index 00000000..4007124e --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/AsynchronousLoader.h @@ -0,0 +1 @@ +#include "../../src/shared/AsynchronousLoader.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/ConfigSharedFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/ConfigSharedFile.h new file mode 100644 index 00000000..f4e8d637 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/ConfigSharedFile.h @@ -0,0 +1 @@ +#include "../../src/shared/ConfigSharedFile.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/FileManifest.h b/engine/shared/library/sharedFile/include/public/sharedFile/FileManifest.h new file mode 100644 index 00000000..c6a8714d --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/FileManifest.h @@ -0,0 +1 @@ +#include "../../src/shared/FileManifest.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/FileNameUtils.h b/engine/shared/library/sharedFile/include/public/sharedFile/FileNameUtils.h new file mode 100644 index 00000000..5e9b0bcd --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/FileNameUtils.h @@ -0,0 +1 @@ +#include "../../src/shared/FileNameUtils.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamer.h b/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamer.h new file mode 100644 index 00000000..2e8bdd1a --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamer.h @@ -0,0 +1 @@ +#include "../../src/shared/FileStreamer.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamerFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamerFile.h new file mode 100644 index 00000000..e9f23dee --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamerFile.h @@ -0,0 +1 @@ +#include "../../src/shared/FileStreamerFile.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamerThread.h b/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamerThread.h new file mode 100644 index 00000000..b44f3499 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/FileStreamerThread.h @@ -0,0 +1 @@ +#include "../../src/shared/FileStreamerThread.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/FirstSharedFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/FirstSharedFile.h new file mode 100644 index 00000000..92896d7b --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/FirstSharedFile.h @@ -0,0 +1 @@ +#include "../../src/shared/FirstSharedFile.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/Iff.h b/engine/shared/library/sharedFile/include/public/sharedFile/Iff.h new file mode 100644 index 00000000..16c28ab1 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/Iff.h @@ -0,0 +1 @@ +#include "../../src/shared/Iff.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/IndentedFileWriter.h b/engine/shared/library/sharedFile/include/public/sharedFile/IndentedFileWriter.h new file mode 100644 index 00000000..e1970852 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/IndentedFileWriter.h @@ -0,0 +1 @@ +#include "../../src/shared/IndentedFileWriter.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/MemoryFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/MemoryFile.h new file mode 100644 index 00000000..d4d408f6 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/MemoryFile.h @@ -0,0 +1 @@ +#include "../../src/shared/MemoryFile.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/OsFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/OsFile.h new file mode 100644 index 00000000..561f387b --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/OsFile.h @@ -0,0 +1,7 @@ +#if defined(PLATFORM_WIN32) +#include "../../src/win32/OsFile.h" +#elif defined(PLATFORM_LINUX) +#include "../../src/linux/OsFile.h" +#else +#error unsupported platform +#endif diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/SetupSharedFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/SetupSharedFile.h new file mode 100644 index 00000000..faf76c22 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/SetupSharedFile.h @@ -0,0 +1 @@ +#include "../../src/shared/SetupSharedFile.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/TreeFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/TreeFile.h new file mode 100644 index 00000000..09975be8 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/TreeFile.h @@ -0,0 +1 @@ +#include "../../src/shared/TreeFile.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/TreeFile_SearchNode.h b/engine/shared/library/sharedFile/include/public/sharedFile/TreeFile_SearchNode.h new file mode 100644 index 00000000..a5b048b7 --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/TreeFile_SearchNode.h @@ -0,0 +1 @@ +#include "../../src/shared/TreeFile_SearchNode.h" diff --git a/engine/shared/library/sharedFile/include/public/sharedFile/ZlibFile.h b/engine/shared/library/sharedFile/include/public/sharedFile/ZlibFile.h new file mode 100644 index 00000000..8babd9fd --- /dev/null +++ b/engine/shared/library/sharedFile/include/public/sharedFile/ZlibFile.h @@ -0,0 +1 @@ +#include "../../src/shared/ZlibFile.h" diff --git a/engine/shared/library/sharedFile/src/CMakeLists.txt b/engine/shared/library/sharedFile/src/CMakeLists.txt new file mode 100644 index 00000000..042e7d78 --- /dev/null +++ b/engine/shared/library/sharedFile/src/CMakeLists.txt @@ -0,0 +1,60 @@ + +set(SHARED_SOURCES + shared/AsynchronousLoader.cpp + shared/AsynchronousLoader.h + shared/ConfigSharedFile.cpp + shared/ConfigSharedFile.h + shared/FileManifest.cpp + shared/FileManifest.h + shared/FileNameUtils.cpp + shared/FileNameUtils.h + shared/FileStreamer.cpp + shared/FileStreamerFile.cpp + shared/FileStreamerFile.h + shared/FileStreamer.h + shared/FileStreamerThread.cpp + shared/FileStreamerThread.h + shared/FirstSharedFile.h + shared/Iff.cpp + shared/Iff.h + shared/MemoryFile.cpp + shared/MemoryFile.h + shared/SetupSharedFile.cpp + shared/SetupSharedFile.h + shared/TreeFile.cpp + shared/TreeFile.h + shared/TreeFile_SearchNode.cpp + shared/TreeFile_SearchNode.h + shared/ZlibFile.cpp + shared/ZlibFile.h +) + +if(WIN32) + set(PLATFORM_SOURCES + win32/FirstSharedFile.cpp + win32/OsFile.cpp + win32/OsFile.h + ) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/win32) +else() + set(PLATFORM_SOURCES + linux/OsFile.cpp + linux/OsFile.h + ) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/linux) +endif() + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/shared + ${SWG_ENGINE_SOURCE_DIR}/shared/library/sharedDebug/include/public + ${SWG_ENGINE_SOURCE_DIR}/shared/library/sharedFoundation/include/public + ${SWG_ENGINE_SOURCE_DIR}/shared/library/sharedFoundationTypes/include/public + ${SWG_ENGINE_SOURCE_DIR}/shared/library/sharedMemoryManager/include/public +) + +add_library(sharedFile STATIC + ${SHARED_SOURCES} + ${PLATFORM_SOURCES} +) diff --git a/engine/shared/library/sharedFile/src/linux/OsFile.cpp b/engine/shared/library/sharedFile/src/linux/OsFile.cpp new file mode 100644 index 00000000..a132016f --- /dev/null +++ b/engine/shared/library/sharedFile/src/linux/OsFile.cpp @@ -0,0 +1,123 @@ +// ====================================================================== +// +// OsFile.cpp +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/OsFile.h" + +#include +#include +#include +#include + +// ====================================================================== + +void OsFile::install() +{ +} + +// ---------------------------------------------------------------------- + +bool OsFile::exists(const char *fileName) +{ + struct stat statBuffer; + if (stat(fileName, &statBuffer) != 0) + return false; + + if (S_ISDIR(statBuffer.st_mode)) + return false; + + return true; +} + +// ---------------------------------------------------------------------- + +int OsFile::getFileSize(const char *fileName) +{ + struct stat statBuffer; + int fileSize = 0; + + if (stat(fileName, &statBuffer) == 0) + { + fileSize = static_cast(statBuffer.st_size); + } + return fileSize; +} + +// ---------------------------------------------------------------------- + +OsFile *OsFile::open(const char *fileName, bool randomAccess) +{ + UNREF(randomAccess); + + if (!exists(fileName)) + return 0; + + // attempt to open the file + const int handle = ::open(fileName, O_RDONLY); + FATAL(handle < 0, ("OsFile::open failed to open file %s, errno=%d, which does exist.", fileName, errno)); + + return new OsFile(handle, DuplicateString(fileName)); +} + +// ---------------------------------------------------------------------- + +OsFile::OsFile(int handle, char *fileName) +: + m_handle(handle), + m_length(0), + m_offset(0), + m_fileName(fileName) +{ + m_length = lseek(m_handle, 0, SEEK_END); + lseek(m_handle, 0, SEEK_SET); +} + +// ---------------------------------------------------------------------- + +OsFile::~OsFile() +{ + close(m_handle); + delete [] m_fileName; +} + +// ---------------------------------------------------------------------- + +int OsFile::length() const +{ + return m_length; +} + +// ---------------------------------------------------------------------- + +void OsFile::seek(int newFilePosition) +{ + if (m_offset != newFilePosition) + { + const int result = lseek(m_handle, newFilePosition, SEEK_SET); + DEBUG_FATAL(result != newFilePosition, ("SetFilePointer failed")); + UNREF(result); + m_offset = newFilePosition; + } +} + +// ---------------------------------------------------------------------- + +int OsFile::read(void *destinationBuffer, int numberOfBytes) +{ + int result = 0; + do + { + result = ::read(m_handle, destinationBuffer, numberOfBytes); + DEBUG_FATAL((result < 0 && errno != EAGAIN), ("Read failed for %s: %d %d %s", m_fileName, result, errno, strerror(errno))); + } while (result < 0); + + m_offset += result; + return result; +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/linux/OsFile.h b/engine/shared/library/sharedFile/src/linux/OsFile.h new file mode 100644 index 00000000..c7fdec5a --- /dev/null +++ b/engine/shared/library/sharedFile/src/linux/OsFile.h @@ -0,0 +1,51 @@ +// ====================================================================== +// +// OsFile.h +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_OsFile_H +#define INCLUDED_OsFile_H + +// ====================================================================== + +class OsFile +{ +public: + + static void install(); + + static bool exists(const char *fileName); + static int getFileSize(const char *fileName); + static OsFile *open(const char *fileName, bool randomAccess=false); + +public: + + ~OsFile(); + + int length() const; + int tell() const; + void seek(int newFilePosition); + int read(void *destinationBuffer, int numberOfBytes); + +private: + + OsFile(int handle, char *fileName); + + OsFile(); + OsFile(const OsFile &); + OsFile &operator =(const OsFile &); + +private: + + int m_handle; + int m_length; + int m_offset; + char *m_fileName; +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.cpp b/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.cpp new file mode 100644 index 00000000..be918f39 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.cpp @@ -0,0 +1,705 @@ +// +// AsynchronousLoader.cpp +// Copyright 2002 Sony Online Entertainment +// All Rights Reserved +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/AsynchronousLoader.h" + +#include "sharedDebug/DebugFlags.h" +#include "sharedFile/ConfigSharedFile.h" +#include "sharedFile/Iff.h" +#include "sharedFile/MemoryFile.h" +#include "sharedFile/TreeFile.h" +#include "sharedFoundation/Clock.h" +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/Os.h" +#include "sharedFoundation/MemoryBlockManagerMacros.h" +#include "sharedFoundation/MemoryBlockManager.h" +#include "sharedSynchronization/Mutex.h" +#include "sharedSynchronization/Semaphore.h" +#include "sharedThread/RunThread.h" +#include "sharedThread/ThreadHandle.h" + +#include +#include +#include +#include + +#ifdef _DEBUG +#include +#endif + +// ====================================================================== + +const Tag TAG_ASYN = TAG(A,S,Y,N); +const Tag TAG_EXTN = TAG(E,X,T,N); +const Tag TAG_LOAD = TAG(L,O,A,D); + +// ====================================================================== + +namespace AsynchronousLoaderNamespace +{ + struct ExtensionFunctions + { + char *extension; + AsynchronousLoader::FetchFunction fetchFunction; + AsynchronousLoader::ReleaseFunction releaseFunction; + }; + typedef stdvector::fwd ExtensionFunctionsList; + + struct FileRecord + { + public: + int8 alreadyCached; + const int8 extensionFunctionsIndex; + const char fileName[2]; + + private: + FileRecord(); + FileRecord &operator =(const FileRecord &); + }; + typedef stdvector::fwd FileRecordList; + + class FileMapComparison + { + public: + bool operator ()(const char *lhs, const char *rhs) const; + }; + + typedef stdmap::fwd FileMap; + + struct CachedFile + { + FileRecord *fileRecord; + AbstractFile *file; + const void *resource; + }; + typedef stdvector::fwd CachedFiles; + typedef stdvector::fwd CachedFilesPool; + + struct Request + { + FileRecordList *fileRecordList; + AsynchronousLoader::Callback callback; + void *data; + CachedFiles *cachedFiles; + }; + typedef stddeque::fwd Requests; + + void remove(); + void submitRequest(Request *request); + void threadRoutine(); + +#ifdef _DEBUG + static void debugReport(); +#endif + + bool ms_installed; +#ifdef _DEBUG + bool ms_debugDisable; + bool ms_debugReport; + bool ms_suspendThread; + bool ms_threadIsSuspended; + bool ms_suspendCallbacks; + int ms_numberOfRequests; + int ms_numberOfPendingRequests; + int ms_numberOfCompletedRequests; + int ms_numberOfSubmittedRequests; + int ms_numberOfRetiredRequests; + int ms_numberOfFetchedResources; + int ms_numberOfAlreadyCachedFiles; +#endif + int ms_numberOfCachedBytes; + int ms_numberOfPostponedRequests; + int const cms_postponeThreshold = 8 * 1024 * 1024; + int ms_enabled; + ThreadHandle ms_threadHandle; + Semaphore ms_eventsPending; + Mutex ms_mutex; + Requests ms_pendingRequests; + Requests ms_completedRequests; + char *ms_fileData; + FileMap ms_fileMap; + ExtensionFunctionsList ms_extensionFunctionsList; + CachedFilesPool ms_cachedFilesPool; + MemoryBlockManager *ms_requestMemoryBlockManager; +} +using namespace AsynchronousLoaderNamespace; + +// ====================================================================== + +bool AsynchronousLoaderNamespace::FileMapComparison::operator()(const char *lhs, const char *rhs) const +{ + return strcmp(lhs, rhs) < 0; +} + +// ====================================================================== + +void AsynchronousLoader::install(const char *fileName) +{ + DEBUG_FATAL(ms_installed, ("Already installed")); + + if (!ConfigSharedFile::getEnableAsynchronousLoader()) + return; + + Iff iff; + if (!iff.open(fileName, true)) + { + DEBUG_WARNING(true, ("Could not load asynchronous data file %s\n", fileName)); + ms_installed = false; + return; + } + + // add to exit chain + ExitChain::add(&AsynchronousLoaderNamespace::remove, "AsynchronousLoader::remove"); + + // load all the data + iff.enterForm(TAG_ASYN); + iff.enterForm(TAG_0001); + + // read all the names + iff.enterChunk(TAG_NAME); + ms_fileData = iff.read_char(iff.getChunkLengthTotal()); + iff.exitChunk(TAG_NAME); + + iff.enterChunk(TAG_EXTN); + const int numberOfExtensions = iff.getChunkLengthTotal(sizeof(int32)); + ms_extensionFunctionsList.reserve(numberOfExtensions); + ExtensionFunctions extensionFunctions; + extensionFunctions.extension = NULL; + extensionFunctions.fetchFunction = NULL; + extensionFunctions.releaseFunction = NULL; + for (int i = 0; i < numberOfExtensions; ++i) + { + extensionFunctions.extension = ms_fileData + iff.read_int32(); + + // hack to deal with bad data where the extension pointed at the entire file name instead of just the exception + char * const dot = strrchr(extensionFunctions.extension, '.'); + if (dot) + extensionFunctions.extension = dot + 1; + + ms_extensionFunctionsList.push_back(extensionFunctions); + } + iff.exitChunk(TAG_EXTN); + + // read all the asynchronous loading records + iff.enterChunk(TAG_LOAD); + while (iff.getChunkLengthLeft()) + { + const int32 count = iff.read_int32(); + FileRecordList *fileRecordList = new FileRecordList; + fileRecordList->reserve(count); + + for (int i = 0; i < count; ++i) + fileRecordList->push_back(reinterpret_cast(ms_fileData + iff.read_int32())); + + const bool result = ms_fileMap.insert(FileMap::value_type(fileRecordList->front()->fileName, fileRecordList)).second; + UNREF(result); + DEBUG_FATAL(!result, ("item was already present")); + } + iff.exitChunk(TAG_LOAD); + + iff.exitForm(TAG_0001); + iff.exitForm(TAG_ASYN); + + ms_requestMemoryBlockManager = new MemoryBlockManager("AsynchronousLoader::Request", true, sizeof(Request), 0, 0, 0); + + ms_threadHandle = runNamedThread("AsynchronousLoader", threadRoutine); + switch (ConfigSharedFile::getAsynchronousLoaderPriority()) + { + case -2: ms_threadHandle->setPriority(Thread::kIdle); break; + case -1: ms_threadHandle->setPriority(Thread::kLow); break; + case 0: ms_threadHandle->setPriority(Thread::kNormal); break; + case 1: ms_threadHandle->setPriority(Thread::kHigh); break; + case 2: ms_threadHandle->setPriority(Thread::kCritical); break; + default: ms_threadHandle->setPriority(Thread::kNormal); break; + }; + +#ifdef _DEBUG + DebugFlags::registerFlag(ms_debugDisable, "SharedFile", "runtimeDisableAsynchronousLoader"); + DebugFlags::registerFlag(ms_debugReport, "SharedFile", "reportAsynchronousLoader", &debugReport); + DebugFlags::registerFlag(ms_suspendCallbacks, "SharedFile", "suspendAsynchronousLoaderCallbacks"); + DebugFlags::registerFlag(ms_suspendThread, "SharedFile", "suspendAsynchronousLoaderThread"); +#endif + + ms_installed = true; + ms_enabled = 0; +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoaderNamespace::remove() +{ + DEBUG_FATAL(!ms_installed, ("not installed")); + Request *request = reinterpret_cast(ms_requestMemoryBlockManager->allocate()); + request->fileRecordList = NULL; + request->callback = NULL; + request->data = NULL; + request->cachedFiles = NULL; + + submitRequest(request); + + // wait for all the requests to be serviced + bool empty = false; + while (!empty) + { + Os::sleep(250); + AsynchronousLoader::processCallbacks(); + + ms_mutex.enter(); + empty = ms_pendingRequests.empty() && ms_completedRequests.empty(); + ms_mutex.leave(); + } + + // make sure the thread is dead + ms_threadHandle->wait(); + + delete [] ms_fileData; + + const FileMap::iterator iEnd = ms_fileMap.end(); + for (FileMap::iterator i = ms_fileMap.begin(); i != iEnd; ++i) + delete i->second; + ms_fileMap.clear(); + + const CachedFilesPool::iterator jEnd = ms_cachedFilesPool.end(); + for (CachedFilesPool::iterator j = ms_cachedFilesPool.begin(); j != jEnd; ++j) + delete *j; + ms_cachedFilesPool.clear(); + + delete ms_requestMemoryBlockManager; + ms_requestMemoryBlockManager = NULL; +} + +// ---------------------------------------------------------------------- + +bool AsynchronousLoader::isInstalled() +{ + return ms_installed; +} + +// ---------------------------------------------------------------------- + +bool AsynchronousLoader::isEnabled() +{ +#ifdef _DEBUG + if (ms_debugDisable) + return false; +#endif + + return ms_installed && ms_enabled == 0; +} + +// ---------------------------------------------------------------------- + +bool AsynchronousLoader::isIdle() +{ + ms_mutex.enter(); + bool const idle = ms_pendingRequests.empty() && ms_completedRequests.empty(); + ms_mutex.leave(); + + return idle; +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoader::disable() +{ + --ms_enabled; +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoader::enable() +{ + ++ms_enabled; + DEBUG_FATAL(ms_enabled > 0, ("AsynchronousLoader::enable enabled too many times")); +} + +// ---------------------------------------------------------------------- + +#ifdef _DEBUG + +void AsynchronousLoaderNamespace::debugReport() +{ + DEBUG_REPORT_LOG_PRINT(true, ("%5.2f=fps %3d=sub %3d=pend %3d=comp %3d=ret %3d=pst %5d=kb\n", Clock::framesPerSecond(), ms_numberOfSubmittedRequests, ms_numberOfPendingRequests, ms_numberOfCompletedRequests, ms_numberOfRetiredRequests, ms_numberOfPostponedRequests, ms_numberOfCachedBytes / 1024)); + ms_numberOfSubmittedRequests = 0; + ms_numberOfRetiredRequests = 0; + ms_numberOfFetchedResources = 0; +} + +#endif + +// ---------------------------------------------------------------------- + +void AsynchronousLoader::bindFetchReleaseFunctions(const char *extension, FetchFunction fetchFunction, ReleaseFunction releaseFunction) +{ + DEBUG_FATAL((fetchFunction != 0) ^ (releaseFunction != 0), ("Must supply both a fetch & release function, or neither")); + + ExtensionFunctionsList::iterator iEnd = ms_extensionFunctionsList.end(); + for (ExtensionFunctionsList::iterator i = ms_extensionFunctionsList.begin(); i != iEnd; ++i) + { + ExtensionFunctions &extensionFunctions = *i; + + if (strcmp(extensionFunctions.extension, extension) == 0) + { + extensionFunctions.fetchFunction = fetchFunction; + extensionFunctions.releaseFunction = releaseFunction; + return; + } + } +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoader::add(const char *fileName, Callback callback, void *data) +{ + DEBUG_FATAL(!isEnabled(), ("AsynchronousLoader not enabled")); + NOT_NULL(callback); + + char buffer[Os::MAX_PATH_LENGTH]; + TreeFile::fixUpFileName(fileName, buffer); + + const FileMap::const_iterator i = ms_fileMap.find(buffer); + if (i != ms_fileMap.end()) + { + Request *request = reinterpret_cast(ms_requestMemoryBlockManager->allocate()); + request->fileRecordList = i->second; + request->callback = callback; + request->data = data; + request->cachedFiles = NULL; + submitRequest(request); + } + else + { + // couldn't find the file, can't asynchronously load it. + (*callback)(data); + } +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoader::remove(Callback callback, void *data) +{ + ms_mutex.enter(); + + { + Requests::iterator iEnd = ms_pendingRequests.end(); + for (Requests::iterator i = ms_pendingRequests.begin(); i != iEnd; ++i) + if ((*i)->callback == callback && (*i)->data == data) + { + (*i)->callback = NULL; + (*i)->data = NULL; + } + } + + { + Requests::iterator iEnd = ms_completedRequests.end(); + for (Requests::iterator i = ms_completedRequests.begin(); i != iEnd; ++i) + if ((*i)->callback == callback && (*i)->data == data) + { + (*i)->callback = NULL; + (*i)->data = NULL; + } + } + + ms_mutex.leave(); +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoaderNamespace::submitRequest(Request *request) +{ + ms_mutex.enter(); + ms_pendingRequests.push_back(request); +#ifdef _DEBUG + ++ms_numberOfRequests; + ++ms_numberOfPendingRequests; + ++ms_numberOfSubmittedRequests; +#endif + ms_mutex.leave(); + + ms_eventsPending.signal(); +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoaderNamespace::threadRoutine() +{ + // loop until a quit request is processed + for (;;) + { + // wait until there is a request to processs + ms_eventsPending.wait(); + + bool postpone = false; + + // get the request to service + ms_mutex.enter(); + + Request *request = ms_pendingRequests.front(); + const FileRecordList *fileRecordList = request->fileRecordList; + + if (fileRecordList && ms_numberOfCachedBytes > cms_postponeThreshold) + { + postpone = true; + ms_numberOfPostponedRequests += 1; + } + + ms_mutex.leave(); + + if (postpone) + continue; + + int bytes = 0; + + // make sure the request is still pending + if (fileRecordList && request->callback) + { + // load all the resource + const FileRecordList::const_iterator jEnd = fileRecordList->end(); + for (FileRecordList::const_iterator j = fileRecordList->begin(); j != jEnd; ++j) + { + FileRecord *fileRecord = *j; + + CachedFile cachedFile; + cachedFile.fileRecord = fileRecord; + cachedFile.file = NULL; + cachedFile.resource = NULL; + + // check if the resource is already loaded + if (!fileRecord->alreadyCached) + { + // try to increase the reference count on the resource + const ExtensionFunctions &extensionFunctions = ms_extensionFunctionsList[fileRecord->extensionFunctionsIndex]; + if (extensionFunctions.fetchFunction) + { + cachedFile.resource = extensionFunctions.fetchFunction(fileRecord->fileName); +#ifdef _DEBUG + ++ms_numberOfFetchedResources; +#endif + } + + // othersize, try to open the file + if (!cachedFile.resource) + { + AbstractFile *file = TreeFile::open(fileRecord->fileName, AbstractFile::PriorityLow, true); + if (file) + { + if (file->isZlibCompressed()) + { + bytes += file->getZlibCompressedLength(); + cachedFile.file = file; + } + else + { + bytes += file->length(); + MemoryFile *memoryFile = new MemoryFile(file); + cachedFile.file = memoryFile; + delete file; + } + + // mark the file as already loaded + if (extensionFunctions.fetchFunction) + { + ms_mutex.enter(); + fileRecord->alreadyCached = true; + ms_mutex.leave(); + } + } + } + + // add the file to the loaded list + if (cachedFile.file || cachedFile.resource) + { + if (!request->cachedFiles) + { + // first try to get one from the pool + ms_mutex.enter(); + + if (!ms_cachedFilesPool.empty()) + { + request->cachedFiles = ms_cachedFilesPool.back(); + ms_cachedFilesPool.pop_back(); + } + + ms_mutex.leave(); + } + + if (!request->cachedFiles) + { + // if that didn't work, we have to allocate a new one + request->cachedFiles = new CachedFiles; + } + + request->cachedFiles->push_back(cachedFile); + } + } +#ifdef _DEBUG + else + ++ms_numberOfAlreadyCachedFiles; +#endif + } + + // DEBUG_REPORT_LOG_PRINT(true, ("Async loading done %s (%d)\n", request->fileName, request->cachedFiles.size())); + } + + // move the request to the completed queue + ms_mutex.enter(); + + ms_pendingRequests.pop_front(); + ms_completedRequests.push_back(request); + +#ifdef _DEBUG + --ms_numberOfPendingRequests; + ++ms_numberOfCompletedRequests; +#endif + ms_numberOfCachedBytes += bytes; + + ms_mutex.leave(); + + // a request of no files signals thread termination + if (!fileRecordList) + break; + } +} + +// ---------------------------------------------------------------------- + +void AsynchronousLoader::processCallbacks() +{ +#if defined(_DEBUG) && defined(_WIN32) + if (ms_suspendThread != ms_threadIsSuspended) + { + if (ms_suspendThread) + ms_threadHandle->suspend(); + else + ms_threadHandle->resume(); + + ms_threadIsSuspended = ms_suspendThread; + } +#endif + +#ifdef _DEBUG + if (!ms_suspendCallbacks) +#endif + { + int callbacksAllowed = ConfigSharedFile::getAsynchronousLoaderCallbacksPerFrame(); + + // look for completed requests to service + for (;;) + { + ms_mutex.enter(); + + if (ms_completedRequests.empty()) + { + ms_mutex.leave(); + break; + } + + Request *request = ms_completedRequests.front(); + ms_completedRequests.pop_front(); + + ms_mutex.leave(); + + int bytes = 0; + + if (request->callback) + { + // add all the preloaded files into the TreeFile cache + if (request->cachedFiles) + { + const CachedFiles::iterator iEnd = request->cachedFiles->end(); + for (CachedFiles::iterator i = request->cachedFiles->begin(); i != iEnd; ++i) + { + CachedFile &cachedFile = *i; + if (cachedFile.file) + { + if (cachedFile.file->isZlibCompressed()) + bytes += cachedFile.file->getZlibCompressedLength(); + else + bytes += cachedFile.file->length(); + TreeFile::addCachedFile(cachedFile.fileRecord->fileName, cachedFile.file); + cachedFile.file = NULL; + } + } + } + + // allow the object to load + (*request->callback)(request->data); + + // pitch the loaded files + TreeFile::clearCachedFiles(); + } + + // free any resources that this asynchronous load still owns + if (request->cachedFiles) + { + const CachedFiles::iterator iEnd = request->cachedFiles->end(); + for (CachedFiles::iterator i = request->cachedFiles->begin(); i != iEnd; ++i) + { + CachedFile &cachedFile = *i; + + ms_mutex.enter(); + cachedFile.fileRecord->alreadyCached = false; + ms_mutex.leave(); + + if (cachedFile.file) + { + if (cachedFile.file->isZlibCompressed()) + bytes += cachedFile.file->getZlibCompressedLength(); + else + bytes += cachedFile.file->length(); + delete cachedFile.file; + cachedFile.file = NULL; + } + + if (cachedFile.resource) + { + ms_extensionFunctionsList[cachedFile.fileRecord->extensionFunctionsIndex].releaseFunction(cachedFile.resource); + cachedFile.resource = NULL; + } + } + request->cachedFiles->clear(); + + ms_mutex.enter(); + + ms_cachedFilesPool.push_back(request->cachedFiles); + request->cachedFiles = NULL; + + ms_mutex.leave(); + } + + ms_mutex.enter(); + #ifdef _DEBUG + --ms_numberOfCompletedRequests; + ++ms_numberOfRetiredRequests; + #endif + ms_numberOfCachedBytes -= bytes; + ms_mutex.leave(); + + // free the request + ms_requestMemoryBlockManager->free(request); + + if (callbacksAllowed && --callbacksAllowed == 0) + break; + } + } + + { + ms_mutex.enter(); + int postponed = ms_numberOfPostponedRequests; + ms_numberOfPostponedRequests = 0; + ms_mutex.leave(); + + while (postponed) + { + ms_eventsPending.signal(); + postponed--; + } + } +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.h b/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.h new file mode 100644 index 00000000..4439d4ca --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.h @@ -0,0 +1,41 @@ +// ====================================================================== +// +// AsynchronousLoader.h +// Copyright 2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_AsynchronousLoader_H +#define INCLUDED_AsynchronousLoader_H + +// ====================================================================== + +class AsynchronousLoader +{ +public: + + typedef void (*Callback)(void *data); + typedef const void *(*FetchFunction)(const char *fileName); + typedef void (*ReleaseFunction)(const void *resource); + +public: + + static void install(const char *fileName); + static bool isInstalled(); + + static void disable(); + static void enable(); + static bool isEnabled(); + static bool isIdle(); + + static void bindFetchReleaseFunctions(const char *extension, FetchFunction fetchFunction, ReleaseFunction releaseFunction); + + static void add(const char *fileName, Callback callback, void *data); + static void remove(Callback callback, void *data); + static void processCallbacks(); +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/ConfigSharedFile.cpp b/engine/shared/library/sharedFile/src/shared/ConfigSharedFile.cpp new file mode 100644 index 00000000..2333afee --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/ConfigSharedFile.cpp @@ -0,0 +1,101 @@ +// ====================================================================== +// +// ConfigSharedFile.cpp +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/ConfigSharedFile.h" + +#include "sharedFoundation/ConfigFile.h" + +#include + +// ====================================================================== + +#define KEY_INT(a,b) (ms_ ## a = ConfigFile::getKeyInt("SharedFile", #a, b)) +#define KEY_BOOL(a,b) (ms_ ## a = ConfigFile::getKeyBool("SharedFile", #a, b)) +//#define KEY_FLOAT(a,b) (ms_ ## a = ConfigFile::getKeyFloat("SharedFile", #a, b)) +//#define KEY_STRING(a,b) (ms_ ## a = ConfigFile::getKeyString("SharedFile", #a, b)) + +// ====================================================================== + +namespace ConfigSharedFileNamespace +{ + typedef std::vector StringPtrArray; + bool ms_enableAsynchronousLoader; + int ms_asynchronousLoaderPriority; + int ms_asynchronousLoaderCallbacksPerFrame; + bool ms_validateIff; + StringPtrArray ms_preloads; // ConfigFile owns the pointer +} + +using namespace ConfigSharedFileNamespace; + +// ====================================================================== + +void ConfigSharedFile::install() +{ + KEY_BOOL(enableAsynchronousLoader, true); + KEY_INT(asynchronousLoaderPriority, 0); + KEY_INT(asynchronousLoaderCallbacksPerFrame, 0); + KEY_BOOL(validateIff, false); + + int index = 0; + char const * result = 0; + do + { + result = ConfigFile::getKeyString("SharedFile", "preload", index++, 0); + if (result) + ms_preloads.push_back(result); + } + while (result); +} + +// ---------------------------------------------------------------------- + +bool ConfigSharedFile::getEnableAsynchronousLoader() +{ + return ms_enableAsynchronousLoader; +} + +// ---------------------------------------------------------------------- + +int ConfigSharedFile::getAsynchronousLoaderPriority() +{ + return ms_asynchronousLoaderPriority; +} + +// ---------------------------------------------------------------------- + +int ConfigSharedFile::getAsynchronousLoaderCallbacksPerFrame() +{ + return ms_asynchronousLoaderCallbacksPerFrame; +} + +// ---------------------------------------------------------------------- + +bool ConfigSharedFile::getValidateIff() +{ + return(ms_validateIff); +} + +// ---------------------------------------------------------------------- + +int ConfigSharedFile::getNumberOfTreeFilePreloads() +{ + return static_cast(ms_preloads.size()); +} + +// ---------------------------------------------------------------------- + +char const * ConfigSharedFile::getTreeFilePreload(int const index) +{ + VALIDATE_RANGE_INCLUSIVE_EXCLUSIVE(0, index, getNumberOfTreeFilePreloads()); + return ms_preloads[static_cast(index)]; +} + +// ====================================================================== + diff --git a/engine/shared/library/sharedFile/src/shared/ConfigSharedFile.h b/engine/shared/library/sharedFile/src/shared/ConfigSharedFile.h new file mode 100644 index 00000000..e32743bc --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/ConfigSharedFile.h @@ -0,0 +1,29 @@ +// ====================================================================== +// +// ConfigSharedFile.h +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_ConfigSharedFile_H +#define INCLUDED_ConfigSharedFile_H + +// ====================================================================== + +class ConfigSharedFile +{ +public: + static void install(); + + static bool getEnableAsynchronousLoader(); + static int getAsynchronousLoaderPriority(); + static int getAsynchronousLoaderCallbacksPerFrame(); + static bool getValidateIff(); + static int getNumberOfTreeFilePreloads(); + static char const * getTreeFilePreload(int index); +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/FileManifest.cpp b/engine/shared/library/sharedFile/src/shared/FileManifest.cpp new file mode 100644 index 00000000..3a849751 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileManifest.cpp @@ -0,0 +1,438 @@ +// ====================================================================== +// +// FileManifest.cpp +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2001-2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/FileManifest.h" + +#include "sharedFoundation/ConfigFile.h" +#include "sharedFoundation/Crc.h" +#include "sharedFoundation/ExitChain.h" +#include "fileInterface/StdioFile.h" +#include "sharedFile/FileNameUtils.h" + +#include +#include +#include + +// ====================================================================== + +namespace FileManifestNamespace +{ + struct FileManifestEntry + { + std::string name; + int size; + int accesses; + std::string scene; + }; + + typedef std::map ManifestMap; + typedef std::pair TransitionVectorEntry; + typedef std::vector TransitionVector; + + static ManifestMap s_manifest; + static TransitionVector s_transitionVector; + static bool s_installed = false; + static bool s_updateManifest = false; + static int s_accessThreshold = -1; + static std::string s_currentSceneId = "none"; + static bool s_isValidScene = false; + static bool s_isTransitionScene = true; + + const static std::string s_manifestDataTable = "datatables/manifest/skufree.iff"; + + const static std::string s_validScenes [] = {"tutorial", "space_npe_falcon", "dungeon1:npe_shared_station", "dungeon1:npe_dungeon", "space_ord_mantell"}; + const static int s_numValidScenes = sizeof (s_validScenes) / sizeof (s_validScenes[0]); + + const static std::string s_transitionScenes [] = {"dungeon1", "none", "unknown"}; + const static int s_numTransitionScenes = sizeof (s_transitionScenes) / sizeof (s_transitionScenes[0]); + + const int fileSizeBufferSize = 512; + const int fileNameBufferSize = 512; + const int sceneIdBufferSize = 128; +} +using namespace FileManifestNamespace; + +// ====================================================================== + +void FileManifest::install() +{ + DEBUG_FATAL(s_installed, ("FileManifest::install(): already installed")); + s_installed = true; + + ExitChain::add(FileManifest::remove, "FileManifest::remove", 0, true); + +#if PRODUCTION == 0 + // update the access threshold if one was specified + s_accessThreshold = ConfigFile::getKeyInt("SharedFile", "fileManifestAccessThreshold", -1, s_accessThreshold); + + // if we are developing, and want to update the manifest, read in the tab file as well + const char * manifestFile = ConfigFile::getKeyString("SharedFile", "updateFileManifest", 0, NULL); + if (manifestFile != NULL) + { + s_updateManifest = true; + + if (FileNameUtils::isReadable(manifestFile)) + { + // read in the table version of the manifest + StdioFile inputFile(manifestFile,"r"); + DEBUG_FATAL(!inputFile.isOpen(), ("FileManifet::install(): Could not open %s.", manifestFile)); + + int fileLength = inputFile.length(); + char* fileBuffer = new char[fileLength]; + memset(fileBuffer, 0, fileLength); + + int bytes_read = inputFile.read(fileBuffer, fileLength); + memset(fileBuffer + bytes_read, 0, fileLength - bytes_read); + + char* currentPos = fileBuffer; + char* delimeter; + + // skip past the two header lines of the datatable + currentPos = strstr(currentPos, "\n"); + if (currentPos) + { + ++currentPos; + currentPos = strstr(currentPos, "\n"); + } + if (currentPos) + ++currentPos; + + char* fileSizeBuffer = new char[fileSizeBufferSize]; + int fileSize = 0; + char* fileNameBuffer = new char[fileNameBufferSize]; + char* sceneIdBuffer = new char[sceneIdBufferSize]; + + // iterate through the fileBuffer + while (currentPos && currentPos < fileBuffer + fileLength && strstr(currentPos, "\n")) + { + // clear out all values + fileSize = 0; + memset(fileSizeBuffer, 0, fileSizeBufferSize); + memset(fileNameBuffer, 0, fileNameBufferSize); + memset(sceneIdBuffer, 0, sceneIdBufferSize); + + // get filename + delimeter = strstr(currentPos, "\t"); + if (!delimeter) + { + DEBUG_WARNING(true, ("FileManifest::install(): Couldn't find a tab at position %i in file manfiest table, stopped iterating on elements!\n", currentPos - fileBuffer)); + break; + } + int numCharacters = delimeter - currentPos; + FATAL(numCharacters > fileNameBufferSize, ("Copying too much data!")); + strncpy(fileNameBuffer, currentPos, numCharacters); + currentPos = delimeter + 1; + + // get sceneId + delimeter = strstr(currentPos, "\t"); + if (!delimeter) + { + DEBUG_WARNING(true, ("FileManifest::install(): Couldn't find a tab at position %i in file manfiest table, stopped iterating on elements!\n", currentPos - fileBuffer)); + break; + } + + numCharacters = delimeter - currentPos; + FATAL(numCharacters > sceneIdBufferSize, ("Copying too much data!")); + strncpy(sceneIdBuffer, currentPos, numCharacters); + currentPos = delimeter + 1; + + // get filesize + delimeter = strstr(currentPos, "\n"); + if (!delimeter) + { + DEBUG_WARNING(true, ("FileManifest::install(): Couldn't find a newline at position %i in file manfiest table, stopped iterating on elements!\n", currentPos - fileBuffer)); + break; + } + numCharacters = delimeter - currentPos; + FATAL(numCharacters > fileSizeBufferSize, ("Copying too much data!")); + strncpy(fileSizeBuffer, currentPos, numCharacters); + fileSize = atoi(fileSizeBuffer); + currentPos = delimeter + 1; + + if (isValidScene(sceneIdBuffer)) + addStoredManifestEntry(fileNameBuffer, sceneIdBuffer, fileSize); + } + + delete [] fileBuffer; + delete [] fileSizeBuffer; + delete [] fileNameBuffer; + delete [] sceneIdBuffer; + inputFile.close(); + } + else + { + DEBUG_WARNING(true, ("FileManifest::install(): Couldn't access %s for reading", manifestFile)); + } + } +#endif +} + +// ----------------------------------------------------------------------- + +void FileManifest::remove() +{ + DEBUG_FATAL(!s_installed, ("FileManifest::remove(): not installed")); + s_installed = false; + +#if PRODUCTION == 0 + // dump out the new manifest if requested + const char * manifestFile = ConfigFile::getKeyString("SharedFile", "updateFileManifest", 0, NULL); + if (manifestFile != NULL) + { + StdioFile outputFile(manifestFile,"w"); + DEBUG_FATAL(!outputFile.isOpen(), ("FileManifest::remove(): Could not open %s for writing.", manifestFile)); + char * buffer = new char[1024]; + std::vector manifestEntries; + + sprintf(buffer, "fileName\tsceneId\tfileSize\n"); + outputFile.write(strlen(buffer), buffer); + sprintf(buffer, "s\ts\ti\n"); + outputFile.write(strlen(buffer), buffer); + + DEBUG_REPORT_LOG_PRINT(s_accessThreshold >= 0, ("FileManifestAccessReport:\tFile:\tScene:\tSize:\tAccesses:\n")); + for (ManifestMap::iterator i = s_manifest.begin(); i != s_manifest.end(); ++i) + { + if (!i->second) + { + DEBUG_WARNING(true, ("FileManifest::remove(): Found a null pointer in the fileManifest map!\n")); + continue; + } + + // output the entry in the file + sprintf(buffer, "%s\t%s\t%i\n", ((i->second)->name).c_str(), ((i->second)->scene).c_str(), (i->second)->size); + manifestEntries.push_back(buffer); + + // print and log the entry if it is in the access threshold + DEBUG_REPORT_LOG_PRINT((i->second)->accesses <= s_accessThreshold, ("FileManifestAccessReport:\t%s\t%s\t%i\t%i\n", ((i->second)->name).c_str(), ((i->second)->scene).c_str(), (i->second)->size, (i->second)->accesses)); + + // delete the actual entry + delete i->second; + i->second = 0; + } + + std::sort(manifestEntries.begin(), manifestEntries.end()); + for (std::vector::const_iterator j = manifestEntries.begin(); j != manifestEntries.end(); ++j) + outputFile.write(j->length(), j->c_str()); + + delete [] buffer; + outputFile.close(); + + return; + } +#endif + + for (ManifestMap::iterator i = s_manifest.begin(); i != s_manifest.end(); ++i) + { + // delete the actual entry + delete i->second; + i->second = 0; + } + + s_transitionVector.clear(); +} + +// ----------------------------------------------------------------------- + +void FileManifest::addNewManifestEntry(const char *fileName, int fileSize) +{ +#if PRODUCTION == 0 + // check to see if the config option to update the manifest was set + if (!s_updateManifest) + return; + + // we don't know if the next scene will be valid, so we hang onto these elements just in case + if (s_isTransitionScene) + addToTransitionVector(fileName, fileSize); + + // if we are not in a valid scene, return + if (!s_isValidScene) + return; + + // throw away useless data + if (!fileName || !strlen(fileName) || !strchr(fileName, '/')) + return; + + const uint32 crc = Crc::calculate(fileName); + FileManifestEntry * entry = new FileManifestEntry(); + entry->name = fileName; + entry->scene = s_currentSceneId; + entry->size = fileSize; + entry->accesses = 0; + + std::pair insertReturn = s_manifest.insert(std::pair(crc, entry)); + + // increment the accesses + ++(((insertReturn.first)->second)->accesses); + + // if the insert failed + if (!insertReturn.second) + { + // make sure the file is using the new fileSize - fileSize is 0 if this is entered due to a TreeFile::exists call + if (fileSize) + ((insertReturn.first)->second)->size = fileSize; + + // delete the new entry we created + delete entry; + } +#else + return; +#endif +} + +// ----------------------------------------------------------------------- + +void FileManifest::addStoredManifestEntry(const char *fileName, const char * sceneId, int fileSize) +{ + const uint32 crc = Crc::calculate(fileName); + FileManifestEntry * entry = new FileManifestEntry(); + entry->name = fileName; + entry->scene = sceneId; + entry->size = fileSize; + entry->accesses = 0; + + std::pair insertReturn = s_manifest.insert(std::pair(crc, entry)); + + // if the insert failed, delete the entry we created + if (!insertReturn.second) + delete entry; +} + +// ----------------------------------------------------------------------- + +void FileManifest::addToTransitionVector(const char *fileName, int fileSize) +{ + TransitionVectorEntry entry(fileName, fileSize); + s_transitionVector.push_back(entry); +} + +// ----------------------------------------------------------------------- + +void FileManifest::addTransitionElementsToManifest() +{ + TransitionVector::const_iterator end = s_transitionVector.end(); + for (TransitionVector::const_iterator i = s_transitionVector.begin(); i != end; ++i) + addNewManifestEntry((i->first).c_str(), i->second); + clearAllTransitionElements(); +} + +// ----------------------------------------------------------------------- + +void FileManifest::clearAllTransitionElements() +{ + s_transitionVector.clear(); +} + +// ----------------------------------------------------------------------- + +void FileManifest::setSceneId(const char *newScene) +{ +#if PRODUCTION == 0 + // check to see if the config option to update the manifest was set + if (!s_updateManifest) + return; + + // don't do anything if the scene hasn't changed + if (s_currentSceneId.compare(newScene) == 0) + return; + + if (strlen(newScene) == 0) + s_currentSceneId = "unknown"; + else + s_currentSceneId = newScene; + + // update whether this scene is a valid one that we want to add entries into, or if this is a + // scene we want to wait on (we wait until we get a valid or invalid scene) + // if we get an invalid scene, we throw away the entries we accumulated + // if we get a valid scene, we add all the entries we accumulated + // this functionality is necessary because of how the client gets information about its scene, and + // when it gets information about the buildout location it is in + s_isValidScene = isValidScene(s_currentSceneId.c_str()); + s_isTransitionScene = isTransitionScene(s_currentSceneId.c_str()); + + // on any scene transition, we either clear out or use all entries in the transition vector + // unless we are going to another wait scene, in which case we keep accumulating + if (s_isValidScene) + addTransitionElementsToManifest(); + else if (!s_isTransitionScene) + clearAllTransitionElements(); +#else + return; +#endif +} + +// ----------------------------------------------------------------------- + +bool FileManifest::isValidScene(const char *scene) +{ + std::string sceneString(scene); + for (int i = 0; i < s_numValidScenes; ++i) + { + // if the scene matches the name, return true + if (sceneString.compare(s_validScenes[i]) == 0) + return true; + //otherwise, if the scene name is _, we return true (multiple zones) + else if (sceneString.find(s_validScenes[i] + "_", 0) == 0 && sceneString.length() == (s_validScenes[i].length() + 2)) + return true; + } + return false; +} + +// ----------------------------------------------------------------------- + +bool FileManifest::isTransitionScene(const char *scene) +{ + std::string sceneString(scene); + for (int i = 0; i < s_numTransitionScenes; ++i) + { + // if the scene matches the name, return true + if (sceneString.compare(s_transitionScenes[i]) == 0) + return true; + //otherwise, if the scene name is _, we return true (multiple zones) + else if (sceneString.find(s_transitionScenes[i] + "_", 0) == 0 && sceneString.length() == (s_transitionScenes[i].length() + 2)) + return true; + } + return false; +} + +// ----------------------------------------------------------------------- + +bool FileManifest::contains(const char *fileName) +{ + uint32 crc = Crc::calculate(fileName); + return contains(crc); +} + +// ----------------------------------------------------------------------- + +bool FileManifest::contains(uint32 crc) +{ + ManifestMap::iterator i = s_manifest.find(crc); + + if (i == s_manifest.end()) + return false; + + return true; +} + +// ----------------------------------------------------------------------- + +std::string FileManifest::getDatatableName() +{ + return s_manifestDataTable; +} + +// ----------------------------------------------------------------------- + +bool FileManifest::shouldUpdateManifest() +{ + return s_updateManifest; +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/FileManifest.h b/engine/shared/library/sharedFile/src/shared/FileManifest.h new file mode 100644 index 00000000..8f6ae5c4 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileManifest.h @@ -0,0 +1,59 @@ +// ====================================================================== +// +// FileManifest.h +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2001-2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_FileManifest_H +#define INCLUDED_FileManifest_H + +#include + +// ====================================================================== + +/** + * This class keeps information about what files have been opened via this client + * in order to track what assets are necessary for this client to run on the + * server it is connecting to. It will read in and dump out it's info to a file if + * the config setting is set. + */ +class FileManifest +{ +public: + + static void install(); + static void remove(); + + static void addNewManifestEntry(const char *fileName, int fileSize); + static void addStoredManifestEntry(const char *fileName, const char *sceneId, int fileSize); + static void addToTransitionVector(const char *fileName, int fileSize); + + static void addTransitionElementsToManifest(); + static void clearAllTransitionElements(); + + static void setSceneId(const char *newScene); + static bool isValidScene(const char *); + static bool isTransitionScene(const char *); + + static bool contains(const char *fileName); + static bool contains(uint32 crc); + + static std::string getDatatableName(); + static bool shouldUpdateManifest(); + +private: + + /// disabled + FileManifest(); + /// disabled + FileManifest(const FileManifest &); + /// disabled + FileManifest &operator =(const FileManifest &); +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/FileNameUtils.cpp b/engine/shared/library/sharedFile/src/shared/FileNameUtils.cpp new file mode 100644 index 00000000..01ac3c98 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileNameUtils.cpp @@ -0,0 +1,263 @@ +// ============================================================================ +// +// FileNameUtils.cpp +// Copyright Sony Online Entertainment +// +// ============================================================================ + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/FileNameUtils.h" + +#include +#include + +// ============================================================================ +// +// FileNameUtils +// +// ============================================================================ + +//----------------------------------------------------------------------------- +std::string FileNameUtils::get(std::string const &path, int const flags) +{ + std::string result; + + if (flags & drive) + { + result += getDrive(path); + } + if (flags & directory) + { + result += getDirectory(path); + } + if (flags & fileName) + { + result += getFileName(path); + } + if (flags & extension) + { + std::string const & extension = getExtension(path); + + if ( !extension.empty() + && (flags & fileName)) + { + result += "."; + } + + result += extension; + } + + return result; +} + +//----------------------------------------------------------------------------- +bool FileNameUtils::isReadable(std::string const &path) +{ + bool result = false; + + if (!path.empty()) + { + FILE *fp = fopen(path.c_str(), "r"); + + result = (fp != NULL); + + if (fp != NULL) + { + fclose(fp); + } + } + + return result; +} + +//----------------------------------------------------------------------------- +bool FileNameUtils::isWritable(std::string const &path) +{ + bool result = false; + + if (!path.empty()) + { + FILE *fp = fopen(path.c_str(), "w"); + + result = (fp != NULL); + + if (fp != NULL) + { + fclose(fp); + } + } + + return result; +} + +//----------------------------------------------------------------------------- +bool FileNameUtils::isIff(std::string const &path) +{ + bool result = false; + FILE *fp = fopen(path.c_str(), "rt"); + + if ((fp) && + (getc(fp) == 'F') && + (getc(fp) == 'O') && + (getc(fp) == 'R') && + (getc(fp) == 'M')) + { + result = true; + fclose(fp); + } + + return result; +} + +//----------------------------------------------------------------------------- +std::string FileNameUtils::getDrive(std::string const &path) +{ + std::string drive; + + // Check if local drive + + size_t const semicolonIndex = path.find(':'); + + if (semicolonIndex != std::string::npos) + { + drive = path.substr(0, semicolonIndex + 1); + } + else + { + std::string fixedUpPath(path); + swapChar(fixedUpPath, '\\', '/'); + + // Check if network drive + + size_t const doubleSlashIndex = fixedUpPath.find("//"); + + if (doubleSlashIndex != std::string::npos) + { + size_t const firstSlashIndex = fixedUpPath.find('/', doubleSlashIndex + 2); + + if (firstSlashIndex != std::string::npos) + { + drive = fixedUpPath.substr(doubleSlashIndex, firstSlashIndex - doubleSlashIndex); + } + } + } + + return drive; +} + +//----------------------------------------------------------------------------- +std::string FileNameUtils::getDirectory(std::string const &path) +{ + std::string fixedUpPath(path); + swapChar(fixedUpPath, '\\', '/'); + + std::string directory; + + // Extract the filename position + + size_t const lastSlashIndex = fixedUpPath.rfind('/'); + + // Extract network drive position + + size_t const doubleSlashIndex = fixedUpPath.find("//"); + + // Extract the directory position + + size_t const firstSlashIndex = fixedUpPath.find('/', (doubleSlashIndex != std::string::npos) ? (doubleSlashIndex + 2) : 0); + + // Extract the directory + + if (firstSlashIndex != std::string::npos) + { + directory = fixedUpPath.substr(firstSlashIndex, lastSlashIndex - firstSlashIndex + 1); + } + + return directory; +} + +//----------------------------------------------------------------------------- +std::string FileNameUtils::getFileName(std::string const &path) +{ + std::string fixedUpPath(path); + swapChar(fixedUpPath, '\\', '/'); + + std::string fileName; + + // Extract the extension position + + size_t const dotIndex = fixedUpPath.rfind('.'); + + // Extract the filename position + + size_t const lastSlashIndex = fixedUpPath.rfind('/'); + + // Extract the filename + + if (lastSlashIndex != std::string::npos) + { + if (dotIndex == std::string::npos) + { + // Extension was not found + + fileName = fixedUpPath.substr(lastSlashIndex + 1); + } + else + { + // Extension was found + + fileName = fixedUpPath.substr(lastSlashIndex + 1, dotIndex - lastSlashIndex - 1); + } + } + else + { + // There is no path + + if (dotIndex == std::string::npos) + { + // Extension was not found + + fileName = fixedUpPath; + } + else + { + // Extension was found + + fileName = fixedUpPath.substr(0, dotIndex); + } + } + + return fileName; +} + +//----------------------------------------------------------------------------- +std::string FileNameUtils::getExtension(std::string const &path) +{ + std::string extension; + + // Extract the extension + + size_t const dotIndex = path.rfind('.'); + + if (dotIndex != std::string::npos) + { + extension = path.substr(dotIndex + 1); + } + + return extension; +} + +//----------------------------------------------------------------------------- +void FileNameUtils::swapChar(std::string &path, char const sourceCharacter, char const destinationCharacter) +{ + std::string::iterator iterPath = path.begin(); + + for (; iterPath != path.end(); ++iterPath) + { + if (*iterPath == sourceCharacter) + { + *iterPath = destinationCharacter; + } + } +} + +// ============================================================================ diff --git a/engine/shared/library/sharedFile/src/shared/FileNameUtils.h b/engine/shared/library/sharedFile/src/shared/FileNameUtils.h new file mode 100644 index 00000000..84540f82 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileNameUtils.h @@ -0,0 +1,64 @@ +// ============================================================================ +// +// FileNameUtils.h +// Copyright Sony Online Entertainment +// +// ============================================================================ + +#ifndef INCLUDED_FileNameUtils_H +#define INCLUDED_FileNameUtils_H + +//----------------------------------------------------------------------------- +class FileNameUtils +{ +public: + + enum + { + drive = 1L << 0, + directory = 1L << 1, + fileName = 1L << 2, + extension = 1L << 3 + }; + + // Returns a combination of items depending on the enums passed in above. + // Ex: get(FileNameUtils::fileName | FileNameUtils::extension) returns + // the "filename.extension", notice the dot is automatically appended. + + static std::string get(std::string const &path, int const flags); + + // Returns only the local or network drive + + static std::string getDrive(std::string const &path); + + // Returns only the diretory + + static std::string getDirectory(std::string const &path); + + // Returns only the filename, no extension or dot + + static std::string getFileName(std::string const &path); + + // Returns just the extension, no dot + + static std::string getExtension(std::string const &path); + + static bool isReadable(std::string const &path); + static bool isWritable(std::string const &path); + static bool isIff(std::string const &path); + + static void swapChar(std::string &path, char const sourceCharacter, char const destinationCharacter); + +private: + + // Disabled + + FileNameUtils(); + ~FileNameUtils(); + FileNameUtils(FileNameUtils const &); + FileNameUtils &operator =(FileNameUtils const &); +}; + +// ============================================================================ + +#endif // INCLUDED_FileNameUtils_H diff --git a/engine/shared/library/sharedFile/src/shared/FileStreamer.cpp b/engine/shared/library/sharedFile/src/shared/FileStreamer.cpp new file mode 100644 index 00000000..6c47b26c --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileStreamer.cpp @@ -0,0 +1,269 @@ +// ====================================================================== +// +// FileStreamer.cpp +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2002 Sony Online Entertainment +// All Rights Reserved +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/FileStreamer.h" + +#include "sharedFile/FileStreamerThread.h" +#include "sharedFile/OsFile.h" +#include "sharedFoundation/CrashReportInformation.h" +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/Production.h" +#include "sharedFoundation/PerThreadData.h" +#include "sharedFoundation/MemoryBlockManager.h" +#include "sharedSynchronization/Gate.h" + +#include + +// ====================================================================== + +bool FileStreamer::ms_installed; +bool FileStreamer::ms_useThread; + +// ====================================================================== + +namespace FileStreamerNamespace +{ + bool reportModDirectory(char const * const fileName ,char const * const directory, char const * const description); +} + +using namespace FileStreamerNamespace; + +// ====================================================================== +// Install the FileStreamer + +void FileStreamer::install(bool useThread) +{ + DEBUG_FATAL(ms_installed, ("FileStreamer::install already installed")); + ms_installed = true; + ms_useThread = useThread; + File::install(ms_useThread); + + if (ms_useThread) + FileStreamerThread::install(); + + ExitChain::add(FileStreamer::remove, "FileStreamer::remove", 0, true); +} + +// ---------------------------------------------------------------------- +/** + * Remove the FileStreamer subsystem. + * + * This function will fatal if it has to wait more than MaxTimeToWait + * for the file streamer thread to shut down. + */ + +void FileStreamer::remove(void) +{ + DEBUG_FATAL(!ms_installed, ("FileStreamer::remove not installed")); + ms_installed = false; +} + +// ---------------------------------------------------------------------- +/** + * Check if a file exists. + * + * This routine does not indicate that the file is actually readable by + * the process. + * + * @param fileName File name to check existence of + * @return True if the file exists, otherwise false. + */ + +bool FileStreamer::exists(const char *fileName) +{ + return OsFile::exists(fileName); +} + +// ---------------------------------------------------------------------- +/** + * Check if a file exists. + * + * This routine does not indicate that the file is actually readable by + * the process. + * + * @param fileName File name to check existence of + * @return True if the file exists, otherwise false. + */ + +int FileStreamer::getFileSize(const char *fileName) +{ + return OsFile::getFileSize(fileName); +} + +// ---------------------------------------------------------------------- +/** + * Open a file. + * + * @param fileName [In] File name to open + * @param handle [Out] Handle to the opened file + * @return True if the file was successfully opened, otherwise false. + */ + +FileStreamer::File *FileStreamer::open(const char *fileName, bool randomAccess) +{ + DEBUG_FATAL(!ms_installed, ("not installed")); + DEBUG_FATAL(!fileName, ("file name null")); + + OsFile *osFile = OsFile::open(fileName, randomAccess); + if (!osFile) + return NULL; + +#if PRODUCTION + // Stop checking the fileName if one of these returns true + reportModDirectory(fileName, "ui/", "UI") || + reportModDirectory(fileName, "texture/", "Texture") || + reportModDirectory(fileName, "effect/", "Effect") || + reportModDirectory(fileName, "appearance/", "Appearance") || + reportModDirectory(fileName, "music/", "Music") || + reportModDirectory(fileName, "sound/", "Sound") || + reportModDirectory(fileName, "camera/", "Camera") || + reportModDirectory(fileName, "shader/", "Shader"); +#endif + + // return the opened file + return new FileStreamer::File(osFile); +} + +// ====================================================================== + +MemoryBlockManager *FileStreamer::File::ms_memoryBlockManager; +bool FileStreamer::File::ms_useThread; + +// ====================================================================== + +void FileStreamer::File::install(bool useThread) +{ + ms_memoryBlockManager = new MemoryBlockManager("FileStreamer::File::memoryBlockManager", true, sizeof(FileStreamer::File), 0, 0, 0); + ExitChain::add(&remove, "FileStreamer::File"); + ms_useThread = useThread; +} + +// ---------------------------------------------------------------------- + +void FileStreamer::File::remove() +{ + delete ms_memoryBlockManager; + ms_memoryBlockManager = NULL; +} + +// ---------------------------------------------------------------------- + +void *FileStreamer::File::operator new(size_t size) +{ + UNREF(size); + NOT_NULL (ms_memoryBlockManager); + DEBUG_FATAL(size != sizeof(FileStreamer::File), ("incorrect File size")); + DEBUG_FATAL(size != static_cast (ms_memoryBlockManager->getElementSize()), ("installed with bad size")); + return ms_memoryBlockManager->allocate(); +} + +// ---------------------------------------------------------------------- + +void FileStreamer::File::operator delete(void *pointer) +{ + NOT_NULL (ms_memoryBlockManager); + ms_memoryBlockManager->free(pointer); +} + +// ---------------------------------------------------------------------- + +FileStreamer::File::File(OsFile *osFile) +: + m_osFile(osFile), + m_offset(0) +{ +} + +// ---------------------------------------------------------------------- + +FileStreamer::File::~File() +{ + close(); +} + +// ---------------------------------------------------------------------- + +bool FileStreamer::File::isOpen() const +{ + return m_osFile != NULL; +} + +// ---------------------------------------------------------------------- + +int FileStreamer::File::length() const +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + return m_osFile->length(); +} + +// ---------------------------------------------------------------------- + +int FileStreamer::File::read(int offset, void *destinationBuffer, int numberOfBytes, AbstractFile::PriorityType priority) +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + + if (!ms_useThread) + { + m_osFile->seek(offset); + return m_osFile->read(destinationBuffer, numberOfBytes); + } + + int returnValue = -1; + + Gate *gate = PerThreadData::getFileStreamerReadGate(); + + // build the request + FileStreamerThread::Request *newRequest = new FileStreamerThread::Request; + newRequest->type = FileStreamerThread::Request::Read; + newRequest->osFile = m_osFile; + newRequest->buffer = destinationBuffer; + newRequest->offset = offset; + newRequest->bytesToBeRead = numberOfBytes; + newRequest->bytesRead = 0; + newRequest->gate = gate; + newRequest->priority = priority; + newRequest->returnValue = &returnValue; + + // submit the request + FileStreamerThread::submitRequest(newRequest); + + // wait for the request to complete + gate->wait(); + gate->close(); + + DEBUG_FATAL(returnValue < 0, ("failed to set the return value")); + return returnValue; +} + +// ---------------------------------------------------------------------- + +void FileStreamer::File::close() +{ + if (isOpen()) + { + delete m_osFile; + m_osFile = NULL; + } +} + +// ====================================================================== + +bool FileStreamerNamespace::reportModDirectory(char const * const fileName ,char const * const directory, char const * const description) +{ + if (strstr(fileName, directory)) + { + DEBUG_REPORT_LOG(true, ("Loaded %s off filesystem: %s\n", description, fileName)); + CrashReportInformation::addStaticText("%s file: %s\n", description, fileName); + return true; + } + return false; +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/FileStreamer.h b/engine/shared/library/sharedFile/src/shared/FileStreamer.h new file mode 100644 index 00000000..bfe9a5f4 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileStreamer.h @@ -0,0 +1,105 @@ +// ====================================================================== +// +// FileStreamer.h +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2002 Sony Online Entertainment +// All Rights Reserveed +// +// ====================================================================== + +#ifndef INCLUDED_FileStreamer_H +#define INCLUDED_FileStreamer_H + +// ====================================================================== + +class FileStreamerThread; +class MemoryBlockManager; +class OsFile; + +#include "fileInterface/AbstractFile.h" + +// ====================================================================== +/** + * Low-level disk access routines. + * + * This class spawns a thread to take care of all disk access. + * + * This class basically wraps the FileStreamerThread class, which no other + * class should access. + * + * Game programmers likely want to use the TreeFile class to access disk + * files instead of this class. + */ + +class FileStreamer +{ + friend class FileStreamerThread; + +public: + + class File; + +public: + + static void install(bool useThread); + static void remove(); + + static bool exists(const char *fileName); + static int getFileSize(const char *fileName); + static File *open(const char *fileName, bool randomAccess=false); + +private: + + static bool ms_installed; + static bool ms_useThread; +}; + +// ====================================================================== + +class FileStreamer::File +{ + friend class FileStreamerThread; + +public: + + static void install(bool useThread); + static void *operator new(size_t size); + static void operator delete(void *memory); + +public: + + File(OsFile *osFile); + ~File(); + + bool isOpen() const; + int length() const; + int read(int offset, void *destinationBuffer, int numberOfBytes, AbstractFile::PriorityType priority); + void close(); + +private: + + static void remove(); + +private: + + File(); + File(const File &); + File &operator =(const File &); + +private: + + static MemoryBlockManager *ms_memoryBlockManager; + +private: + + static bool ms_useThread; + +private: + + OsFile *m_osFile; + int m_offset; +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/FileStreamerFile.cpp b/engine/shared/library/sharedFile/src/shared/FileStreamerFile.cpp new file mode 100644 index 00000000..d6a798d5 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileStreamerFile.cpp @@ -0,0 +1,180 @@ +// ====================================================================== +// +// FileStreamerFile.cpp +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/FileStreamerFile.h" + +#include "sharedFile/OsFile.h" +#include "sharedFile/FileStreamer.h" +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/MemoryBlockManager.h" + +// ====================================================================== + +MemoryBlockManager *FileStreamerFile::ms_memoryBlockManager; + +// ====================================================================== + +void FileStreamerFile::install() +{ + DEBUG_FATAL(ms_memoryBlockManager, ("FileStreamerFile::install already installed")); + ms_memoryBlockManager = new MemoryBlockManager("FileStreamerFile::memoryBlockManager", true, sizeof(FileStreamerFile), 0, 0, 0); + ExitChain::add(&FileStreamerFile::remove, "FileStreamerFile::remove"); +} + +// ---------------------------------------------------------------------- + +void FileStreamerFile::remove() +{ + DEBUG_FATAL(!ms_memoryBlockManager,("FileStreamerFile is not installed")); + + delete ms_memoryBlockManager; + ms_memoryBlockManager = 0; +} + +// ---------------------------------------------------------------------- + +void *FileStreamerFile::operator new(size_t size) +{ + DEBUG_FATAL(!ms_memoryBlockManager,("FileStreamerFile is not installed")); + + // do not try to alloc a descendant class with this allocator + DEBUG_FATAL(size != sizeof(FileStreamerFile),("bad size")); + UNREF(size); + + return ms_memoryBlockManager->allocate(); +} + +// ---------------------------------------------------------------------- + +void FileStreamerFile::operator delete(void *pointer) +{ + DEBUG_FATAL(!ms_memoryBlockManager,("Projectile is not installed")); + ms_memoryBlockManager->free(pointer); +} + +// ====================================================================== + +FileStreamerFile::FileStreamerFile(PriorityType priority, FileStreamer::File &file) +: AbstractFile(priority), + m_file(&file), + m_owner(true), + m_baseOffset(0), + m_length(m_file->length()), + m_offset(0) +{ +} + +// ---------------------------------------------------------------------- + +FileStreamerFile::FileStreamerFile(PriorityType priority, FileStreamer::File &file, int baseOffset, int length) +: AbstractFile(priority), + m_file(&file), + m_owner(false), + m_baseOffset(baseOffset), + m_length(length), + m_offset(0) +{ +} + +// ---------------------------------------------------------------------- + +FileStreamerFile::~FileStreamerFile() +{ + close(); +} + +// ---------------------------------------------------------------------- + +bool FileStreamerFile::isOpen() const +{ + return m_file != NULL; +} + +// ---------------------------------------------------------------------- + +int FileStreamerFile::length() const +{ + DEBUG_FATAL(!isOpen(), ("file not open")); + return m_length; +} + +// ---------------------------------------------------------------------- + +int FileStreamerFile::tell() const +{ + DEBUG_FATAL(!isOpen(), ("file not open")); + return m_offset; +} + +// ---------------------------------------------------------------------- + +bool FileStreamerFile::seek(SeekType seekType, int offset) +{ + DEBUG_FATAL(!isOpen(), ("file not open")); + + switch (seekType) + { + case SeekBegin: + m_offset = offset; + break; + + case SeekCurrent: + m_offset += offset; + break; + + case SeekEnd: + m_offset = m_length + offset; + break; + } + + m_offset = clamp(0, m_offset, m_length); + + return true; +} + +// ---------------------------------------------------------------------- + +int FileStreamerFile::read(void *destinationBuffer, int numberOfBytes) +{ + DEBUG_FATAL(!isOpen(), ("file not open")); + + // make sure they don't read more file + if (m_offset + numberOfBytes > m_length) + numberOfBytes = m_length - m_offset; + + const int bytesRead = m_file->read(m_baseOffset + m_offset, destinationBuffer, numberOfBytes, m_priority); + m_offset += bytesRead; + return bytesRead; +} + +// ---------------------------------------------------------------------- + +int FileStreamerFile::write(int, const void *) +{ + DEBUG_FATAL(true, ("cannot call write on tree files")); + return 0; +} + +// ---------------------------------------------------------------------- + +void FileStreamerFile::flush() +{ + DEBUG_FATAL(true, ("cannot call flush on tree files")); +} + +// ---------------------------------------------------------------------- + +void FileStreamerFile::close() +{ + if (m_owner) + delete m_file; + m_file = NULL; +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/FileStreamerFile.h b/engine/shared/library/sharedFile/src/shared/FileStreamerFile.h new file mode 100644 index 00000000..4f01af96 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileStreamerFile.h @@ -0,0 +1,73 @@ +// ====================================================================== +// +// FileStreamerFile.h +// Copyright 2002 Sony Online Entertainment +// All rights reserved. +// +// ====================================================================== + +#ifndef INCLUDED_FileStreamerFile_H +#define INCLUDED_FileStreamerFile_H + +// ====================================================================== + +class MemoryBlockManager; +class OsFile; + +#include "fileInterface/AbstractFile.h" +#include "sharedFile/FileStreamer.h" + +// ====================================================================== + +class FileStreamerFile : public AbstractFile +{ +public: + + static void install(); + static void *operator new(size_t size); + static void operator delete(void *memory); + +public: + + FileStreamerFile(PriorityType priority, FileStreamer::File &file); + FileStreamerFile(PriorityType priority, FileStreamer::File &file, int baseOffset, int length); + virtual ~FileStreamerFile(); + + virtual bool isOpen() const; + virtual int length() const; + virtual int tell() const; + virtual bool seek(SeekType seekType, int offset); + virtual int read(void *destinationBuffer, int numberOfBytes); + + virtual int write(int numberOfBytes, const void *sourceBuffer); + virtual void flush(); + + virtual void close(); + +private: + + static MemoryBlockManager *ms_memoryBlockManager; + +private: + + static void remove(); + +private: + + FileStreamerFile(); + FileStreamerFile(const FileStreamerFile &); + FileStreamerFile &operator =(const FileStreamerFile &); + +private: + + FileStreamer::File *m_file; + const bool m_owner; + const int m_baseOffset; + const int m_length; + int m_offset; +}; + +// ====================================================================== + +#endif + diff --git a/engine/shared/library/sharedFile/src/shared/FileStreamerThread.cpp b/engine/shared/library/sharedFile/src/shared/FileStreamerThread.cpp new file mode 100644 index 00000000..1d3c0b8c --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileStreamerThread.cpp @@ -0,0 +1,422 @@ +// ====================================================================== +// +// FileStreamerThread.cpp +// Portions copyright 1999 Bootprint Entertainment +// Portions copyright 2002 Bootprint Entertainment +// All Rights Reserved +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/FileStreamerThread.h" + +#include "fileInterface/AbstractFile.h" +#include "sharedFile/FileStreamer.h" +#include "sharedFile/FileStreamerFile.h" +#include "sharedFile/OsFile.h" +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/PerThreadData.h" +#include "sharedFoundation/MemoryBlockManager.h" +#include "sharedFoundation/MemoryBlockManager.h" +#include "sharedSynchronization/Gate.h" +#include "sharedSynchronization/Semaphore.h" +#include "sharedSynchronization/Mutex.h" +#include "sharedThread/RunThread.h" +#include "sharedThread/ThreadHandle.h" + +#include + +// ====================================================================== + +bool FileStreamerThread::ms_installed; +ThreadHandle FileStreamerThread::ms_threadHandle; +Semaphore FileStreamerThread::ms_eventsPending; +Mutex FileStreamerThread::ms_queueCriticalSection; +volatile FileStreamerThread::Request *FileStreamerThread::ms_firstAudioVideoRequest; +volatile FileStreamerThread::Request *FileStreamerThread::ms_lastAudioVideoRequest; +volatile FileStreamerThread::Request *FileStreamerThread::ms_firstDataRequest; +volatile FileStreamerThread::Request *FileStreamerThread::ms_lastDataRequest; +volatile FileStreamerThread::Request *FileStreamerThread::ms_firstLowRequest; +volatile FileStreamerThread::Request *FileStreamerThread::ms_lastLowRequest; + +namespace FileStreamerThreadNamespace +{ + int const cms_maxReadSize = 128 * 1024; +}; +using namespace FileStreamerThreadNamespace; + +// ====================================================================== + +void FileStreamerThread::install() +{ + DEBUG_FATAL(ms_installed, ("FileStreamerThread::install already installed")); + ms_installed = true; + + Request::install(); + + // create the thread to handle the file access, will be triggered into action through the eventsPending semaphore + ms_threadHandle = runNamedThread("File", threadRoutine); + ms_threadHandle->setPriority(Thread::kHigh); + + ExitChain::add(&remove, "FileStreamerThread::remove"); +} + +// ---------------------------------------------------------------------- + +void FileStreamerThread::remove() +{ + DEBUG_FATAL(!ms_installed, ("FileStreamerThread::remove not installed")); + + //sumbit a quit request + Request *newRequest = new Request; + newRequest->type = Request::Quit; + newRequest->priority = AbstractFile::PriorityData; + newRequest->gate = PerThreadData::getFileStreamerReadGate(); + submitRequest(newRequest); + + ms_threadHandle->wait(); + ms_installed = false; +} + +// ---------------------------------------------------------------------- +/** + * Put a ruquest into the request queue(s). + * + * This function puts a new request at the back of the appropriate queue. + * All access to the queue is protected by critical sections. + */ + +void FileStreamerThread::submitRequest(Request *request) +{ + NOT_NULL(request); + + if (request->priority == AbstractFile::PriorityAudioVideo) + { + ms_queueCriticalSection.enter(); + // add it to the linked list of requests + if (ms_lastAudioVideoRequest) + ms_lastAudioVideoRequest->next = request; + else + ms_firstAudioVideoRequest = request; + ms_lastAudioVideoRequest = request; + ms_queueCriticalSection.leave(); + } + else + if (request->priority == AbstractFile::PriorityData) + { + ms_queueCriticalSection.enter(); + // add it to the linked list of requests + if (ms_lastDataRequest) + ms_lastDataRequest->next = request; + else + ms_firstDataRequest = request; + ms_lastDataRequest = request; + ms_queueCriticalSection.leave(); + } + else + if (request->priority == AbstractFile::PriorityLow) + { + ms_queueCriticalSection.enter(); + // add it to the linked list of requests + if (ms_lastLowRequest) + ms_lastLowRequest->next = request; + else + ms_firstLowRequest = request; + ms_lastLowRequest = request; + ms_queueCriticalSection.leave(); + } + else + { + DEBUG_FATAL(true, ("request has unknown priority type")); + } + + // signal the thread that a new event is waiting + ms_eventsPending.signal(); +} + +// ---------------------------------------------------------------------- +/** + * Quit. + * + * This request is queued and processed after the reads are completed + */ + +void FileStreamerThread::processQuit(volatile Request *request) +{ + NOT_NULL(request); + + ExitChain::quit(); + Gate *gate = request->gate; + delete request; + gate->open(); +} + +// ---------------------------------------------------------------------- +/** + * Function to read data into a buffer. + * + * This function breaks up reads over cms_maxReadSize into multiple reads by reading + * cms_maxReadSize bytes, then updating the request data and putting the request back + * on the head of the queue and resignaling the semaphore. When the read is complete + * it stores the number of bytes read into the game-held request->returnVal field and + * triggers and event to tell the game that we're finished. + */ + +void FileStreamerThread::processRead(volatile Request *request) +{ + NOT_NULL(request); + + // shortcut to the file + OsFile *osFile = request->osFile; + + // seek to the requested offset + osFile->seek(request->offset); + + // read the data + if (request->bytesToBeRead > cms_maxReadSize) + { + // only read up to FileStreamerThread::cms_maxReadSize, then resubmit smaller request + const int amountRead = osFile->read(request->buffer, cms_maxReadSize); + + request->offset += amountRead; + request->bytesRead += amountRead; + request->bytesToBeRead -= amountRead; + request->buffer = reinterpret_cast(request->buffer) + amountRead; + + // if we read less than we could have, we're done + if (amountRead < cms_maxReadSize) + { + // store final number of bytes read in storage accessible to main thread + *request->returnValue = static_cast(request->bytesRead); + + Gate *gate = request->gate; + delete request; + + //set event so other main thread continues + gate->open(); + } + else + { + // resubmit request (put it on the head so we get it back first) + if (request->priority == AbstractFile::PriorityAudioVideo) + { + ms_queueCriticalSection.enter(); + if (ms_firstAudioVideoRequest) + { + request->next = ms_firstAudioVideoRequest; + ms_firstAudioVideoRequest = request; + } + else + { + ms_firstAudioVideoRequest = request; + ms_lastAudioVideoRequest = request; + } + ms_queueCriticalSection.leave(); + } + else + if (request->priority == AbstractFile::PriorityData) + { + ms_queueCriticalSection.enter(); + if (ms_firstDataRequest) + { + request->next = ms_firstDataRequest; + ms_firstDataRequest = request; + } + else + { + ms_firstDataRequest = request; + ms_lastDataRequest = request; + } + ms_queueCriticalSection.leave(); + } + else + if (request->priority == AbstractFile::PriorityLow) + { + ms_queueCriticalSection.enter(); + if (ms_firstLowRequest) + { + request->next = ms_firstLowRequest; + ms_firstLowRequest = request; + } + else + { + ms_firstLowRequest = request; + ms_lastLowRequest = request; + } + ms_queueCriticalSection.leave(); + } + else + DEBUG_FATAL(true, ("FileStreamerThread::processRead request has unknown priority type")); + + // signal the file thread that a new request is waiting + ms_eventsPending.signal(); + } + } + else + { + // fulfill entire read request + const int amountRead = osFile->read(request->buffer, request->bytesToBeRead); + + request->bytesRead += amountRead; + request->bytesToBeRead -= amountRead; + + // store final number of bytes read in storage accessible to main thread + *request->returnValue = static_cast(request->bytesRead); + + Gate *gate = request->gate; + delete request; + gate->open(); + } +} + +// ---------------------------------------------------------------------- +/** + * Routine where the file thread runs. + * + * The file thread waits at the WaitForSingleObject until the game thread + * triggers it by calling a submit-request function that involves the queues. + * It then services the first AudioVideo request, and if there are no AudioVideo requests to + * be serviced, a Data request instead. All access to the queue is protected by + * critical sections. + */ + +void FileStreamerThread::threadRoutine() +{ + bool quit = false; + + //loop until a quit request is processed + while (!quit) + { + volatile Request* request; + + //wait until there is data to processs + ms_eventsPending.wait(); + + //get the request to service + ms_queueCriticalSection.enter(); + request = NULL; + + //if there is an AudioVideo request, service it first (interrupting any current data request) + if (ms_firstAudioVideoRequest) + { + request = ms_firstAudioVideoRequest; + ms_firstAudioVideoRequest = ms_firstAudioVideoRequest->next; + if (ms_firstAudioVideoRequest == NULL) + ms_lastAudioVideoRequest = NULL; + request->next = NULL; + } + else + if (ms_firstDataRequest) + { + request = ms_firstDataRequest; + ms_firstDataRequest = ms_firstDataRequest->next; + if (ms_firstDataRequest == NULL) + ms_lastDataRequest = NULL; + request->next = NULL; + } + else + if (ms_firstLowRequest) + { + request = ms_firstLowRequest; + ms_firstLowRequest = ms_firstLowRequest->next; + if (ms_firstLowRequest == NULL) + ms_lastLowRequest = NULL; + request->next = NULL; + } + else + { + DEBUG_FATAL(true, ("no request waiting")); + } + + ms_queueCriticalSection.leave(); + + if (request) + { + switch (request->type) + { + case Request::Quit: + processQuit(request); + quit = true; + break; + + case Request::Read: + processRead(request); + break; + + case Request::Unknown: + default: + DEBUG_FATAL(true, ("FileStreamerThread::threadRoutine unknown request %d", static_cast(request->type))); + break; + } + } + } +} + +// ====================================================================== + +MemoryBlockManager *FileStreamerThread::Request::ms_memoryBlockManager; + +// ====================================================================== + +void FileStreamerThread::Request::install() +{ + ms_memoryBlockManager = new MemoryBlockManager("FileStreamerThread::Request::memoryBlockManager", true, sizeof(Request), 0, 0, 0); + ExitChain::add(&remove, "FileStreamerThread::Request"); +} + +// ---------------------------------------------------------------------- + +void FileStreamerThread::Request::remove() +{ + delete ms_memoryBlockManager; + ms_memoryBlockManager = NULL; +} + +// ---------------------------------------------------------------------- + +void *FileStreamerThread::Request::operator new(size_t size) +{ + UNREF(size); + NOT_NULL(ms_memoryBlockManager); + DEBUG_FATAL(size != sizeof(Request), ("incorrect request size")); + DEBUG_FATAL(size != static_cast(ms_memoryBlockManager->getElementSize()), ("installed with bad size")); + + return ms_memoryBlockManager->allocate(); +} + +// ---------------------------------------------------------------------- + +void FileStreamerThread::Request::operator delete(void *pointer) +{ + NOT_NULL(ms_memoryBlockManager); + ms_memoryBlockManager->free(pointer); +} + +// ---------------------------------------------------------------------- + +FileStreamerThread::Request::Request() +: next(NULL), + type(Request::Unknown), + osFile(NULL), + buffer(0), + bytesToBeRead(0), + bytesRead(0), + gate(NULL), + priority(AbstractFile::PriorityData), + returnValue(NULL) +{ +} + +// ---------------------------------------------------------------------- + +FileStreamerThread::Request::~Request() +{ +#ifdef _DEBUG + next = NULL; + buffer = NULL; + gate = NULL; + returnValue = NULL; +#endif +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/FileStreamerThread.h b/engine/shared/library/sharedFile/src/shared/FileStreamerThread.h new file mode 100644 index 00000000..1d9787b7 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FileStreamerThread.h @@ -0,0 +1,136 @@ +// ====================================================================== +// +// FileStreamerThread.h +// Portions copyright 1999 Bootprint Entertainment +// Portions copyright 2002 Sony Online Entertainment +// All Rights Reserved +// +// ====================================================================== + +#ifndef INCLUDED_FileStreamerThread_H +#define INCLUDED_FileStreamerThread_H + +// ====================================================================== + +#include "fileInterface/AbstractFile.h" +#include "sharedFile/FileStreamer.h" + +class FileStreamerFile; +class Gate; +class MemoryBlockManager; +class Mutex; +class OsFile; +class Semaphore; +class MemoryBlockManager; +class Thread; +template class TypedThreadHandle; +typedef TypedThreadHandle ThreadHandle; + +// ====================================================================== + +// Encapsulates thread for file access +// +// This class represents the file streaming thread. It should only be accessed +// by the FileStreamer class. +// +// All requests, with the exceptions of reads and quits, and responded to immediately. +// The "submit-request" paradigm is used to consistancy. Read and quit requests are +// put in multiple queues and serviced by priority (audio-visual before plain data before low, etc.) +// +// AV request will "interrupt" any current data request, and all reads are only serviced +// 16K at a time. + +class FileStreamerThread +{ + friend class FileStreamer::File; + +public: + + class Request; + +public: + + static void install(); + +private: + + static bool ms_installed; + static ThreadHandle ms_threadHandle; + static Semaphore ms_eventsPending; + static Mutex ms_queueCriticalSection; + static volatile Request *ms_firstAudioVideoRequest; + static volatile Request *ms_lastAudioVideoRequest; + static volatile Request *ms_firstDataRequest; + static volatile Request *ms_lastDataRequest; + static volatile Request *ms_firstLowRequest; + static volatile Request *ms_lastLowRequest; + +private: + + static void remove(void); + + static void verifyOpen(const char *function, int handle); + static void threadRoutine(); + static void submitRequest(Request *request); + + static void processRead(volatile Request *request); + static void processQuit(volatile Request *request); +}; + +// ====================================================================== + +class FileStreamerThread::Request +{ + friend class FileStreamerThread; + friend class FileStreamerFile; + +public: + + static void install(); + static void *operator new(size_t size); + static void operator delete(void *pointer); + +public: + + //initialized to unknown. Must be changed before usage or it will debug_fatal + enum Type + { + Unknown, + Quit, + Read + }; + + volatile Request *next; + Type type; + OsFile *osFile; + void *buffer; + int offset; + int bytesToBeRead; + int bytesRead; + + // event to trigger when finished servicing request + Gate *gate; + + // used to place in correct queue for priority service + AbstractFile::PriorityType priority; + + // storage held by game thread used to pass back return value + int *returnValue; + +public: + + Request(); + ~Request(); + +private: + + static MemoryBlockManager *ms_memoryBlockManager; + +private: + + static void remove(); +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/FirstSharedFile.h b/engine/shared/library/sharedFile/src/shared/FirstSharedFile.h new file mode 100644 index 00000000..7add7f5a --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/FirstSharedFile.h @@ -0,0 +1,20 @@ +// ====================================================================== +// +// FirstFile.h +// copyright (c) 2001 Sony Online Entertainment +// +// ====================================================================== + +#ifndef INCLUDED_FirstFile_H +#define INCLUDED_FirstFile_H + +// ====================================================================== + +#include "sharedFoundation/FirstSharedFoundation.h" +#include "sharedDebug/FirstSharedDebug.h" +#include "sharedMemoryManager/FirstSharedMemoryManager.h" + +// ====================================================================== + +#endif + diff --git a/engine/shared/library/sharedFile/src/shared/Iff.cpp b/engine/shared/library/sharedFile/src/shared/Iff.cpp new file mode 100644 index 00000000..0f1c82e6 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/Iff.cpp @@ -0,0 +1,1753 @@ +// ====================================================================== +// +// Iff.cpp +// jeff grills +// +// copyright 1998 Bootprint Entertainment +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/Iff.h" + +#include "sharedFile/ConfigSharedFile.h" +#include "sharedFile/TreeFile.h" +#include "sharedFoundation/ByteOrder.h" +#include "sharedFoundation/ConfigFile.h" +#include "sharedFoundation/Crc.h" +#include "sharedFoundation/Os.h" +#include "sharedMath/Quaternion.h" +#include "sharedMath/Transform.h" +#include "sharedMath/Vector.h" +#include "sharedMath/VectorArgb.h" + +#include + +// ====================================================================== + +namespace IffNamespace +{ + bool consumeUint32(byte const * & memory, int & length, uint32 & value); + bool isValid(byte const *memory, int length); +} + +using namespace IffNamespace; + + +#define IFF_FATAL(a, b) ((a) ? fatal(b) : NOP) + +#ifdef _DEBUG + #define IFF_DEBUG_FATAL(a, b) IFF_FATAL(a, b) +#else + #define IFF_DEBUG_FATAL(a, b) NOP +#endif + +// ====================================================================== + +void Iff::install() +{ + +} + +// ---------------------------------------------------------------------- +/** + * calculate the number of bytes contained in the raw iff data buffer. + * + * @return The number of bytes of raw iff data stored in the buffer returned + * by Iff::getRawData(). + * + * Requires that trailing non-iff data in the buffer is set to zero. + * @see Iff:getRawData() + */ + +int Iff::calculateRawDataSize(void) const +{ + // calculate the number of data bytes actually used given the max data buffer length and the contents of the data. + if (!data) + return 0; + + // -TF- this assumes any extra non-iff data within the valid iff data buffer has been zeroed out + int offset = 0; + int blockLength; + uint32 tempLength; + + do + { + // get block length (including tag and length field) + memcpy(&tempLength, data + offset + sizeof(Tag), sizeof(uint32)); + blockLength = static_cast(ntohl(tempLength)) + isizeof(Tag) + isizeof(uint32); + offset += blockLength; + } + while ((offset < length) && blockLength); + + return offset; +} + +// ====================================================================== + +bool IffNamespace::consumeUint32(byte const * & memory, int & length, uint32 & value) +{ + if (length < 4) + return false; + + memcpy(&value, memory, sizeof(value)); + memory += sizeof(value); + length -= sizeof(value); + value = ntohl(value); + return true; +} + +// ---------------------------------------------------------------------- + +bool IffNamespace::isValid(byte const *memory, int length) +{ + if (length <= 0) + return false; + + while (length > 0) + { + // read the block's tag (FORM or the the chunk name) + Tag blockTag(0); + if (!consumeUint32(memory, length, blockTag)) + return false; + + // read the block length + uint32 blockLength(0); + if (!consumeUint32(memory, length, blockLength)) + return false; + + // verify the file length can contain the block's data + if ((static_cast(blockLength) < 0) || (static_cast(blockLength) > length)) + return false; + + // verify sub-forms + int subLength = blockLength-sizeof(Tag); + if (blockTag == TAG_FORM && (subLength != 0 && !isValid(memory+sizeof(Tag), subLength ))) + return false; + + // skip the block data + memory += blockLength; + length -= blockLength; + } + + return true; +} + +// ---------------------------------------------------------------------- + +bool Iff::isValid(char const * fileName) +{ + AbstractFile * file = TreeFile::open(fileName, AbstractFile::PriorityData, true); + if (!file) + return false; + + // read in the file + int const fileLength = file->length(); + byte *data = file->readEntireFileAndClose(); + delete file; + file = NULL; + + bool const result = IffNamespace::isValid(data, fileLength); + delete [] data; + data = NULL; + + return result; +} + +// ====================================================================== +// Construct an empty Iff +// +// Remarks: +// +// The Iff can have a file loaded into it by calling open(). +// +// See Also: +// +// Iff::open() + +Iff::Iff(void) +: fileName(NULL), + maxStackDepth(DEFAULT_STACK_DEPTH), + stackDepth(0), + stack(new Stack[DEFAULT_STACK_DEPTH]), + length(0), + data(NULL), + inChunk(false), + growable(false), + nonlinear(false), + ownsData(true) +{ + // clear out the stack data + memset(stack, 0, isizeof(*stack) * maxStackDepth); +} + +// ---------------------------------------------------------------------- +/** + * Construct an Iff from a data buffer. + * + * If iffOwnsData is true, this Iff object will take ownership of the + * buffer (i.e. it will delete the buffer when the iff is destroyed). + * + * Behavior of the Iff object is undefined if the given data buffer + * does not contain valid Iff-format data. + * + * The Iff object created is not growable. + * + * Do not change the contents of the newData buffer while this Iff object + * still exists. The Iff object does not copy the newData buffer. + * + * Any portion of the newData buffer that doesn't contain Iff data + * should be zero-filled. If it is not, we will not be able to + * correctly determine the real size of the valid iff data being passed in. + * + * @param newDataSize [IN] length, in bytes, of the Iff data buffer pointed to by newData + * @param newData [IN] the buffer containing Iff data to be interpreted by this Iff object + * @param iffOwnsData [IN] if true, the Iff takes ownership of the buffer and deletes it during the destructor call; if false, the Iff does not attempt to delete the buffer during the destructor call + */ + +Iff::Iff(int newDataSize, const byte *newData, bool iffOwnsData) : + fileName(NULL), + maxStackDepth(DEFAULT_STACK_DEPTH), + stackDepth(0), + stack(new Stack[DEFAULT_STACK_DEPTH]), + length(newDataSize), + data(const_cast(newData)), + inChunk(false), + growable(false), + nonlinear(false), + ownsData(iffOwnsData) +{ + // clear out the stack data + memset(stack, 0, isizeof(*stack) * maxStackDepth); + + // setup the stack data to know about the data + stack[0].start = 0; + stack[0].used = 0; + stack[0].length = calculateRawDataSize(); +} + +// ---------------------------------------------------------------------- +/** + * Construct an Iff from a specified file. + * + * This routine will effectively create an empty Iff and then call open() + * on that Iff. Because a constructor has no return value, there is no + * way for this routine to return a success indicator when the optional + * flag was specified. + * + * @param newFileName The name of the file to open + * @param optional Optional flag to pass to open() + * @see Iff::open() + */ + +Iff::Iff(const char *newFileName, bool optional) +: fileName(NULL), + maxStackDepth(DEFAULT_STACK_DEPTH), + stackDepth(0), + stack(new Stack[DEFAULT_STACK_DEPTH]), + length(0), + data(NULL), + inChunk(false), + growable(false), + nonlinear(false), + ownsData(true) +{ + // clear out the stack data + memset(stack, 0, isizeof(*stack) * maxStackDepth); + + // open the data file + static_cast(open(newFileName, optional)); +} + +// ---------------------------------------------------------------------- +/** + * Construct an Iff for writing new data. + * + * @param initialSize [IN] Initial size of the Iff data + * @param isGrowable [IN] Flag to indicate if the Iff data may automatically expand as needed, + * @param clearDataBuffer [IN] if true, the iff data buffer is zeroed during construction. if false, the data buffer is uninitialized + */ + +Iff::Iff(int initialSize, bool isGrowable, bool clearDataBuffer) +: fileName(NULL), + maxStackDepth(DEFAULT_STACK_DEPTH), + stackDepth(0), + stack(new Stack[DEFAULT_STACK_DEPTH]), + length(initialSize), + data(new byte[static_cast(length)]), + inChunk(false), + growable(isGrowable), + nonlinear(false), + ownsData(true) +{ + // clear out the stack data + memset(stack, 0, isizeof(Stack) * maxStackDepth); + if (clearDataBuffer) + memset(data, 0, static_cast(length)); +} + +// ---------------------------------------------------------------------- +/** + * Destroy an Iff. + * + * This routine releases all resources allocated by an instance of the Iff. + */ + +Iff::~Iff(void) +{ + close(); + delete [] stack; +} + +// ---------------------------------------------------------------------- +/** + * Calculate the CRC of the all the data in the Iff. + * + * @return The CRC of the contents of the iff. + */ + +uint32 Iff::calculateCrc() const +{ + return Crc::calculate(data, stack[0].length); +} + +// ---------------------------------------------------------------------- +/** + * Read an Iff in from the specified file. + * + * This routine will load all the data from the Iff into memory. + * + * If the specified file does not exist, the behavior of this routine is + * determined by the value of the optional flag. If the flag + * is true, the routine will return false. If the flag is false, + * the routine will call Fatal in debug compiles, but will return false in + * release compiles. + * + * Calling open() on an Iff instance that already contains data will cause the + * old data to be discarded and then will load the new data. + * + * @param newFileName Name of the file to load + * @param optional Whether to allow clean failure + * @return True if the Iff was successfully opened, false otherwise. + * @see Iff::close() + */ + +bool Iff::open(const char *newFileName, bool optional) +{ + AbstractFile * const file = TreeFile::open(newFileName, AbstractFile::PriorityData, optional); + if(!file) + { + DEBUG_FATAL(!optional, ("could not open file '%s'", newFileName)); + return false; + } + + open(*file, newFileName); + delete file; + + // copy the file name + fileName = DuplicateString(newFileName); + + return true; +} + +// ---------------------------------------------------------------------- +/** + * Read an Iff in from the specified file. + * + * This routine will load all the data from the Iff into memory. + * + * + * Calling open() on an Iff instance that already contains data will cause the + * old data to be discarded and then will load the new data. + * + * @param file the file to load + * @return True if the Iff was successfully opened, false otherwise. + * @see Iff::close() + */ + +void Iff::open(AbstractFile & file) +{ + open(file, 0); +} + +// ---------------------------------------------------------------------- + +void Iff::open(AbstractFile & file, char const * const newFileName) +{ + close(); + + // get the data file length + length = file.length(); + + // allocate storage for the data + DEBUG_FATAL(data, ("causing memory leak")); + data = file.readEntireFileAndClose(); + + FATAL(ConfigSharedFile::getValidateIff() && !IffNamespace::isValid(data, length), ("File corruption detected! Iff::isValid failed for %s (size=%d, crc=%08X). Please try a \"Full Scan\" from the LaunchPad.", newFileName ? newFileName : "null", length, Crc::calculate(data, length))); + + // setup the stack data to know about the data + stack[0].start = 0; + stack[0].length = length; + stack[0].used = 0; +} + +// ---------------------------------------------------------------------- +/** + * Release the data associated with the current Iff. + * + * This routine will release the file data that is associated with the Iff, + * whether it was loaded from a file or was being generated by code. This Iff + * can then be used to load another file. + * + * @see Iff::open() + */ + +void Iff::close(void) +{ + delete [] fileName; + fileName = NULL; + + if (ownsData) + delete [] data; + data = NULL; //lint !e672 // possible memory leak in assignment to Iff::data // no, we only delete when we own it + stackDepth = 0; +} + + +// ---------------------------------------------------------------------- + +bool Iff::write(const char *writeFileName, bool optional) +{ + // update the file name + delete [] fileName; + fileName = DuplicateString(writeFileName); + + const bool result = Os::writeFile(fileName, data, stack[0].length); + FATAL(!result && !optional, ("file write failed for %s", fileName)); + + return result; +} + +// ---------------------------------------------------------------------- + +void Iff::fatal(const char *string) const +{ + char buffer[1024]; + + NOT_NULL(string); + + formatLocation(buffer, sizeof(buffer)); + FATAL(true, ("%s: %s", buffer, string)); +} + +// ---------------------------------------------------------------------- +/** + * Print the current Iff location into the specified buffer. + * + * The routine will fill the buffer with the file name and the names + * of all enclosing forms or chunks into the buffer. The routine + * is primarily designed to be used by debugging and error message + * routines. + * + * @param buffer Buffer to format the location into + * @param bufferLength Length of the buffer to avoid overwriting memory + */ + +void Iff::formatLocation(char *buffer, int bufferLength) const +{ + int i, stringLength, totalLength; + + NOT_NULL(buffer); + + // calculate the string length + if (fileName) + stringLength = static_cast(strlen(fileName)); + else + stringLength = 0; + + // calculate the total length of the output buffer + totalLength = stringLength + 1 + stackDepth * 5 + 1; + + // see if there is enough room in the output buffer + if (totalLength > bufferLength) + { + memset(buffer, '*', bufferLength-1); + buffer[bufferLength-1] = '\0'; + DEBUG_FATAL(true, ("format buffer not big enough %d/%d", totalLength, bufferLength)); + return; //lint !e527 // Warning -- Unreachable + } + + // copy the file name into the buffer + if (fileName) + { + strcpy(buffer, fileName); + buffer[stringLength] = '/'; + buffer += stringLength + 1; + } + + // put all the block names into the buffer + for (i = 0; i < stackDepth; ++i) + { + // add the next tag + ConvertTagToString(getBlockName(i), buffer); + buffer += 4; + + if (i+1 < stackDepth) + { + *buffer = '/'; + ++buffer; + } + } + + // terminate the buffer string + *buffer = '\0'; +} + +// ---------------------------------------------------------------------- + +Tag Iff::getFirstTag(int depth) const +{ + Tag t; + + NOT_NULL(data); + + if ( stack[depth].length - stack[depth].used < isizeof(Tag) + isizeof(uint32) ) + { + char buf[1024]; + sprintf( buf, "read overflow depth=[%d] length=[%d] used=[%d] want=[%d]", depth, stack[depth].length, stack[depth].used, isizeof(Tag) + isizeof(uint32) ); + IFF_FATAL(true, buf); + } + memcpy(&t, data + stack[depth].start + stack[depth].used, sizeof(t)); + + return ntohl(t); +} + +// ---------------------------------------------------------------------- +/** + * Get the length of data contained in a block. + * + * @param depth [IN] depth of target block containing the block for which the client wants length info + * @param offset [IN] offset of target block within containing block + */ + +int Iff::getLength(int depth, int offset) const +{ + uint32 u; + + NOT_NULL(data); + IFF_DEBUG_FATAL(stack[depth].length - stack[depth].used+offset < isizeof(Tag) + isizeof(uint32), ("read overflow")); + memcpy(&u, data + stack[depth].start + stack[depth].used + offset + sizeof(Tag), sizeof(u)); + + return static_cast(ntohl(u)); +} + +// ---------------------------------------------------------------------- + +Tag Iff::getSecondTag(int depth) const +{ + Tag t; + + NOT_NULL(data); + IFF_DEBUG_FATAL(stack[depth].length - stack[depth].used < isizeof(Tag) + isizeof(uint32) + isizeof(Tag), ("read overflow")); + memcpy(&t, data + stack[depth].start + stack[depth].used + sizeof(Tag) + sizeof(uint32), sizeof(Tag)); + + return ntohl(t); +} + +// ---------------------------------------------------------------------- +/** + * Adjust the data array as necessary. + * + * This routine will check if the data array needs to be expanded to hold + * the specified amount of new data. If it does, and the Iff is not growable, + * it will call Fatal in debug compiles, but in release compiles the behavior + * is undefined. + * + * The data array will be doubled in size if it does need to be grown until it + * will hold the specified amount of data. + * + * This routine will also handle size being negative, in which case it will + * remove the specified number of bytes from the current location in the Iff. + * + * @param size Delta number of bytes + */ + +void Iff::adjustDataAsNeeded(int size) +{ + // calculate the final required size of the data array + const int neededLength = stack[0].length + size; + + NOT_NULL(data); + IFF_DEBUG_FATAL(neededLength < 0, ("data size underflow")); + + // check if we need to expand the data array + if (neededLength > length) + { + int newLength; + + // make sure the iff was growable + DEBUG_FATAL(!growable, ("data size overflow %d/%d", neededLength, length)); + DEBUG_FATAL(length < 0, ("current length negative %d\n", length)); + + // handle when the iff is created with an initial size of 0, this fixes + // an infinite looping problem + if (length <= 0) + { + length = 1; + } + + // double in size until it supports the needed length + for (newLength = length * 2; newLength < neededLength; newLength *= 2) + ; + + // allocate the new memory + DEBUG_FATAL(newLength < 0, ("negative array allocation")); + byte *newData = new byte[static_cast(newLength)]; + NOT_NULL(newData); + + // copy the old data over to the new data + memcpy(newData, data, stack[0].length); + + // replace the old data with the new data + delete [] data; + data = newData; + length = newLength; + } + + // move data around to either make room or remove data + const int offset = stack[stackDepth].start + stack[stackDepth].used; + const int lengthToEnd = stack[0].length - offset; + if (size > 0) + memmove(data+offset+size, data+offset, lengthToEnd); + else + memmove(data+offset, data+offset-size, lengthToEnd+size); + + // make sure all the enclosing stack entries know about the changed size + for (int i = 0; i <= stackDepth; ++i) + { + // update the stack's idea of the block length + stack[i].length += size; + + // the length of level 0 is the file size, so we should not write it + if (i) + { + // update the data's idea of the block length + if (i == stackDepth && inChunk) + { + const int ui32 = static_cast(htonl(static_cast(stack[i].length))); + memcpy(data+stack[i].start-sizeof(uint32), &ui32, sizeof(uint32)); + } + else + { + // account for forms start beyond the first 4 data bytes, which is their real form name + const int ui32 = static_cast(htonl(static_cast(stack[i].length) + sizeof(Tag))); + memcpy(data+stack[i].start-sizeof(Tag)-sizeof(uint32), &ui32, sizeof(uint32)); + } + } + } +} + +// ---------------------------------------------------------------------- +/** + * Insert one Iff into another. + * + * The Iff read/write pointer will be placed after the inserted data. + * + * If the Iff is already inside a chunk, this routine will call Fatal for + * debug compiles, but will have undefined behavior in release compiles. + * + * @param iff The Iff to insert in this iff + */ + +void Iff::insertIff(const Iff *iff) +{ + NOT_NULL(iff); + NOT_NULL(data); + IFF_DEBUG_FATAL(inChunk, "inside chunk"); + + // make sure the data array can handle this addition + adjustDataAsNeeded(iff->stack[0].length); + + // compute the offset to start inserting data at + const int offset = stack[stackDepth].start + stack[stackDepth].used; + + // add the other iff + memcpy(data+offset, iff->data, iff->stack[0].length); + + // advance past the data + stack[stackDepth].used += iff->stack[0].length; +} + +// ---------------------------------------------------------------------- +/** + * Insert a new form into the Iff at the current location. + * + * This routine will handle adding a form into the middle of an existing + * Iff instance. + * + * If the Iff is already inside a chunk, this routine will call Fatal for + * debug compiles, but will have undefined behavior in release compiles. + * + * @param name Name for the new form + * @param shouldEnterForm True to automatically enter the form + */ + +void Iff::insertForm(Tag name, bool shouldEnterForm) +{ + const int FORM_OVERHEAD = sizeof(Tag) + sizeof(uint32) + sizeof(Tag); + + Tag t; + uint32 ui32; + int offset; + + NOT_NULL(data); + IFF_DEBUG_FATAL(inChunk, "inside chunk"); + + // make sure the data array can handle this addition + adjustDataAsNeeded(FORM_OVERHEAD); + + // compute the offset to start inserting data at + offset = stack[stackDepth].start + stack[stackDepth].used; + + // add the form header + t = htonl(TAG_FORM); + memcpy(data+offset, &t, sizeof(Tag)); + offset += isizeof(Tag); + + // add the size of the form + ui32 = htonl(sizeof(uint32)); + memcpy(data+offset, &ui32, sizeof(uint32)); + offset += isizeof(uint32); + + // add the real form name + t = htonl(name); + memcpy(data+offset, &t, sizeof(Tag)); + + // enter the form if requested + if (shouldEnterForm) + enterForm(); +} + +// ---------------------------------------------------------------------- +/** + * Insert a new chunk into the Iff at the current location. + * + * This routine will handle adding a chunk into the middle of an existing + * Iff instance. + * + * If the Iff is already inside a chunk, this routine will call Fatal for + * debug compiles, but will have undefined behavior in release compiles. + * + * @param name Name for the new form + * @param shouldEnterChunk True to automatically enter the chunk + */ + +void Iff::insertChunk(Tag name, bool shouldEnterChunk) +{ + const int CHUNK_OVERHEAD = sizeof(Tag) + sizeof(uint32); + + Tag t; + int offset; + + NOT_NULL(data); + IFF_DEBUG_FATAL(inChunk, "inside chunk"); + + // make sure the data array can handle this addition + adjustDataAsNeeded(CHUNK_OVERHEAD); + + // compute the offset to start inserting data at + offset = stack[stackDepth].start + stack[stackDepth].used; + + // add the form header + t = htonl(name); + memcpy(data+offset, &t, sizeof(Tag)); + offset += isizeof(Tag); + + // add the size of the chunk + memset(data+offset, 0, sizeof(uint32)); + + // enter the chunk if requested + if (shouldEnterChunk) + enterChunk(); +} + +// ---------------------------------------------------------------------- +/** + * Insert data into the current chunk at the current location. + * + * This routine will handle adding data into the middle of an existing + * chunk. The current position pointer will be moved to the end of + * the inserted data. + * + * If the Iff is not inside a chunk, this routine will call Fatal for + * debug compiles, but will have undefined behavior in release compiles. + * + * @param newData Data to put into the chunk + * @param dataLength Length of the data to copy into the chunk + */ + +void Iff::insertChunkData(const void *newData, int dataLength) +{ + NOT_NULL(data); + DEBUG_FATAL(dataLength < 0, ("dataLength < 0, %d", dataLength)); + IFF_DEBUG_FATAL(!inChunk, "not in chunk"); + + // make sure there is some data to insert + if (dataLength == 0) + return; + + NOT_NULL(newData); + + // make sure the data array can handle this addition + adjustDataAsNeeded(dataLength); + + // compute the offset to start inserting data at + const int offset = stack[stackDepth].start + stack[stackDepth].used; + + + // add the size of the chunk + memcpy(data+offset, newData, dataLength); + + // move the current pointer to the end of the inserted text + stack[stackDepth].used += dataLength; +} + + +// ---------------------------------------------------------------------- +/** + * Add a vector to the Iff chunk. + * + * This routine will write the vector to the chunk such that it can + * be read back using read_floatVector(). + * + * @param vector Vector to add to the chunk data + */ + +void Iff::insertChunkFloatVector(const Vector &vector) +{ + insertChunkData(static_cast(vector.x)); + insertChunkData(static_cast(vector.y)); + insertChunkData(static_cast(vector.z)); +} + +// ---------------------------------------------------------------------- +/** + * Add a vector argb to the Iff chunk. + * + * This routine will write the vector argb to the chunk such that it can + * be read back using read_floatVectorArgb(). + * + * @param color Vector to add to the chunk data + */ + +void Iff::insertChunkFloatVectorArgb(const VectorArgb &color) +{ + insertChunkData(static_cast(color.a)); + insertChunkData(static_cast(color.r)); + insertChunkData(static_cast(color.g)); + insertChunkData(static_cast(color.b)); +} + +// ---------------------------------------------------------------------- +/** + * Add a transform to the Iff chunk. + * + * This routine will write the transform to the chunk such that it can + * be read back using read_floatTransform(). + * + * @param transform Transform to add to the chunk data + */ + +void Iff::insertChunkFloatTransform(const Transform &transform) +{ + for (int y = 0; y < 3; ++y) + for (int x = 0; x < 4; ++x) + insertChunkData(static_cast(transform.matrix[y][x])); +} + +// ---------------------------------------------------------------------- +/** + * Add a quaternion to the Iff chunk. + * + * This routine will write the quaternion to the chunk such that it can + * be read back using read_floatQuaternion(). + */ + +void Iff::insertChunkFloatQuaternion(const Quaternion &quaternion) +{ + insertChunkData(static_cast(quaternion.w)); + insertChunkData(static_cast(quaternion.x)); + insertChunkData(static_cast(quaternion.y)); + insertChunkData(static_cast(quaternion.z)); +} + +// ---------------------------------------------------------------------- +/** + * Insert a string into the current chunk at the current location. + * + * This routine will call insertChunkData(const void *, int length) + * with the string using its string length (plus one for the null + * terminator). + */ + +void Iff::insertChunkString(const char *string) +{ + NOT_NULL(string); + insertChunkData(string, istrlen(string)+1); +} + +//---------------------------------------------------------------------- +/** +* Write a Unicode string into the iff. +* The string may contain nulls within the data. That is, this function writes the entire contents +* of basic_string::data (), rather than basic_string::c_str () +*/ + +void Iff::insertChunkString(const Unicode::String & str) +{ + insertChunkData (static_cast(str.size ())); + insertChunkArray(str.data (), str.size ()); +} + +// ---------------------------------------------------------------------- +/** + * Delete the specified number of bytes within the chunk. + * + * This routine will delete the specified number of bytes within the chunk + * at the current read position. The read position will be unchanged. + * + * Calling this routine while not in a chunk will cause Fatal to be called + * in debug compiles, but its behavior is undefined in release builds. + * + * Specifing a negative length will cause Fatal to be called in debug compiles, + * but its behavior is undefined in release builds. + * + * Trying to delete more bytes than is left in a chunk will cause Fatal to be + * called in debug compiles, but its behavior is undefined in release builds. + * + * @param dataLength Number of bytes to delete + */ + +void Iff::deleteChunkData(int dataLength) +{ + IFF_DEBUG_FATAL(!inChunk, "not in chunk"); + DEBUG_FATAL(dataLength < 0, ("dataLength to delete %d < 0", dataLength)); + DEBUG_FATAL(dataLength > getChunkLengthLeft(), ("deleting more data than exists %d/%d", dataLength, getChunkLengthLeft())); + adjustDataAsNeeded(-dataLength); +} + +// ---------------------------------------------------------------------- +/** + * Seek within a chunk. + * + * This function is only allowed after nonlinear functions have been enabled. + * + * This function behaves much like the normal file seek command, but it moves + * the read pointer within the current chunk. + * + * @param offset Distance to move + * @param seekType Base locaiton of movement + * @see Iff::allowNonlinearFunctions() + */ + +void Iff::seekWithinChunk(int offset, SeekType seekType) +{ + DEBUG_FATAL(!nonlinear, ("nonlinear commands not permitted")); + IFF_DEBUG_FATAL(!inChunk, "not in chunk"); + + switch (seekType) + { + case SeekBegin: + stack[stackDepth].used = offset; + break; + + case SeekCurrent: + stack[stackDepth].used += offset; + break; + + case SeekEnd: + stack[stackDepth].used = stack[stackDepth].length + offset; + break; + } + + DEBUG_FATAL(stack[stackDepth].used < 0, ("seek underflow %d", stack[stackDepth].used)); + DEBUG_FATAL(stack[stackDepth].used > stack[stackDepth].length, ("seek overflow %d/%d", stack[stackDepth].used, stack[stackDepth].length)); +} + +// ---------------------------------------------------------------------- +/** + * Return to the top of the enclosing form. + * + * This function is only allowed after nonlinear functions have been enabled. + * + * Calling this function will return the current read pointer to the beginning + * of the enclosing form. If this function is called while within a chunk, it + * will cause Fatal to be called in a debug compile, but will have undefined + * behavior in a release compile. + * + * @see Iff::allowNonlinearFunctions() + */ + +void Iff::goToTopOfForm(void) +{ + DEBUG_FATAL(!nonlinear, ("nonlinear commands not permitted")); + IFF_DEBUG_FATAL(inChunk, "in chunk"); + stack[stackDepth].used = 0; +} + + +// ---------------------------------------------------------------------- +/** + * Determine whether this Iff is at the end of its currently enclosing form. + * + * This routine indicates whether there are any more chunks or forms in the + * current form or not. + * + * @return True if at the end of the form, false otherwise + */ + +bool Iff::atEndOfForm(void) const +{ + return (stack[stackDepth].used == stack[stackDepth].length); +} + +// ---------------------------------------------------------------------- +/** + * Get the number of blocks left in the current enclosing form. + * + * @return The number of blocks left in the currently enclosing form + */ + +int Iff::getNumberOfBlocksLeft(void) const +{ + int result, offset; + + IFF_DEBUG_FATAL(inChunk, "in chunk"); + + // count the number of times we can advance the used amount by the block size + for (result = 0, offset = 0; stack[stackDepth].used+offset < stack[stackDepth].length; ++result, offset += (getLength(stackDepth, offset) + isizeof(Tag) + isizeof(uint32))) + ; + + return result; +} + +// ---------------------------------------------------------------------- +/** + * Return the name of the specified block. + * + * @return The tag for the specified block depth + */ + +Tag Iff::getBlockName(int depth) const +{ + Tag t; + + t = getFirstTag(depth); + if (t == TAG_FORM) + t = getSecondTag(depth); + + return t; +} + +// ---------------------------------------------------------------------- +/** + * Return the length of the current block. + * + * @return The length of the current block + */ + +int Iff::getCurrentLength(void) const +{ + return getLength(stackDepth); +} + +// ---------------------------------------------------------------------- +/** + * Determine if the currently selected block is a chunk. + * + * If at the end of the current form, so there is no currently selected block, + * this routine will call Fatal for debug compiles, but its behavior will be + * undefined for release compiles. + * + * @return True if the currently selected block is a chunk, false otherwise. + * @see Iff::atEndOfForm() + */ + +bool Iff::isCurrentChunk(void) const +{ + return (getFirstTag(stackDepth) != TAG_FORM); +} + +// ---------------------------------------------------------------------- +/** + * Determine if the currently selected block is a form. + * + * If at the end of the current form, so there is no currently selected block, + * this routine will call Fatal for debug compiles, but its behavior will be + * undefined for release compiles. + * + * @return True if the currently selected block is a form, false otherwise. + * @see Iff::atEndOfForm() + */ + +bool Iff::isCurrentForm(void) const +{ + return (getFirstTag(stackDepth) == TAG_FORM); +} + +// ---------------------------------------------------------------------- +/** + * Increase the Iff stack depth if needed. + * + * If adding another stack entry will cause the stack to overflow, this + * routine will allocate a new larger stack and copy the data from the + * old stack into the new stack. + * + * Currently the stack will grow linearly by 16 elements. + */ + +void Iff::growStackAsNeeded(void) +{ + FATAL(stackDepth >= maxStackDepth, ("stack already blown")); + + if (stackDepth+1 == maxStackDepth) + { + // add another 16 entries to the stack depth + maxStackDepth += 16; + + // allocate a new stack + Stack *newStack = new Stack[static_cast(maxStackDepth)]; + + // copy the data over + memcpy(newStack, stack, isizeof(*stack) * (stackDepth + 1)); + + // replace the old stack + delete [] stack; + stack = newStack; + } +} + +// ---------------------------------------------------------------------- + +bool Iff::enterForm(Tag name, bool validateName, bool optional) +{ + UNREF(optional); + + // make sure it's a form, and check to make sure it's named appropriately + if (!inChunk && !atEndOfForm() && isCurrentForm() && (!validateName || getSecondTag(stackDepth) == name)) + { + growStackAsNeeded(); + + // setup the next stack frame + Stack &s = stack[stackDepth+1]; + s.start = stack[stackDepth].start + stack[stackDepth].used + isizeof(Tag) + isizeof(uint32) + isizeof(Tag); + s.length = getLength(stackDepth) - isizeof(Tag); + s.used = 0; + + // advance the stack frame + ++stackDepth; + + return true; + } + + if (!optional) + { + char tagname[5]; + ConvertTagToString(name, tagname); + char buf[256]; + snprintf( buf, sizeof(buf), "enter form [%s] failed", tagname ); + + IFF_FATAL(true, (buf)); + } + return false; +} + +// ---------------------------------------------------------------------- +/** + * Exit the current form. + * + * It is illegal to attempt to exit the enclosing chunk while the Iff is not + * already within a chunk. Doing so in a debug compile will call Fatal, but + * in a release compile the behavior is undefined. + * + * This version of the routine will validate that the chunk name that it is + * exiting is of the specified name. If it is not, in a debug compile the + * + * @see Iff::enterForm() + */ + +void Iff::exitForm(Tag name, bool mayNotBeAtEndOfForm) +{ + UNREF(name); + UNREF(mayNotBeAtEndOfForm); + + IFF_DEBUG_FATAL(stackDepth == 0, ("exiting root")); + IFF_DEBUG_FATAL(inChunk, ("exitForm called within a chunk")); + IFF_DEBUG_FATAL(!mayNotBeAtEndOfForm && !atEndOfForm(), ("exit form but not at the end of it")); + IFF_FATAL(getSecondTag(stackDepth-1) != name, ("exit form with incorrect name")); + exitForm(mayNotBeAtEndOfForm); +} + +// ---------------------------------------------------------------------- +/** + * Exit the current form. + * + * It is illegal to exit the "root" form. Doing so in a debug compile will + * call Fatal, but in a release compile the behavior is undefined. + * + * It is also illegal to attempt to exit the enclosing form while the Iff has + * entered but not exited a chunk. Again, doing so in a debug compile will + * call Fatal, but in a release compile the behavior is undefined. + * + * Exiting the form will leave the Iff's current position just beyond that form + * the enclosing form. + * + * @see Iff::enterForm() + */ + +void Iff::exitForm(bool mayNotBeAtEndOfForm) +{ + UNREF(mayNotBeAtEndOfForm); + IFF_DEBUG_FATAL(stackDepth == 0, ("exiting root")); + IFF_DEBUG_FATAL(!mayNotBeAtEndOfForm && !atEndOfForm(), ("exiting form but not at the end of it")); + IFF_DEBUG_FATAL(inChunk, ("exit form called within a chunk")); + + // advance beyond the form we where in + stack[stackDepth-1].used += stack[stackDepth].length + isizeof(Tag) + isizeof(uint32) + isizeof(Tag); + + // pop the stack frame off + --stackDepth; +} + +// ---------------------------------------------------------------------- + +bool Iff::enterChunk(Tag name, bool validateName, bool optional) +{ + UNREF(optional); + + // make sure it's a chunk, and check to make sure it's named appropriately + if (!inChunk && !atEndOfForm() && isCurrentChunk() && (!validateName || getFirstTag(stackDepth) == name)) + { + growStackAsNeeded(); + + // setup the next stack frame + Stack &s = stack[stackDepth+1]; + s.start = stack[stackDepth].start + stack[stackDepth].used + isizeof(Tag) + isizeof(uint32); + s.length = getLength(stackDepth); + s.used = 0; + + // advance the stack frame + ++stackDepth; + + // mark that we are in a chunk + inChunk = true; + + return true; + } + + if (!optional) + { + char strName[8]; + char strFirstTag[8]; + ConvertTagToString(name, strName); + ConvertTagToString(getFirstTag(stackDepth), strFirstTag); + FATAL(true, ("enter chunk [%s] failed: inChunk=%d, atEndOfForm()=%d, isCurrentChunk()=%d, getFirstTag(stackDepth)=[%s]", + strName, inChunk, atEndOfForm(), isCurrentChunk(), strFirstTag)); + } + + return false; +} + +// ---------------------------------------------------------------------- +/** + * Exit the current chunk. + * + * It is illegal to attempt to exit the enclosing chunk while the Iff is not + * already within a chunk. Doing so in a debug compile will call Fatal, but + * in a release compile the behavior is undefined. + * + * This version of the routine will validate that the chunk name that it is + * exiting is of the specified name. If it is not, in a debug compile the + * + * @see Iff::enterForm() + */ + +void Iff::exitChunk(Tag name, bool mayNotBeAtEndOfChunk) +{ + UNREF(name); + UNREF(mayNotBeAtEndOfChunk); + IFF_DEBUG_FATAL(!inChunk, ("not in chunk")); + IFF_DEBUG_FATAL(!mayNotBeAtEndOfChunk && getChunkLengthLeft(), ("exiting chunk but not at the end of it")); + IFF_FATAL(getFirstTag(stackDepth-1) != name, ("exit with incorrect name")); + exitChunk(mayNotBeAtEndOfChunk); +} + +// ---------------------------------------------------------------------- +/** + * Exit the current chunk. + * + * It is illegal to attempt to exit the enclosing chunk while the Iff is not + * already within a chunk. Doing so in a debug compile will call Fatal, but + * in a release compile the behavior is undefined. + * + * @see Iff::enterForm() + */ + +void Iff::exitChunk(bool mayNotBeAtEndOfChunk) +{ + UNREF(mayNotBeAtEndOfChunk); + IFF_DEBUG_FATAL(!mayNotBeAtEndOfChunk && getChunkLengthLeft(), ("exiting chunk but not at the end of it")); + + // leave the chunk + DEBUG_FATAL(!inChunk, ("not in chunk")); + inChunk = false; + + // advance beyond the chunk we where in + stack[stackDepth-1].used += stack[stackDepth].length + isizeof(Tag) + isizeof(uint32); + + // pop the stack frame off + --stackDepth; +} + +// ---------------------------------------------------------------------- +/** + * Skip forward past the specified number of blocks. + * + * It is illegal to call this function while inside a chunk. Doing so in + * a debug compile will call Fatal, but the behavior will be undefined in + * a release compile. + * + * If there are fewer blocks left in the current form that the number of + * blocks requested to skip, the behavior is determined by the optional flag. + * If the optional flag is true, the routine will return false. If + * that flag is not false, in debug compiles the Fatal routine will + * be called, but in release compiles the routine will return false. + * + * @param count Number of blocks to advance + * @param optional Flag to determine behavior if the operation cannot succeed + * @return True if the specified number of blocks were skipped, otherwise false + */ + +bool Iff::goForward(int count, bool optional) +{ + UNREF(optional); + DEBUG_FATAL(inChunk, ("in chunk")); + + for ( ; count; --count) + { + if (atEndOfForm()) + { + DEBUG_FATAL(!optional, ("at end of form")); + return false; + } + + stack[stackDepth].used += (getLength(stackDepth) + isizeof(Tag) + isizeof(uint32)); + } + + return true; +} + +// ---------------------------------------------------------------------- + +bool Iff::seek(Tag name, BlockType type) +{ + DEBUG_FATAL(inChunk, ("in chunk")); + + while (!atEndOfForm()) + { + if (getCurrentName() == name && (type == BT_either || (type == BT_form && isCurrentForm() || (type == BT_chunk && isCurrentChunk())))) + return true; + + // advance past the current block + stack[stackDepth].used += (getLength(stackDepth) + isizeof(Tag) + isizeof(uint32)); + } + + return false; +} + +// ---------------------------------------------------------------------- +/** + * Return the total number of elements of data in the chunk, read or unread. + * + * It is illegal to call this routine if the Iff is not currently within + * a chunk. + * + * The routine will make sure the amount of data is an even multiple of + * the element size. If it is not, the routine will call Fatal in debug + * compiles, and in release compiles the number of elements will be + * truncated to the highest integer. + * + * If the element size specified is 1, this routine will return the number + * of bytes of data in the chunk. + * + * @param elementSize Number of bytes per element + * @return The total number of elements of data in the chunk + * @see Iff::getChunkLengthLeft() + */ + +int Iff::getChunkLengthTotal(int elementSize) const +{ + DEBUG_FATAL(!inChunk, ("not in chunk")); + DEBUG_FATAL(stack[stackDepth].length % elementSize != 0, ("%d not a multiple of %d", stack[stackDepth].length, elementSize)); + return (stack[stackDepth].length / elementSize); +} + +// ---------------------------------------------------------------------- +/** + * Return the number of unread elements of data in the chunk. + * + * It is illegal to call this routine if the Iff is not currently within + * a chunk. + * + * The routine will make sure the amount of data is an even multiple of + * the element size. If it is not, the routine will call Fatal in debug + * compiles, and in release compiles the number of elements will be + * truncated to the highest integer. + * + * If the element size specified is 1, this routine will return the number + * of bytes of data left in the chunk. + * + * @param elementSize Number of bytes per element + * @return The number of unread elements of data in the chunk + * @see Iff::getChunkLengthTotal() + */ + +int Iff::getChunkLengthLeft(int elementSize) const +{ + DEBUG_FATAL(!inChunk, ("not in chunk")); + const int left = stack[stackDepth].length - stack[stackDepth].used; + DEBUG_FATAL(left % elementSize != 0, ("%d not a multiple of %d", left, elementSize)); + return (left / elementSize); +} + +// ---------------------------------------------------------------------- + +void Iff::read_misc(void *readData, int readLength) +{ + NOT_NULL(readData); + NOT_NULL(data); + DEBUG_FATAL(!inChunk, ("not in chunk")); + + Stack &s = stack[stackDepth]; + + DEBUG_FATAL(s.used+readLength > s.length, ("overflow %d/%d in file [%s]", s.used+readLength, s.length, getFileName())); + memcpy(readData, data+s.start+s.used, readLength); + + s.used += readLength; +} + +// ---------------------------------------------------------------------- + +void Iff::read_floatVector(int count, Vector *array) +{ + float f; + + NOT_NULL(array); + + for (int i = 0; i < count; ++i) + { + read_misc(&f, isizeof(f)); + array[i].x = static_cast(f); + + read_misc(&f, isizeof(f)); + array[i].y = static_cast(f); + + read_misc(&f, isizeof(f)); + array[i].z = static_cast(f); + } +} + +// ---------------------------------------------------------------------- + +void Iff::read_floatTransform(int count, Transform *array) +{ + float f; + + NOT_NULL(array); + + for (int i = 0; i < count; ++i) + for (int y = 0; y < 3; ++y) + for (int x = 0; x < 4; ++x) + { + read_misc(&f, isizeof(f)); + array[i].matrix[y][x] = static_cast(f); + } +} + +// ---------------------------------------------------------------------- + +void Iff::read_floatQuaternion(int count, Quaternion *array) +{ + NOT_NULL(array); + + for (int i = 0; i < count; ++i) + { + Quaternion &q = array[i]; + q.w = read_float(); + q.x = read_float(); + q.y = read_float(); + q.z = read_float(); + } +} + +// ---------------------------------------------------------------------- + +Transform Iff::read_floatTransform(void) +{ + Transform t; + float f; + + for (int y = 0; y < 3; ++y) + for (int x = 0; x < 4; ++x) + { + read_misc(&f, isizeof(f)); + t.matrix[y][x] = static_cast(f); + } + + return t; +} + +// ---------------------------------------------------------------------- + +Quaternion Iff::read_floatQuaternion(void) +{ + const real w = read_float(); + const real x = read_float(); + const real y = read_float(); + const real z = read_float(); + + return Quaternion(w, x, y, z); +} + +// ---------------------------------------------------------------------- +/** + * Read a string from the Iff. + * + * The string is an 8-bit character string with a 0-valued byte as a terminator. + * + * If the string is longer than the max length specified, the routine will call + * Fatal in debug compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param string Buffer to read the string into + * @param maxLength Size of the buffer + */ + +void Iff::read_string(char *string, int maxLength) +{ + NOT_NULL(string); + NOT_NULL(data); + DEBUG_FATAL(!inChunk, ("not in chunk")); + + Stack &s = stack[stackDepth]; + + // get a pointer to the start of the source string + char *source = reinterpret_cast(data + s.start + s.used); + + // copy the string + for ( ; *source; ++string, ++source, ++s.used, --maxLength) + { + DEBUG_FATAL(s.used >= s.length, ("hit end of chunk before string terminator")); + DEBUG_FATAL(maxLength <= 0, ("destination string too short")); + *string = *source; + } + + // step over the null terminator on the input + ++s.used; + + // null terminate the output string + DEBUG_FATAL(maxLength <= 0, ("destination string too short")); + *string = '\0'; +} + +// ---------------------------------------------------------------------- +/** + * Read a string from the Iff. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The string is an 8-bit character string with a 0-valued byte as a terminator. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dynamically allocated string + */ + +char *Iff::read_string(void) +{ + NOT_NULL(data); + DEBUG_FATAL(!inChunk, ("not in chunk")); + + Stack &s = stack[stackDepth]; + + // get a pointer to the start of the source string + char *source = reinterpret_cast(data + s.start + s.used); + int maxLength = s.length - s.used; + int sourceLength = 0; + + // search for the end of the string + for ( ; sourceLength < maxLength && source[sourceLength]; ++sourceLength) + ; + + // verify that we found the null terminator + DEBUG_FATAL(sourceLength >= maxLength, ("hit end of chunk before string terminator")); + + // create and copy the string + ++sourceLength; + char *string = new char[static_cast(sourceLength)]; + memcpy(string, source, sourceLength); + + // update the amount used in the chunk + s.used += sourceLength; + + return string; +} + +// ---------------------------------------------------------------------- + +/** +* Read a std::string from the iff. +* The string may NOT contain nulls within the data. That is, this function only reads the contents +* of basic_string::c_str (), rather than basic_string::data (). +* +* Note that his behavior is different from the unicode version of this function. +*/ + +void Iff::read_string(std::string &string) +{ + NOT_NULL(data); + DEBUG_FATAL(!inChunk, ("not in chunk")); + + Stack &s = stack[stackDepth]; + + // get a pointer to the start of the source string + char *source = reinterpret_cast(data + s.start + s.used); + int maxLength = s.length - s.used; + int sourceLength = 0; + + // search for the end of the string + for ( ; sourceLength < maxLength && source[sourceLength]; ++sourceLength) + ; + + // verify that we found the null terminator + DEBUG_FATAL(sourceLength >= maxLength, ("hit end of chunk before string terminator")); + + // account for the null terminator + ++sourceLength; + + s.used += sourceLength; + string.reserve(sourceLength); + string = source; +} + +// ---------------------------------------------------------------------- + +std::string Iff::read_stdstring() +{ + std::string str; + read_string (str); + return str; +} + +//---------------------------------------------------------------------- + +/** +* Read a Unicode string from the iff. +* The string may contain nulls within the data. That is, this function can read the entire contents +* of basic_string::data (), rather than basic_string::c_str () +*/ + +//---------------------------------------------------------------------- + +void Iff::read_string(Unicode::String &str) +{ + const int32 count = read_int32 (); + unsigned short * data = new unsigned short [count]; + read_uint16 (count, data); + str.assign (data, static_cast(count)); + delete [] data; +} + +/** +* Read a Unicode string from the iff. +* The string may contain nulls within the data. That is, this function can read the entire contents +* of basic_string::data (), rather than basic_string::c_str () +*/ + +//---------------------------------------------------------------------- + +Unicode::String Iff::read_unicodeString() +{ + Unicode::String str; + read_string (str); + return str; +} + +// ---------------------------------------------------------------------- +/** + * Read a vector from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the vector that was in the chunk + * @see Iff::read_*() + */ + +Vector Iff::read_floatVector(void) +{ + float f; + real x, y, z; + + read_misc(&f, isizeof(f)); + x = static_cast(f); + + read_misc(&f, isizeof(f)); + y = static_cast(f); + + read_misc(&f, isizeof(f)); + z = static_cast(f); + + return Vector(x, y, z); +} + +// ---------------------------------------------------------------------- +/** + * Read a vector from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the vector that was in the chunk + * @see Iff::read_*() + */ + +VectorArgb Iff::read_floatVectorArgb(void) +{ + float f; + real a, r, g, b; + + read_misc(&f, isizeof(f)); + a = static_cast(f); + + read_misc(&f, isizeof(f)); + r = static_cast(f); + + read_misc(&f, isizeof(f)); + g = static_cast(f); + + read_misc(&f, isizeof(f)); + b = static_cast(f); + + return VectorArgb(a, r, g, b); +} + +// ====================================================================== + diff --git a/engine/shared/library/sharedFile/src/shared/Iff.h b/engine/shared/library/sharedFile/src/shared/Iff.h new file mode 100644 index 00000000..71e07c44 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/Iff.h @@ -0,0 +1,1353 @@ +// ====================================================================== +// +// Iff.h +// +// copyright 1998 Bootprint Entertainment +// copyright 2001 Sony Online Entertainment +// +// ====================================================================== + +#ifndef INCLUDED_Iff_H +#define INCLUDED_Iff_H + +// ====================================================================== + +#include "sharedFoundation/Tag.h" + +class AbstractFile; +class Quaternion; +class Transform; +class Vector; +class VectorArgb; + +// ====================================================================== + +class Iff +{ +public: + + enum SeekType + { + SeekBegin, + SeekCurrent, + SeekEnd + }; + +private: + + // explicitly disable these routines + Iff(const Iff &); + Iff &operator =(const Iff &); + +private: + + enum + { + DEFAULT_STACK_DEPTH = 64 + }; + + enum BlockType + { + BT_either, + BT_form, + BT_chunk + }; + + struct Stack + { + int start; + int length; + int used; + }; + +private: + + char *fileName; + + int maxStackDepth; + int stackDepth; + Stack *stack; + + int length; + byte *data; + + bool inChunk; + bool growable; + bool nonlinear; + bool ownsData; + +private: + + Tag getFirstTag(int depth) const; + int getLength(int depth, int offset=0) const; + Tag getSecondTag(int depth) const; + Tag getBlockName(int depth) const; + + void fatal(const char *string) const; + void read_misc(void *data, int length); + void growStackAsNeeded(void); + void adjustDataAsNeeded(int size); + int calculateRawDataSize(void) const; + + bool enterForm(Tag name, bool validateName, bool optional); + bool enterChunk(Tag name, bool validateName, bool optional); + + bool seek(Tag name, BlockType blockType); + +public: + + static void install(); + static bool isValid(char const * fileName); + +public: + + Iff(void); + Iff(int newDataSize, const byte *newData, bool iffOwnsData=true); + explicit Iff(const char *fileName, bool optional=false); + explicit Iff(int initialSize, bool growable=true, bool clearDataBuffer=false); + ~Iff(void); + + const char* getFileName (void) const; + + uint32 calculateCrc() const; + + // open and close operations + bool open(const char *filename, bool optional=false); + void open(AbstractFile & file); + void open(AbstractFile & file, char const * fileName); + void close(void); + bool write(const char *filename, bool optional=false); + + // raw data retrieval + const byte *getRawData(void) const; + int getRawDataSize(void) const; + + void formatLocation(char *buffer, int bufferLength) const; + + // creating forms and chunk + void insertIff(const Iff *iff); + void insertForm(Tag name, bool shouldEnterForm=true); + void insertChunk(Tag name, bool shouldEnterChunk=true); + void insertChunkData(const void *data, int length); + void insertChunkString(const char *string); + void insertChunkString(const Unicode::String & str); + void insertChunkFloatVector(const Vector &vector); + void insertChunkFloatVectorArgb (const VectorArgb& color); + void insertChunkFloatTransform(const Transform &transform); + void insertChunkFloatQuaternion(const Quaternion &quaternion); + + template void insertChunkArray(T const * array, int size) + { + insertChunkData(array,sizeof(T) * size); + } + template void insertChunkData(const T &object) + { + // -qq- I really don't want to put the body here, but it's the only way it works in MSVC 5.0 + insertChunkData(&object, sizeof(object)); + } + + // delete data from the chunk data + void deleteChunkData(int length); + + // nonlinear functions + void allowNonlinearFunctions(void); + void seekWithinChunk(int offset, SeekType seekType); + void goToTopOfForm(void); + + // get information on current block + Tag getCurrentName(void) const; + int getCurrentLength(void) const; + bool isCurrentChunk(void) const; + bool isCurrentForm(void) const; + bool atEndOfForm(void) const; + + // get the number of blocks left in the current enclosing form + int getNumberOfBlocksLeft(void) const; + + // get information about the number of bytes in the current chunk + int getChunkLengthTotal(int elementSize=1) const; + int getChunkLengthLeft(int elementSize=1) const; + + // enter/exit forms + void enterForm(void); + void enterForm(Tag name); + bool enterForm(bool optional); + bool enterForm(Tag name, bool optional); + void exitForm(Tag name, bool mayNotBeAtEndOfForm=false); + void exitForm(bool mayNotBeAtEndOfForm=false); + + // enter/exit chunks + void enterChunk(void); + void enterChunk(Tag name); + bool enterChunk(bool optional); + bool enterChunk(Tag name, bool optional); + void exitChunk(Tag name, bool mayNotBeAtEndOfChunk=false); + void exitChunk(bool mayNotBeAtEndOfChunk=false); + + // skip forward blocks + bool goForward(int count=1, bool optional=false); + bool seek(Tag name); + bool seekForm(Tag name); + bool seekChunk(Tag name); + + // read single entities + bool read_bool8(void); + int8 read_int8(void); + int16 read_int16(void); + int32 read_int32(void); + uint8 read_uint8(void); + uint16 read_uint16(void); + uint32 read_uint32(void); + real read_float(void); + Vector read_floatVector(void); + VectorArgb read_floatVectorArgb(void); + Transform read_floatTransform(void); + Quaternion read_floatQuaternion(void); + + // read arrays + void read_int8 (int count, int8 *array); + void read_int16 (int count, int16 *array); + void read_int32 (int count, int32 *array); + void read_uint8 (int count, uint8 *array); + void read_uint16(int count, uint16 *array); + void read_uint32(int count, uint32 *array); + void read_char (int count, char *array); + void read_floatVector(int count, Vector *array); + void read_floatTransform(int count, Transform *array); + void read_floatQuaternion(int count, Quaternion *array); + + int8 *read_int8 (int count); + int16 *read_int16 (int count); + int32 *read_int32 (int count); + uint8 *read_uint8 (int count); + uint16 *read_uint16(int count); + uint32 *read_uint32(int count); + char *read_char (int count); + + void readRest_int8 (int8 *array, int numberOfElements); + void readRest_int16 (int16 *array, int numberOfElements); + void readRest_int32 (int32 *array, int numberOfElements); + void readRest_uint8 (uint8 *array, int numberOfElements); + void readRest_uint16(uint16 *array, int numberOfElements); + void readRest_uint32(uint32 *array, int numberOfElements); + void readRest_char (char *array, int numberOfElements); + + int8 *readRest_int8 (void); + int16 *readRest_int16 (void); + int32 *readRest_int32 (void); + uint8 *readRest_uint8 (void); + uint16 *readRest_uint16(void); + uint32 *readRest_uint32(void); + char *readRest_char (void); + + void read_string(char *string, int maxLength); + char *read_string(void); + + void read_string(std::string &string); + std::string read_stdstring(); + + void read_string(Unicode::String &str); + Unicode::String read_unicodeString(); + +#if 0 + real *read_float (int count, real *array=NULL); +#endif +}; + +// ---------------------------------------------------------------------- +/** + * retrieve name of the iff file + * + * @return iff name + */ + +inline const char* Iff::getFileName (void) const +{ + return fileName; +} + +// ====================================================================== +// retrieve the number of bytes contained in the raw iff data buffer +// +// Return Value: +// +// The number of bytes of raw iff data stored in the buffer returned +// by Iff::getRawData(). +// +// See Also: +// +// Iff:getRawData() + +inline int Iff::getRawDataSize(void) const +{ + if (!data || !stack) + return 0; + else + return stack[0].length; +} + +// ---------------------------------------------------------------------- +/** + * retrieve a read-only pointer to the raw data managed by the Iff. + * + * This routine can be used by utilities that need the ability + * to store raw iff data via a mechanism other than Iff::write(). + * + * @return A read-only pointer to the data buffer containing the raw Iff data + * managed by this Iff object. It may be NULL if no data is currently + * managed by the Iff. + * @see Iff::getRawDataSize(), Iff::write() + */ + +inline const byte *Iff::getRawData(void) const +{ + return data; +} + +// ---------------------------------------------------------------------- +/** + * Allow use of nonlinear functions. + * + * This routine will enable the Iff to perform the nonlinear movement + * functions, listed in the "see also" section. + * + * @see Iff::goToTopOfForm(), Iff::seekWithinChunk() + */ + +inline void Iff::allowNonlinearFunctions(void) +{ + nonlinear = true; +} + +// ---------------------------------------------------------------------- +/** + * Return the name of the currently selected block. + * + * If at the end of the current form, so there is no currently selected block, + * this routine will call Fatal for debug compiles, but its behavior will be + * undefined for release compiles. + * + * @return The tag for the currently selected block + * @see Iff::atEndOfForm() + */ + +inline Tag Iff::getCurrentName(void) const +{ + return getBlockName(stackDepth); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next form. + * + * This routine will enter the next form in the Iff regardless of its name. + * If the Iff is not positioned to enter a new form, it will call Fatal. + */ + +inline void Iff::enterForm(void) +{ + static_cast(enterForm(0, false, false)); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next form. + * + * This routine will check to make sure that the Iff is positioned to enter + * a new form with the specified name, and enter it. If the Iff is not pointing + * at a form, or the name isn't correct, Fatal will be called. + * + * @param name Name of the next form + */ + +inline void Iff::enterForm(Tag name) +{ + static_cast(enterForm(name, true, false)); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next form. + * + * This routine will enter the next form in the Iff regardless of its name. + * If the Iff is not positioned to enter a new form, the behavior depends upon + * the optional flag. If the optional flag is true, the routine will return + * false, otherwise it will call Fatal. + */ + +inline bool Iff::enterForm(bool optional) +{ + return enterForm(0, false, optional); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next form. + * + * This routine will check to make sure that the Iff is positioned to enter + * a new form with the specified name, and enter it. If the Iff is not pointing + * at a form, or the name isn't correct, the behavior depends upon + * the optional flag. If the optional flag is true, the routine will return + * false, otherwise it will call Fatal. + */ + +inline bool Iff::enterForm(Tag name, bool optional) +{ + return enterForm(name, true, optional); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next chunk. + * + * This routine will enter the next chunk in the Iff regardless of its name. + * If the Iff is not positioned to enter a new chunk, it will call Fatal. + */ + +inline void Iff::enterChunk(void) +{ + static_cast(enterChunk(0, false, false)); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next chunk. + * + * This routine will check to make sure that the Iff is positioned to enter + * a new chunk with the specified name, and enter it. If the Iff is not pointing + * at a chunk, or the name isn't correct, Fatal will be called. + */ + +inline void Iff::enterChunk(Tag name) +{ + static_cast(enterChunk(name, true, false)); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next chunk. + * + * This routine will enter the next chunk in the Iff regardless of its name. + * If the Iff is not positioned to enter a new chunk, the behavior depends upon + * the optional flag. If the optional flag is true, the routine will return + * false, otherwise it will call Fatal. + */ + +inline bool Iff::enterChunk(bool optional) +{ + return enterChunk(0, false, optional); +} + +// ---------------------------------------------------------------------- +/** + * Enter the next chunk. + * + * This routine will check to make sure that the Iff is positioned to enter + * a new chunk with the specified name, and enter it. If the Iff is not pointing + * at a chunk, or the name isn't correct, the behavior depends upon + * the optional flag. If the optional flag is true, the routine will return + * false, otherwise it will call Fatal. + */ + +inline bool Iff::enterChunk(Tag name, bool optional) +{ + return enterChunk(name, true, optional); +} + +// ---------------------------------------------------------------------- +/** + * Seek forward through the current enclosing form looking for the named block. + * + * The block may be either a chunk or a form. If the block is not found, + * the iff read position will be left at the end of the block. + * + * @param name The tag of the block to seek for. + * @return True if the named block was found, otherwise false. + */ + +inline bool Iff::seek(Tag name) +{ + return seek(name, BT_either); +} + +// ---------------------------------------------------------------------- +/** + * Seek forward through the current enclosing form looking for the named form. + * + * If the block is not found, the iff read position will be left at the end of the enclosing form. + * + * @param name The tag of the form to seek for. + * @return True if the named form was found, otherwise false. + */ + +inline bool Iff::seekForm(Tag name) +{ + return seek(name, BT_form); +} + +// ---------------------------------------------------------------------- +/** + * Seek forward through the current enclosing form looking for the named chunk. + * + * If the block is not found, the iff read position will be left at the end of the enclosing form. + * + * @param name The tag of the chunk to seek for. + * @return True if the named chunk was found, otherwise false. + */ + +inline bool Iff::seekChunk(Tag name) +{ + return seek(name, BT_chunk); +} + +// ---------------------------------------------------------------------- +/** + * Read a bool from the current chunk. + * + * The bool is read as an 8 bit value. If the value is 0, the result is false, + * otherwise it is true. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the bool that was in the chunk. + * @see Iff::read_*() + */ + +inline bool Iff::read_bool8(void) +{ + int8 i; + read_misc(&i, isizeof(i)); + return (i != 0); +} + +// ---------------------------------------------------------------------- +/** + * Read an int8 from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the int8 that was in the chunk + * @see Iff::read_*() + */ + +inline int8 Iff::read_int8(void) +{ + int8 i; + read_misc(&i, isizeof(i)); + return i; +} + +// ---------------------------------------------------------------------- +/** + * Read an int16 from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the int16 that was in the chunk + * @see Iff::read_*() + */ + +inline int16 Iff::read_int16(void) +{ + int16 i; + read_misc(&i, isizeof(i)); + return i; +} + +// ---------------------------------------------------------------------- +/** + * Read an int32 from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the int32 that was in the chunk + * @see Iff::read_*() + */ + +inline int32 Iff::read_int32(void) +{ + int32 i; + read_misc(&i, isizeof(i)); + return i; +} + +// ---------------------------------------------------------------------- +/** + * Read an uint8 from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the uint8 that was in the chunk + * @see Iff::read_*() + */ + +inline uint8 Iff::read_uint8(void) +{ + uint8 i; + read_misc(&i, isizeof(i)); + return i; +} + +// ---------------------------------------------------------------------- +/** + * Read an uint16 from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the uint16 that was in the chunk + * @see Iff::read_*() + */ + +inline uint16 Iff::read_uint16(void) +{ + uint16 i; + read_misc(&i, isizeof(i)); + return i; +} + +// ---------------------------------------------------------------------- +/** + * Read an uint32 from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the uint32 that was in the chunk + * @see Iff::read_*() + */ + +inline uint32 Iff::read_uint32(void) +{ + uint32 i; + read_misc(&i, isizeof(i)); + return i; +} + +// ---------------------------------------------------------------------- +/** + * Read an float from the current chunk. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return The value of the float that was in the chunk + * @see Iff::read_*() + */ + +inline real Iff::read_float(void) +{ + float f; + read_misc(&f, isizeof(f)); + return static_cast(f); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int8's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_int8(int count, int8 *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int16's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_int16(int count, int16 *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int32's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_int32(int count, int32 *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int8's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_uint8(int count, uint8 *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int16's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_uint16(int count, uint16 *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int32's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_uint32(int count, uint32 *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int32's from the current chunk into a specified array. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @param array Array to store the entries + * @see Iff::read_*() + */ + +inline void Iff::read_char(int count, char *array) +{ + read_misc(array, count * isizeof(*array)); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int8's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline int8 *Iff::read_int8(int count) +{ + int8 *array = new int8[static_cast(count)]; + read_int8(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int16's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline int16 *Iff::read_int16(int count) +{ + int16 *array = new int16[static_cast(count)]; + read_int16(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int32's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline int32 *Iff::read_int32(int count) +{ + int32 *array = new int32[static_cast(count)]; + read_int32(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint8's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline uint8 *Iff::read_uint8(int count) +{ + uint8 *array = new uint8[static_cast(count)]; + read_uint8(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint16's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline uint16 *Iff::read_uint16(int count) +{ + uint16 *array = new uint16[static_cast(count)]; + read_uint16(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint32's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline uint32 *Iff::read_uint32(int count) +{ + uint32 *array = new uint32[static_cast(count)]; + read_uint32(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of char's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param count Number of elements to read + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline char *Iff::read_char(int count) +{ + char *array = new char[static_cast(count)]; + read_char(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int8's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_int8(int8 *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_int8(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int16's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_int16(int16 *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_int16(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int32's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_int32(int32 *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_int32(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint8's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_uint8(uint8 *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_uint8(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint16's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_uint16(uint16 *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_uint16(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint32's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_uint32(uint32 *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_uint32(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of char's from the current chunk into a pre-allocated array. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If the read will overwrite the specified array, in debug compiles the + * routine will call Fatal, but in release compiles the effect will be + * undefined. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @param array Array to read the data into + * @param numberOfElements Maximum number of elements inthe array + * @see Iff::read_*() + */ + +inline void Iff::readRest_char(char *array, int numberOfElements) +{ + UNREF(numberOfElements); + const int count = getChunkLengthLeft(isizeof(*array)); + DEBUG_FATAL(count > numberOfElements, ("Iff::ReadRest_int8 overflow array %d/%d", count, numberOfElements)); + read_char(count, array); +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int8's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline int8 *Iff::readRest_int8(void) +{ + const int count = getChunkLengthLeft(isizeof(int8)); + int8 *const array = new int8[static_cast(count)]; + read_int8(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int16's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline int16 *Iff::readRest_int16(void) +{ + const int count = getChunkLengthLeft(isizeof(int16)); + int16 * const array = new int16[static_cast(count)]; + read_int16(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of int32's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline int32 *Iff::readRest_int32(void) +{ + const int count = getChunkLengthLeft(isizeof(int32)); + int32 * const array = new int32[static_cast(count)]; + read_int32(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint8's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline uint8 *Iff::readRest_uint8(void) +{ + const int count = getChunkLengthLeft(isizeof(uint8)); + uint8 * const array = new uint8[static_cast(count)]; + read_uint8(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint16's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline uint16 *Iff::readRest_uint16(void) +{ + const int count = getChunkLengthLeft(isizeof(uint16)); + uint16 * const array = new uint16[static_cast(count)]; + read_uint16(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of uint32's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline uint32 *Iff::readRest_uint32(void) +{ + const int count = getChunkLengthLeft(isizeof(uint32)); + uint32 * const array = new uint32[static_cast(count)]; + read_uint32(count, array); + return array; +} + +// ---------------------------------------------------------------------- +/** + * Read an array of char's from the current chunk into a dynamically allocated array. + * + * The caller is responsible for freeing the memory allocated by this routine. + * + * The number of elements that this routine reads will be determined by the + * amount of data left in the chunk. If the amount of data left is not a + * exact multiple of the element size of the array, the routine will call + * Fatal in debug compiles, and will truncate the number of items in release + * compiles. + * + * If this routine attempts to read beyond the end of the chunk, this routine + * will call Fatal in debug compiles, but its behavior is undefined in release + * compiles. + * + * @return Dyanmically allocated array containing the data + * @see Iff::read_*() + */ + +inline char *Iff::readRest_char(void) +{ + const int count = getChunkLengthLeft(isizeof(char)); + char * const array = new char[static_cast(count)]; + read_char(count, array); + return array; +} + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/IndentedFileWriter.cpp b/engine/shared/library/sharedFile/src/shared/IndentedFileWriter.cpp new file mode 100644 index 00000000..2c5eb7f2 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/IndentedFileWriter.cpp @@ -0,0 +1,129 @@ +// ====================================================================== +// +// IndentedFileWriter.cpp +// Copyright 2004 Sony Online Entertainment, Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/IndentedFileWriter.h" +#include + +// ====================================================================== +// class IndentedFileWriter: PUBLIC STATIC +// ====================================================================== + +IndentedFileWriter *IndentedFileWriter::createWriter(char const *filename) +{ + IndentedFileWriter *const newWriter = new IndentedFileWriter(); + if (newWriter->createFile(filename)) + return newWriter; + else + { + // Kill the new writer since we weren't able to open the file for writing. + delete newWriter; + return NULL; + } +} + +// ====================================================================== +// class IndentedFileWriter: PUBLIC +// ====================================================================== + +IndentedFileWriter::~IndentedFileWriter() +{ + if (m_file) + { + fclose(m_file); + m_file = NULL; + } +} + +// ---------------------------------------------------------------------- + +void IndentedFileWriter::indent() +{ + ++m_indentCount; +} + +// ---------------------------------------------------------------------- + +void IndentedFileWriter::unindent() +{ + if (m_indentCount <= 0) + { + WARNING(true, ("unindent() called when indentation level is [%d], ignoring unindent call.", m_indentCount)); + return; + } + --m_indentCount; +} + +// ---------------------------------------------------------------------- + +void IndentedFileWriter::writeLine(char const *line) const +{ + FATAL(!m_file, ("writeLine(): m_file is NULL, programmer error.")); + FATAL(!line, ("writeLine(): line argument is NULL.")); + + //-- Write one tab for each indentation level. + for (int i = 0; i < m_indentCount; ++i) + fputc(static_cast('\t'), m_file); + + //-- Write the line of text. + fputs(line, m_file); + + //-- Add a newline. + fputc(static_cast('\n'), m_file); +} + +// ---------------------------------------------------------------------- + +void IndentedFileWriter::writeLineFormat(char const *format, ...) const +{ + va_list varArgList; + + //-- Capture the variable arguments. + va_start(varArgList, format); + + //-- Print the string to the buffer. + char buffer[2048]; + + int const formatCount = vsnprintf(buffer, sizeof(buffer), format, varArgList); + UNREF(formatCount); + + //-- Ensure it's null terminated. + buffer[sizeof(buffer) - 1] = '\0'; + + //-- Do the real print. + writeLine(buffer); + + //-- Release the var args. + va_end(varArgList); +} + +// ====================================================================== +// class IndentedFileWriter: PRIVATE +// ====================================================================== + +IndentedFileWriter::IndentedFileWriter() : + m_indentCount(0), + m_file(NULL) +{ +} + +// ---------------------------------------------------------------------- + +bool IndentedFileWriter::createFile(char const *filename) +{ + if (m_file) + { + WARNING(true, ("m_file is already set, ignoring createFile() call.")); + return false; + } + + m_file = fopen(filename, "w"); + return (m_file != NULL); +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/IndentedFileWriter.h b/engine/shared/library/sharedFile/src/shared/IndentedFileWriter.h new file mode 100644 index 00000000..679a700c --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/IndentedFileWriter.h @@ -0,0 +1,48 @@ +// ====================================================================== +// +// IndentedFileWriter.h +// Copyright 2004 Sony Online Entertainment, Inc. +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_IndentedFileWriter_H +#define INCLUDED_IndentedFileWriter_H + +// ====================================================================== + +#include + +// ====================================================================== + +class IndentedFileWriter +{ +public: + + static IndentedFileWriter *createWriter(char const *filename); + +public: + + ~IndentedFileWriter(); + + void indent(); + void unindent(); + + void writeLine(char const *line) const; + void writeLineFormat(char const *format, ...) const; + +private: + + IndentedFileWriter(); + bool createFile(char const *filename); + +private: + + int m_indentCount; + FILE *m_file; + +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/MemoryFile.cpp b/engine/shared/library/sharedFile/src/shared/MemoryFile.cpp new file mode 100644 index 00000000..4c023f8c --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/MemoryFile.cpp @@ -0,0 +1,177 @@ +// ====================================================================== +// +// MemoryFile.cpp +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/MemoryFile.h" + +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/MemoryBlockManager.h" + +// ====================================================================== + +MemoryBlockManager *MemoryFile::ms_memoryBlockManager; + +// ====================================================================== + +void MemoryFile::install() +{ + DEBUG_FATAL(ms_memoryBlockManager, ("MemoryFile::install already installed")); + ms_memoryBlockManager = new MemoryBlockManager("MemoryFile::memoryBlockManager", true, sizeof(MemoryFile), 0, 0, 0); + ExitChain::add(&remove, ("MemoryFile::remove")); +} + +// ---------------------------------------------------------------------- + +void MemoryFile::remove() +{ + DEBUG_FATAL(!ms_memoryBlockManager,("MemoryFileis not installed")); + + delete ms_memoryBlockManager; + ms_memoryBlockManager = 0; +} + +// ---------------------------------------------------------------------- + +void *MemoryFile::operator new(size_t size) +{ + DEBUG_FATAL(!ms_memoryBlockManager,("MemoryFile is not installed")); + + // do not try to alloc a descendant class with this allocator + DEBUG_FATAL(size != sizeof(MemoryFile),("bad size")); + UNREF(size); + + return ms_memoryBlockManager->allocate(); +} + +// ---------------------------------------------------------------------- + +void MemoryFile::operator delete(void *pointer) +{ + DEBUG_FATAL(!ms_memoryBlockManager,("Projectile is not installed")); + ms_memoryBlockManager->free(pointer); +} + +// ====================================================================== + +MemoryFile::MemoryFile(byte *buffer, int length) +: AbstractFile(PriorityData), + m_buffer(buffer), + m_length(length), + m_offset(0) +{ +} + +// ---------------------------------------------------------------------- + +MemoryFile::MemoryFile(AbstractFile *file) +: AbstractFile(PriorityData), + m_buffer(NULL), + m_length(file->length()), + m_offset(0) +{ + m_buffer = file->readEntireFileAndClose(); +} + +// ---------------------------------------------------------------------- + +MemoryFile::~MemoryFile() +{ + close(); +} + +// ---------------------------------------------------------------------- + +bool MemoryFile::isOpen() const +{ + return m_buffer != NULL; +} + +// ---------------------------------------------------------------------- + +int MemoryFile::length() const +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + return m_length; +} + +// ---------------------------------------------------------------------- + +int MemoryFile::tell() const +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + return m_offset; +} + +// ---------------------------------------------------------------------- + +bool MemoryFile::seek(SeekType seekType, int offset) +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + switch (seekType) + { + case SeekBegin: + m_offset = offset; + break; + + case SeekCurrent: + m_offset += offset; + break; + + case SeekEnd: + m_offset = m_length + offset; + break; + } + + m_offset = clamp(0, m_offset, m_length); + return true; +} + +// ---------------------------------------------------------------------- + +int MemoryFile::read(void *destinationBuffer, int numberOfBytes) +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + DEBUG_FATAL(numberOfBytes < 0,("asked to read %d bytes", numberOfBytes)); + + // don't let them read past end-of-file + if (m_offset + numberOfBytes > m_length) + numberOfBytes = m_length - m_offset; + + // read from the buffer + memcpy(destinationBuffer, m_buffer + m_offset, numberOfBytes); + + m_offset += numberOfBytes; + return numberOfBytes; +} + +// ---------------------------------------------------------------------- + +int MemoryFile::write(int, const void *) +{ + DEBUG_FATAL(true, ("writing to a memory file is not supported")); + return 0; +} + +// ---------------------------------------------------------------------- + +void MemoryFile::close() +{ + delete [] m_buffer; + m_buffer = NULL; +} + +// ---------------------------------------------------------------------- + +byte *MemoryFile::readEntireFileAndClose() +{ + byte *result = m_buffer; + m_buffer = NULL; + return result; +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/MemoryFile.h b/engine/shared/library/sharedFile/src/shared/MemoryFile.h new file mode 100644 index 00000000..d2f77d81 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/MemoryFile.h @@ -0,0 +1,65 @@ +// ====================================================================== +// +// MemoryFile.h +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_MemoryFile_H +#define INCLUDED_MemoryFile_H + +// ====================================================================== + +class MemoryBlockManager; + +#include "fileInterface/AbstractFile.h" + +// ====================================================================== + +class MemoryFile : public AbstractFile +{ +public: + + static void install(); + static void *operator new(size_t size); + static void operator delete(void* pointer); + +public: + + MemoryFile(byte *buffer, int length); + MemoryFile(AbstractFile *file); + virtual ~MemoryFile(); + + virtual bool isOpen() const; + virtual int length() const; + virtual int tell() const; + virtual bool seek(SeekType seekType, int offset); + virtual int read(void *destinationBuffer, int numberOfBytes); + virtual int write(int numberOfBytes, const void *sourceBuffer); + virtual void close(); + + virtual byte *readEntireFileAndClose(); + +private: + + MemoryFile(); + MemoryFile(const MemoryFile &); + MemoryFile &operator =(const MemoryFile &); + + static void remove(); + +private: + + static MemoryBlockManager *ms_memoryBlockManager; + +private: + + byte *m_buffer; + const int m_length; + int m_offset; +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/SetupSharedFile.cpp b/engine/shared/library/sharedFile/src/shared/SetupSharedFile.cpp new file mode 100644 index 00000000..0db5e683 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/SetupSharedFile.cpp @@ -0,0 +1,39 @@ +// ====================================================================== +// +// SetupSharedFile.cpp +// copyright (c) 2001 Sony Online Entertainment +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/SetupSharedFile.h" + +#include "sharedFile/ConfigSharedFile.h" +#include "sharedFile/FileManifest.h" +#include "sharedFile/FileStreamer.h" +#include "sharedFile/FileStreamerFile.h" +#include "sharedFile/Iff.h" +#include "sharedFile/MemoryFile.h" +#include "sharedFile/OsFile.h" +#include "sharedFile/TreeFile.h" +#include "sharedFile/ZlibFile.h" +#include "sharedDebug/InstallTimer.h" + +// ====================================================================== + +void SetupSharedFile::install(bool useFileStreamer, uint32 skuBits) +{ + InstallTimer const installTimer("SetupSharedFile::install"); + + ConfigSharedFile::install(); + FileStreamerFile::install(); + FileStreamer::install(useFileStreamer); + OsFile::install(); + TreeFile::install(skuBits); + FileManifest::install(); + MemoryFile::install(); + ZlibFile::install(); + Iff::install(); +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/SetupSharedFile.h b/engine/shared/library/sharedFile/src/shared/SetupSharedFile.h new file mode 100644 index 00000000..17765bb7 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/SetupSharedFile.h @@ -0,0 +1,27 @@ +// ====================================================================== +// +// SetupSharedFile.h +// copyright (c) 2001 Sony Online Entertainment +// +// ====================================================================== + +#ifndef INCLUDED_SetupSharedFile_H +#define INCLUDED_SetupSharedFile_H + +// ====================================================================== + +class SetupSharedFile +{ +public: + + static void install(bool useFileStreamer, uint32 skuBits=0); + +private: + SetupSharedFile(); + SetupSharedFile(const SetupSharedFile &); + SetupSharedFile &operator =(const SetupSharedFile &); +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/TreeFile.cpp b/engine/shared/library/sharedFile/src/shared/TreeFile.cpp new file mode 100644 index 00000000..09acf384 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/TreeFile.cpp @@ -0,0 +1,971 @@ +// ====================================================================== +// +// TreeFile.cpp +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2001-2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/TreeFile.h" +#include "sharedFile/TreeFile_SearchNode.h" + +#include "sharedDebug/DebugFlags.h" +#include "sharedDebug/PixCounter.h" +#include "sharedFile/ConfigSharedFile.h" +#include "sharedFile/FileManifest.h" +#include "sharedFile/FileStreamer.h" +#include "sharedFoundation/ConfigFile.h" +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/Os.h" +#include "sharedFoundation/Production.h" +#include "sharedSynchronization/Mutex.h" + +#include +#include +#include +#include +#include +#include + +// ====================================================================== + +bool TreeFile::ms_installed; +bool TreeFile::ms_haveCachedFiles; +Mutex TreeFile::ms_criticalSection; +TreeFile::SearchNodes TreeFile::ms_searchNodes; +TreeFile::SearchCache * TreeFile::ms_searchCache; + +int TreeFile::ms_numberOfFilesOpenedTotal; +int TreeFile::ms_sizeOfFilesOpenedTotal; +#if PRODUCTION == 0 +bool TreeFile::ms_debugReportFlagShowMetrics; +bool TreeFile::ms_debugReportFlagShowSearchPaths; +bool TreeFile::ms_debugLogFlag; +int TreeFile::ms_unexpectedCacheMisses; +#endif + +namespace TreeFileNamespace +{ + static char const * const cms_priorityStrings[] = + { + "L", + "M", + "H" + }; + + class CachedFilesComparator + { + public: + bool operator ()(const char *lhs, const char *rhs) const + { + return strcmp(lhs, rhs) < 0; + } + }; + + typedef std::map CachedFilesMap; + static CachedFilesMap cachedFilesMap; + +#if PRODUCTION == 0 + bool ms_debugLogSynchronousOnly; + bool ms_warnTreeFileOpens; + + PixCounter::ResetInteger ms_numberOfFilesOpened[3]; + PixCounter::ResetInteger ms_sizeOfFilesOpened[3]; + PixCounter::ResetString ms_treeFilesOpened; +#endif +} +using namespace TreeFileNamespace; + +// ====================================================================== +// Install the TreeFile system + +void TreeFile::install(uint32 skuBits) +{ + DEBUG_FATAL(ms_installed, ("already installed")); + ms_installed = true; + +#if PRODUCTION == 0 + ms_numberOfFilesOpened[0].bindToCounter("TreeFileLowOpenCount"); + ms_numberOfFilesOpened[1].bindToCounter("TreeFileMediumOpenCount"); + ms_numberOfFilesOpened[2].bindToCounter("TreeFileHighOpenCount"); + ms_sizeOfFilesOpened[0].bindToCounter("TreeFileLowOpenBytes"); + ms_sizeOfFilesOpened[1].bindToCounter("TreeFileMediumOpenBytes"); + ms_sizeOfFilesOpened[2].bindToCounter("TreeFileHighOpenBytes"); + ms_treeFilesOpened.bindToCounter("TreeFilesOpened"); +#endif + + ExitChain::add(TreeFile::remove, "TreeFile::remove", 0, true); + + // the value 20 is used here for legacy support + int const maxPriority = ConfigFile::getKeyInt("SharedFile", "maxSearchPriority", 20); + + // search all the specified skus + bool first = true; + for (uint32 sku = 0; first || skuBits; first = false, skuBits &= ~(1 << sku), ++sku) + { + // skip adding and verifying path, tree, and TOC for skus not flagged on the account + if (!first && (skuBits & (1 << sku)) == 0) + continue; + + // figure out what sku to be loading from. handle 0 to mean no sku numbers are specificed (legacy support) + char skuText[8] = { '\0' }; + if (skuBits) + sprintf(skuText, "_%02d_", static_cast(sku)); + + // add all the search paths + for (int priority = 0; priority <= maxPriority; ++priority) + { + // add all search paths + { + char buffer[32]; + sprintf(buffer, "searchPath%s%d", skuText, priority); + + char const * result; + for (int index = 0; (result = ConfigFile::getKeyString("SharedFile", buffer, index, NULL)) != NULL; ++index) + TreeFile::addSearchPath(result, priority); + } + + // add all search trees + { + char buffer[32]; + sprintf(buffer, "searchTree%s%d", skuText, priority); + + char const * result; + for (int index = 0; (result = ConfigFile::getKeyString("SharedFile", buffer, index, NULL)) != NULL; ++index) + TreeFile::addSearchTree(result, priority); + } + + // add in any TOCs + { + char buffer[32]; + sprintf(buffer, "searchTOC%s%d", skuText, priority); + + char const * result; + for (int index = 0; (result = ConfigFile::getKeyString("SharedFile", buffer, index, NULL)) != NULL; ++index) + TreeFile::addSearchTOC(result, priority); + } + } + + // add cached files + { + int const numberPreloads = ConfigSharedFile::getNumberOfTreeFilePreloads(); + for (int i = 0; i < numberPreloads; ++i) + { + char const * const path = ConfigSharedFile::getTreeFilePreload(i); + if (path != 0) + { + AbstractFile * const file = open(path, AbstractFile::PriorityData, true); + if (file) + { + TreeFile::addCachedFile(path, file); + DEBUG_REPORT_LOG(true, ("preloaded %s\n", path)); + } + else + { + DEBUG_REPORT_LOG(true, ("FAILED preloading %s\n", path)); + } + } + } + } + } + + //-- add an absolute search path after all search nodes + { + TreeFile::addSearchAbsolute(ConfigFile::getKeyInt("SharedFile", "searchAbsolute", 0, !ms_searchNodes.empty() ? ms_searchNodes.front()->getPriority() + 1 : 0)); + } + + //-- add search cache after all search nodes + { + TreeFile::addSearchCache(ConfigFile::getKeyInt("SharedFile", "searchCache", 0, !ms_searchNodes.empty() ? ms_searchNodes.front()->getPriority() + 1 : 0)); + } + +#if PRODUCTION == 0 + DebugFlags::registerFlag(ms_debugReportFlagShowMetrics, "SharedFile", "reportTreeFileMetrics", debugReportMetrics); + DebugFlags::registerFlag(ms_debugReportFlagShowSearchPaths, "SharedFile", "reportTreeFilePaths", debugReportPaths); + DebugFlags::registerFlag(ms_debugLogFlag, "SharedFile", "logTreeFileOpens"); + DebugFlags::registerFlag(ms_debugLogSynchronousOnly, "SharedFile", "logTreeFileOpensSynchronousOnly"); + DebugFlags::registerFlag(ms_warnTreeFileOpens, "SharedFile", "warnTreeFileOpens"); +#endif +} + +// ---------------------------------------------------------------------- +/** + * Remove the TreeFile system. + */ + +void TreeFile::remove(void) +{ + clearCachedFiles(); + + ms_criticalSection.enter(); + + DEBUG_FATAL(!ms_installed, ("not installed")); + ms_installed = false; + + // remove all the search nodes + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); i != iEnd; ++i) + delete *i; + ms_searchNodes.clear(); + + ms_searchCache = 0; + + ms_criticalSection.leave(); + +#if PRODUCTION == 0 + DEBUG_WARNING(ms_unexpectedCacheMisses, ("%d unexpected asynchronous loader cache misses occurred", ms_unexpectedCacheMisses)); + + DebugFlags::unregisterFlag(ms_debugReportFlagShowMetrics); + DebugFlags::unregisterFlag(ms_debugReportFlagShowSearchPaths); + DebugFlags::unregisterFlag(ms_debugLogFlag); + DebugFlags::unregisterFlag(ms_debugLogSynchronousOnly); + DebugFlags::unregisterFlag(ms_warnTreeFileOpens); +#endif +} + +// ---------------------------------------------------------------------- + +bool TreeFile::isLoggingFiles() +{ +#if PRODUCTION == 0 + return ms_debugLogFlag; +#else + return false; +#endif +} + +// ---------------------------------------------------------------------- + +#ifdef _DEBUG +void TreeFile::setLogTreeFileOpens(bool const logTreeFileOpens) +{ + ms_debugLogFlag = logTreeFileOpens; +} +#endif + +// ---------------------------------------------------------------------- + +#if PRODUCTION == 0 + +void TreeFile::debugReportMetrics() +{ + REPORT_LOG_PRINT(true, ("TreeFile: %d,%d,%d=opened %d,%d,%d=bytes %d=oTotal %d=bTotal %d=cacheMiss\n", ms_numberOfFilesOpened[0].getLastFrameValue(), ms_numberOfFilesOpened[1].getLastFrameValue(), ms_numberOfFilesOpened[2].getLastFrameValue(), ms_sizeOfFilesOpened[0].getLastFrameValue(), ms_sizeOfFilesOpened[1].getLastFrameValue(), ms_sizeOfFilesOpened[2].getLastFrameValue(), ms_numberOfFilesOpenedTotal, ms_sizeOfFilesOpenedTotal, ms_unexpectedCacheMisses)); +} + +#endif + +// ---------------------------------------------------------------------- + +#if PRODUCTION == 0 + +void TreeFile::debugReportPaths() +{ + ms_criticalSection.enter(); + + DEBUG_REPORT_PRINT(true, ("TreeFile search paths\n")); + DEBUG_OUTPUT_STATIC_VIEW_BEGINFRAME("Foundation\\Treefile"); + DEBUG_OUTPUT_STATIC_VIEW("Foundation\\Treefile", ("TreeFile search paths\n")); + + // Print information about the search paths + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); i != iEnd; ++i) + (*i)->debugPrint(); + + DEBUG_OUTPUT_STATIC_VIEW_ENDFRAME("Foundation\\Treefile"); + + ms_criticalSection.leave(); +} + +#endif + +// ---------------------------------------------------------------------- + +bool TreeFile::searchNodePriorityOrder(const SearchNode *a, const SearchNode *b) +{ + return a->getPriority() > b->getPriority(); +} + +// ---------------------------------------------------------------------- +/** + * Add a SearchNode to the node list. + * + * The node list is prioritized from highest to lowest priority. New + * nodes with the same priority as old nodes will be inserted after the + * last priority match. + */ + +void TreeFile::addSearchNode(SearchNode *newNode) +{ + NOT_NULL(newNode); + ms_criticalSection.enter(); + + SearchNodes::iterator insertionPoint = std::lower_bound(ms_searchNodes.begin(), ms_searchNodes.end(), newNode, searchNodePriorityOrder); + IGNORE_RETURN(ms_searchNodes.insert(insertionPoint, newNode)); + + ms_criticalSection.leave(); +} + +// ---------------------------------------------------------------------- +/** + * Add a SearchAbsolute to the node list. + * + * This type of search entry will attempt to try to load the file from + * the specified path, by prefixing the path before every file name. The + * path may be absolute or relative. + * + * @param path Path to the search directory to add + * @param priority Priority for this search entry + */ + +void TreeFile::addSearchPath(const char *path, int priority) +{ + addSearchNode(new SearchPath(priority, path)); +} + +// ---------------------------------------------------------------------- +/** + * Add a SearchAbsolute to the node list. + * + * This type of search entry will attempt to try to load the file exactly + * as named from the disk subsystem, allowing absolute or relative file + * name paths. + * + * @param priority Search priority for the absolute path + */ + +void TreeFile::addSearchAbsolute(int priority) +{ + addSearchNode(new SearchAbsolute(priority)); +} + +// ---------------------------------------------------------------------- + +void TreeFile::addSearchCache(int priority) +{ + IS_NULL(ms_searchCache); + ms_searchCache = new SearchCache(priority); + addSearchNode(ms_searchCache); +} + +// ---------------------------------------------------------------------- +/** + * Add a SearchTree to the node list. + * + * @param fileName File name of the search tree + * @param priority Search priority for the search tree + */ + +void TreeFile::addSearchTree(const char *fileName, int priority) +{ + if (FileStreamer::exists (fileName)) + addSearchNode(new SearchTree(priority, fileName)); + else + { +#ifdef _DEBUG + DEBUG_FATAL(true, ("TreeFile::addSearchTree - [%s] not found", fileName)); +#else + WARNING(true, ("TreeFile::addSearchTree - [%s] not found", fileName)); +#endif + } +} + +// ---------------------------------------------------------------------- +/** + * Add a SearchTOC to the node list. + * + * @param fileName File name of the search tree + */ + +void TreeFile::addSearchTOC(const char *fileName, int priority) +{ + if (FileStreamer::exists (fileName)) + addSearchNode(new SearchTOC(priority, fileName)); + else + { +#ifdef _DEBUG + DEBUG_FATAL(true, ("TreeFile::addSearchTOC - [%s] not found", fileName)); +#else + WARNING(true, ("TreeFile::addSearchTOC - [%s] not found", fileName)); +#endif + } +} + +// ---------------------------------------------------------------------- +/** + * Validate a SearchTree. + * + * @param fileName File name of the search tree to validate + */ + +bool TreeFile::validateSearchTree(const char *fileName) +{ + return SearchTree::validate(fileName); +} + +// ---------------------------------------------------------------------- +/** + * Remove all the search settings. + * + * This routine will remove all the search trees, paths, and absolute + * settings from the TreeFile class. + */ + +void TreeFile::removeAllSearches(void) +{ + ms_criticalSection.enter(); + + // remove all the search nodes + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); i != iEnd; ++i) + delete *i; + ms_searchNodes.clear(); + + ms_criticalSection.leave(); +} + +// ---------------------------------------------------------------------- +/** + * Find the node (if any) the requested file is in. + * + * @return Pointer to the highest priority node containing the file. If the + * file is not found, NULL is returned. If checking for filename collisions is + * set on, then this function DEBUG_FATALS if multiple files match + */ + +TreeFile::SearchNode *TreeFile::find(const char *fileName) +{ + DEBUG_FATAL(!ms_installed, ("not installed")); + + if (!fileName) + { + DEBUG_WARNING(true, ("TreeFile::find() Cannot find a null filename")); + return NULL; + } + + if (fileName[0] == '\0') + { + DEBUG_WARNING(true, ("TreeFile::find() Cannot find an empty filename")); + return NULL; + } + + // search the list of nodes looking to see if the specified file exists + bool deleted = false; + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); !deleted && i != iEnd; ++i) + if ((*i)->exists(fileName, deleted)) + return *i; + + return NULL; +} + +// ---------------------------------------------------------------------- +/** + * Check if a specified file can be found. + * + * @param fileName File name to check for existance + * @return True if the file exists, otherwise false + */ + +bool TreeFile::exists(const char *fileName) +{ + char fixedFileName[Os::MAX_PATH_LENGTH]; + fixUpFileName(fixedFileName, fileName, true); + + DEBUG_FATAL(!ms_installed, ("TreeFile::exists not installed")); + +#if PRODUCTION == 0 + FileManifest::addNewManifestEntry(fixedFileName, 0); +#endif + + return (find(fixedFileName) != NULL); +} + +// ---------------------------------------------------------------------- + +int TreeFile::getFileSize(const char *fileName) +{ + char fixedFileName[Os::MAX_PATH_LENGTH]; + fixUpFileName(fixedFileName, fileName, true); + + // search the list of nodes looking to see if the specified file exists + bool deleted = false; + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); !deleted && i != iEnd; ++i) + { + int size = (*i)->getFileSize(fixedFileName, deleted); + if (size >= 0) + return size; + } + + return -1; +} + +// ---------------------------------------------------------------------- +/** + * This function assumes the output buffer is large enough. + * The output path will be the same length or shorter than the input path. + */ + +void TreeFile::fixUpFileName(char *output, const char *fileName, bool warning) +{ + bool currentIsSlash = false; + bool previousIsSlash = false; + + UNREF(warning); + +#ifdef _DEBUG + // since we only emit a warning the first time we see the malformed file name, if we invert the warning-desired flag + // it will behave exactly as we want + warning = !warning; +#endif + + // skip this warnings for paths + const char *f = fileName; + + // skip leading "\" or "/" + while (f[0] == '\\' || f[0] == '/') + { +#ifdef _DEBUG + if (!warning) + { + WARNING(true, ("Malformed file name: %s'", fileName)); + warning = true; + } +#endif + + ++f; + } + + // skip leading ".\" or "./" + while (f[0] == '.' && (f[1] == '\\' || f[1] == '/')) + { +#ifdef _DEBUG + if (!warning) + { + WARNING(true, ("Malformed file name: %s'", fileName)); + warning = true; + } +#endif + + f += 2; + } + + // skip leading "..\" or "../" + while (f[0] == '.' && f[1] == '.' && (f[2] == '\\' || f[2] == '/')) + { +#ifdef _DEBUG + if (!warning) + { + WARNING(true, ("Malformed file name: %s'", fileName)); + warning = true; + } +#endif + + f += 3; + } + + for (; *f; ++f) + { + // convert all backslashes to forward slashes, and lowercase all the characters + const char c = *f; + if (c == '\\' || c == '/') + { + currentIsSlash = true; + *output = '/'; + } + else + { + currentIsSlash = false; + *output = static_cast(tolower(c)); + } + + // strip repeated forward slashes + if (!currentIsSlash || !previousIsSlash) + { + ++output; + previousIsSlash = currentIsSlash; + } +#ifdef _DEBUG + else + if (!warning) + { + WARNING(true, ("Malformed file name: %s'", fileName)); + warning = true; + } +#endif + } + + *output = '\0'; +} + +// ---------------------------------------------------------------------- +/** + * Fix up the file name. + * + * This function will lowercase the string, change all slashes to forward slashes, remove duplicate slashes, + * and more. This function will not strip off full paths that are part of the search directories. + * + * The output buffer must be at least as large as the input buffer. The output buffer may be the same as + * the input buffer, however warning messages may be garbled in that case. + */ + +void TreeFile::fixUpFileName(const char *fileName, char *outName) +{ + fixUpFileName(outName, fileName, false); +} + +// ---------------------------------------------------------------------- +/** + * Find the node (if any) the requested file is in. + * + * @return Pointer to the highest priority node containing the file. If the + * file is not found, NULL is returned. + */ + +bool TreeFile::getPathName(const char *fileName, char *pathName, int pathNameLength) +{ + NOT_NULL(fileName); + NOT_NULL(pathName); + DEBUG_FATAL(pathNameLength < 1, ("invalid pathNameLength %d", pathNameLength)); + DEBUG_FATAL(!ms_installed, ("not installed")); + + // search the list of nodes looking to see if the specified file exists + SearchNode *node = find(fileName); + if (node) + { + node->getPathName(fileName, pathName, pathNameLength); + return true; + } + + *pathName = '\0'; + return false; +} + +// ---------------------------------------------------------------------- +/** + * Open a file. + * + * The handle of the file is copied to the handlePointer. The handle + * is -1 on failure. + * + * If the allowFail parameter is false, this routine will call Fatal + * if the file could not be opened + * + * @param fileName File name to open + * @param allowFail True to return false on failure, otherwise Fatal + * @return pointer to a newly allocated AbstractFile-derived class (caller must delete), NULL if failure. + */ + +AbstractFile* TreeFile::open(const char *fileName, AbstractFile::PriorityType priority, bool allowFail) +{ + AbstractFile *file = NULL; + + char fixedFileName[Os::MAX_PATH_LENGTH]; + fixUpFileName(fixedFileName, fileName, true); + + // check the cache to see if the file has been preloaded + ms_criticalSection.enter(); + const bool haveCachedFiles = ms_haveCachedFiles; + ms_criticalSection.leave(); + + if (haveCachedFiles) + { + ms_criticalSection.enter(); + + CachedFilesMap::iterator cachedIterator = cachedFilesMap.find(fixedFileName); + if (cachedIterator != cachedFilesMap.end()) + { + file = cachedIterator->second; + cachedIterator->second = NULL; + } + + ms_criticalSection.leave(); + +#if PRODUCTION == 0 + if (!file && Os::isMainThread()) + { + DEBUG_REPORT_LOG(true, ("unexpected cache miss %d %s\n", ms_unexpectedCacheMisses, fixedFileName)); + ++ms_unexpectedCacheMisses; + } +#endif + + if (file) + { +#if PRODUCTION == 0 + REPORT_LOG(ms_debugLogFlag && !ms_debugLogSynchronousOnly, ("TF::open(%s) %s @ [cached]\n", cms_priorityStrings[priority], fixedFileName)); + if (PixCounter::connectedToPixProfiler()) + ms_treeFilesOpened.append("C\t%d\t%s\n", file->length(), fixedFileName); + + FileManifest::addNewManifestEntry(fixedFileName, file->length()); +#endif + return file; + } + } + +#if PRODUCTION == 0 + bool first = true; +#endif + + bool deleted = false; + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); !file && !deleted && i != iEnd; ++i) + { + file = (*i)->open(fixedFileName, priority, deleted); + +#if PRODUCTION == 0 + if (file) + { + if (PixCounter::connectedToPixProfiler()) + ms_treeFilesOpened.append("%s\t%d\t%s\n", first ? "F" : cms_priorityStrings[priority], file->length(), fixedFileName); + + if (ms_debugLogFlag || ms_warnTreeFileOpens) + { + char buffer[Os::MAX_PATH_LENGTH]; + (*i)->getPathName(fixedFileName, buffer, sizeof(buffer)); + + if (ms_warnTreeFileOpens) + WARNING(true, ("TF::open(%s) %s @ %s, [size=%d]\n", cms_priorityStrings[priority], fixedFileName, buffer, file->length())); + else + { + REPORT_LOG(!ms_debugLogSynchronousOnly || (ms_debugLogSynchronousOnly && (priority == AbstractFile::PriorityData) && (*i != ms_searchCache)), ("TF::open(%s) %s @ %s, [size=%d]\n", cms_priorityStrings[priority], fixedFileName, buffer, file->length())); + DEBUG_OUTPUT_CHANNEL("Foundation\\Treefile", ("TF::open %s -- %s\n", fixedFileName, buffer)); + } + } + } + + first = false; +#endif + } + + if (!file) + { + FATAL(!allowFail, ("open '%s' not found", fixedFileName)); + +#if PRODUCTION == 0 + FileManifest::addNewManifestEntry(fixedFileName, 0); +#endif + + return NULL; + } + + const int length = file->length(); + ++ms_numberOfFilesOpenedTotal; + ms_sizeOfFilesOpenedTotal += length; + +#if PRODUCTION == 0 + ++ms_numberOfFilesOpened[priority]; + ms_sizeOfFilesOpened[priority] += length; + + FileManifest::addNewManifestEntry(fixedFileName, length); +#endif + + return file; +} + +// ---------------------------------------------------------------------- + +int TreeFile::getNumberOfSearchPaths(void) +{ + int count = 0; + + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); i != iEnd; ++i) + if (dynamic_cast(*i) != 0) + count++; + + return count; +} + +// ---------------------------------------------------------------------- + +const char *TreeFile::getSearchPath(int index) +{ + int count = 0; + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); i != iEnd; ++i) + { + const SearchPath *searchPath = dynamic_cast(*i); + if (searchPath != NULL) + { + if (count == index) + return searchPath->getPathName(); + count++; + } + } + + return NULL; +} + +//----------------------------------------------------------------- +/** + * This function will return the shortest trailing path of its input that + * can be loaded by the TreeFile. If no files can be loaded, this routine + * will return NULL. + */ + +const char *TreeFile::getShortestExistingPath(const char *path) +{ + NOT_NULL(path); + + if (!*path) + return NULL; + + const char *result = NULL; + do + { + // check if this one exists + if (exists(path)) + result = path; + + // advance past the next path separator + while (*path && *path != '\\' && *path != '/') + ++path; + if (*path) + ++path; + + } while (*path); + + return result; +} + +// ---------------------------------------------------------------------- + +static bool pathcompare(const char *lhs, const char *rhs) +{ + for ( ; *lhs && *rhs; ++lhs, ++rhs) + { + char l = static_cast(tolower(*lhs)); + if (l == '\\') + l = '/'; + + char r = static_cast(tolower(*rhs)); + if (r == '\\') + r = '/'; + + if (l != r) + return false; + } + + return *lhs == '\0' && *rhs == '/'; +} + +// ---------------------------------------------------------------------- + +bool TreeFile::stripTreeFileSearchPathFromFile(const char *inputPath, char *outputPath, int outputPathBufferSize) +{ + // convert the input to an absolute patah + char buffer[Os::MAX_PATH_LENGTH]; + if (!Os::getAbsolutePath(inputPath, buffer, sizeof(buffer))) + return false; + + // scan all the search nodes + const SearchNodes::iterator iEnd = ms_searchNodes.end(); + for (SearchNodes::iterator i = ms_searchNodes.begin(); i != iEnd; ++i) + { + // make sure it's a relative search path + const SearchPath *searchPath = dynamic_cast(*i); + if (searchPath != NULL) + { + // check to see if the the search path is a prefix for the requested path + const char *path = searchPath->getPathName(); + + if (pathcompare(path, buffer)) + { + // make sure the output buffer has enough room + int const pathLength = static_cast(strlen(path)); + int const outputLength = static_cast(strlen(buffer)) - pathLength; + if (outputLength > outputPathBufferSize) + return false; + + // return the buffer + strcpy(outputPath, buffer + pathLength + 1); + return true; + } + } + } + + return false; +} + +// ---------------------------------------------------------------------- + +bool TreeFile::stripTreeFileSearchPathFromFile(const std::string &inputPath, std::string &outputPath) +{ + char buffer[Os::MAX_PATH_LENGTH]; + + if (!stripTreeFileSearchPathFromFile(inputPath.c_str(), buffer, sizeof(buffer))) + return false; + + outputPath = buffer; + return true; +} + +// ---------------------------------------------------------------------- + +void TreeFile::addCachedFile(const char *fileName, AbstractFile *file) +{ + ms_criticalSection.enter(); + + const bool result = cachedFilesMap.insert(CachedFilesMap::value_type(fileName, file)).second; + UNREF(result); + DEBUG_FATAL(!result, ("item was already present")); + ms_haveCachedFiles = true; + + ms_criticalSection.leave(); +} + +// ---------------------------------------------------------------------- + +void TreeFile::clearCachedFiles() +{ + ms_criticalSection.enter(); + + // delete any remaining files + CachedFilesMap::iterator iEnd = cachedFilesMap.end(); + for (CachedFilesMap::iterator i = cachedFilesMap.begin(); i != iEnd; ++i) + if (i->second) + { + DEBUG_REPORT_LOG(true, ("Unused cached tree file %s(%d)\n", i->first, i->second->length())); + delete i->second; + } + + cachedFilesMap.clear(); + ms_haveCachedFiles = false; + + ms_criticalSection.leave(); +} + +// ---------------------------------------------------------------------- + +int TreeFile::getNumberOfFilesOpenedTotal() +{ + return ms_numberOfFilesOpenedTotal; +} + +// ---------------------------------------------------------------------- + +int TreeFile::getSizeOfFilesOpenedTotal() +{ + return ms_sizeOfFilesOpenedTotal; +} + +// ---------------------------------------------------------------------- + +int TreeFile::cacheFile(char const * const fileName) +{ + NOT_NULL(ms_searchCache); + return ms_searchCache->addCachedFile(fileName); +} + +// ====================================================================== + +AbstractFile* TreeFile::TreeFileFactory::createFile(const char *filename, const char *open_type) +{ + UNREF(open_type); + return open(filename, AbstractFile::PriorityData, true); +} + +// ====================================================================== + diff --git a/engine/shared/library/sharedFile/src/shared/TreeFile.h b/engine/shared/library/sharedFile/src/shared/TreeFile.h new file mode 100644 index 00000000..f28e7315 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/TreeFile.h @@ -0,0 +1,143 @@ +// ====================================================================== +// +// TreeFile.h +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2001-2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_TreeFile_H +#define INCLUDED_TreeFile_H + +// ====================================================================== + +class Compressor; + +// @todo codereorg remove dependency on mutex +class Mutex; +#include "fileInterface/AbstractFile.h" + +/*NOTE: TreeFile_SearchNode.h is included AFTER TreeFile is defined to allow + the subclasses to be broken out into a separate file module without creating + additional include dependencies on the modules that use TreeFile. +*/ + +// ====================================================================== + +/** + * Provide access to files, whether in a treefile or on actual disk. + * + * This class is multi-thread safe for modifying the search order, opening + * files, and closing files. Applications should not attempt to read a + * single opened file from multiple threads at the same time, but it is + * acceptable to read from different files in different threads. + */ +class TreeFile +{ +private: + + // The following are defined in a seperate file (TreeFile_SearchNode.*) + class SearchNode; + class SearchPath; + class SearchAbsolute; + class SearchTree; + class SearchTOC; + class SearchCache; + + friend class SearchNode; + friend class TreeFileBuilder; + friend class TreeFileBuilderHelper; + friend class TreeFileExtractor; + +public: + + class TreeFileFactory : public AbstractFileFactory + { + public: + virtual AbstractFile* createFile(const char *filename, const char *open_type); + }; + friend class TreeFileFactory; + +public: + + static void install(uint32 skuBits); + static void remove(); + + static bool isLoggingFiles(); + +#ifdef _DEBUG + static void setLogTreeFileOpens(bool logTreeFileOpens); +#endif + + static void debugReportPaths(); + static void debugReportMetrics(); + + static void addSearchPath(const char *path, int priority); + static void addSearchAbsolute(int priority); + static void addSearchTree(const char *fileName, int priority); + static void addSearchTOC(const char *fileName, int priority); + static bool validateSearchTree(const char *fileName); + static void removeAllSearches(); + + static bool exists(const char *fileName); + static int getFileSize(const char *fileName); + static DLLEXPORT AbstractFile *open(const char *filename, AbstractFile::PriorityType, bool allowFail); + + static void fixUpFileName(const char *fileName, char *outName); + static bool getPathName(const char *fileName, char *pathName, int pathNameLength); + static const char *getShortestExistingPath(const char *fileName); + static bool stripTreeFileSearchPathFromFile(const std::string &inputPath, std::string &outputPath); + static bool stripTreeFileSearchPathFromFile(const char *inputPath, char *outputPath, int outputPathBufferLength); + + + static int getNumberOfSearchPaths(); + static const char *getSearchPath(int index); + + static void addCachedFile(const char *fileName, AbstractFile *file); + static void clearCachedFiles(); + + static int getNumberOfFilesOpenedTotal(); + static int getSizeOfFilesOpenedTotal(); + + static int cacheFile(char const * fileName); + +private: + + typedef stdvector::fwd SearchNodes; + +private: + + /// disabled + TreeFile(); + /// disabled + TreeFile(const TreeFile &); + /// disabled + TreeFile &operator =(const TreeFile &); + + static void addSearchCache(int priority); + static bool searchNodePriorityOrder(const SearchNode *a, const SearchNode *b); + static void addSearchNode(SearchNode *newNode); + static SearchNode *find(const char *fileName); + + static void fixUpFileName(char *output, const char *filename, bool warning); + +private: + + static bool ms_installed; + static bool ms_haveCachedFiles; + static Mutex ms_criticalSection; + static SearchNodes ms_searchNodes; + static SearchCache * ms_searchCache; + + static bool ms_debugReportFlagShowMetrics; + static bool ms_debugReportFlagShowSearchPaths; + static bool ms_debugLogFlag; + static int ms_numberOfFilesOpenedTotal; + static int ms_sizeOfFilesOpenedTotal; + static int ms_unexpectedCacheMisses; +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/TreeFile_SearchNode.cpp b/engine/shared/library/sharedFile/src/shared/TreeFile_SearchNode.cpp new file mode 100644 index 00000000..7596b1d1 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/TreeFile_SearchNode.cpp @@ -0,0 +1,1112 @@ +// ====================================================================== +// +// TreeFile_SearchNode.cpp +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2001-2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/TreeFile_SearchNode.h" + +#include "sharedCompression/Compressor.h" +#include "sharedCompression/ZlibCompressor.h" +#include "sharedDebug/DebugFlags.h" +#include "sharedFile/FileStreamerFile.h" +#include "sharedFile/FileStreamer.h" +#include "sharedFile/MemoryFile.h" +#include "sharedFile/ZlibFile.h" +#include "sharedFoundation/ConfigFile.h" +#include "sharedFoundation/Crc.h" +#include "sharedFoundation/MemoryBlockManager.h" +#include "sharedFoundation/Os.h" +#include "sharedFoundation/PersistentCrcString.h" +#include "sharedFoundation/PointerDeleter.h" +#include "sharedFoundation/Production.h" +#include "sharedFoundation/TemporaryCrcString.h" +#include "sharedSynchronization/Mutex.h" + +#include +#include +#include + +// ====================================================================== + +const Tag TAG_TREE = TAG(T,R,E,E); +const Tag TAG_TOC = TAG3(T,O,C); + +// ====================================================================== + +TreeFile::SearchNode::SearchNode(int priority) +: + m_priority(priority) +{ +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchNode::~SearchNode(void) +{ +} +// ====================================================================== + +TreeFile::SearchPath::SearchPath(int priority, const char *path) +: SearchNode(priority), + m_pathName(NULL), + m_pathNameLength(0) +{ + NOT_NULL(path); + DEBUG_FATAL(!path[0], ("empty path")); + + // convert from relative to absolute path + char absolutePath[Os::MAX_PATH_LENGTH]; + const bool result = Os::getAbsolutePath(path, absolutePath, sizeof(absolutePath)); + FATAL(!result, ("Could not convert to absolute path. Does it exist? %s", path)); + + // clean the path name up and remove any trailing slash + // this function will remove leading slashes but we actually want them, so make sure we preserve it + int const offset = (absolutePath[0] == '/') ? 1 : 0; + TreeFile::fixUpFileName(absolutePath+offset, absolutePath+offset); + m_pathNameLength = strlen(absolutePath); + if (m_pathNameLength && absolutePath[m_pathNameLength-1] == '/') + absolutePath[--m_pathNameLength] = '\0'; + + // copy the path to a safe place + m_pathName = DuplicateString(absolutePath); +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchPath::~SearchPath(void) +{ + delete [] m_pathName; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchPath::debugPrint(void) +{ + DEBUG_REPORT_PRINT(true, (" %d=priority %s=path\n", getPriority(), m_pathName)); + DEBUG_OUTPUT_STATIC_VIEW("Foundation\\Treefile", (" %d=priority %s=path\n", getPriority(), m_pathName)); +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchPath::makeAbsolutePath(const char *fileName, char *buffer) const +{ + NOT_NULL(fileName); + NOT_NULL(buffer); + DEBUG_FATAL(strlen(m_pathName) + 1 + strlen(fileName) + 1 > Os::MAX_PATH_LENGTH, ("file name to long %d/%d", strlen(m_pathName) + strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + + strcpy(buffer, m_pathName); + buffer[m_pathNameLength] = '/'; + strcpy(buffer+m_pathNameLength+1, fileName); +} + +// ---------------------------------------------------------------------- + +bool TreeFile::SearchPath::exists(const char *fileName, bool &) const +{ + char buffer[Os::MAX_PATH_LENGTH]; + makeAbsolutePath(fileName, buffer); + return FileStreamer::exists(buffer); +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchPath::getFileSize(const char *fileName, bool &) const +{ + char buffer[Os::MAX_PATH_LENGTH]; + makeAbsolutePath(fileName, buffer); + return FileStreamer::getFileSize(buffer); +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchPath::getPathName(const char *fileName, char *outPathName, int outPathNameLength) const +{ + UNREF(outPathNameLength); + DEBUG_FATAL(istrlen(m_pathName) + 1 + istrlen(fileName) + 1 > outPathNameLength, ("file name too long %d/%d", strlen(m_pathName) + 1 + strlen(fileName) + 1, outPathNameLength)); + +#ifdef _DEBUG + bool deleted = false; + DEBUG_FATAL(!exists(fileName, deleted), ("file name doesn't exist")); +#endif + + makeAbsolutePath(fileName, outPathName); +} + +// ---------------------------------------------------------------------- + +AbstractFile *TreeFile::SearchPath::open(const char *fileName, AbstractFile::PriorityType priority, bool &) +{ + char buffer[Os::MAX_PATH_LENGTH]; + makeAbsolutePath(fileName, buffer); + FileStreamer::File *file = FileStreamer::open(buffer); + if (!file) + return NULL; + return new FileStreamerFile(priority, *file); +} + +// ====================================================================== + +TreeFile::SearchAbsolute::SearchAbsolute(int priority) +: SearchNode(priority) +{ +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchAbsolute::~SearchAbsolute(void) +{ +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchAbsolute::debugPrint(void) +{ +} + +// ---------------------------------------------------------------------- + +bool TreeFile::SearchAbsolute::exists(const char *fileName, bool &) const +{ + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH, ("Filename too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + return FileStreamer::exists(fileName); +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchAbsolute::getFileSize(const char *fileName, bool &) const +{ + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH, ("Filename too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + return FileStreamer::getFileSize(fileName); +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchAbsolute::getPathName(const char *fileName, char *pathName, int pathNameLength) const +{ + NOT_NULL(fileName); + NOT_NULL(pathName); +#ifdef _DEBUG + bool deleted = false; + DEBUG_FATAL(!exists(fileName, deleted), ("fileName does not exist")); +#endif + + UNREF(pathNameLength); + DEBUG_FATAL(istrlen(fileName) + 1 > pathNameLength, ("Filename too long %d/%d", strlen(fileName) + 1, pathNameLength)); + strcpy(pathName, fileName); +} + +// ---------------------------------------------------------------------- + +AbstractFile *TreeFile::SearchAbsolute::open(const char *fileName, AbstractFile::PriorityType priority, bool &) +{ + NOT_NULL(fileName); + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH, ("Filename too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + + FileStreamer::File *file = FileStreamer::open(fileName); + if (!file) + return NULL; + return new FileStreamerFile(priority, *file); +} + +// ====================================================================== + +bool TreeFile::SearchTree::validate(const char *fileName) +{ + // open the file + FileStreamer::File *file = FileStreamer::open(fileName); + if (!file) + return false; + + // read the header + Header header; + const int readPos = file->read(0, &header, sizeof(header), AbstractFile::PriorityData); + + // close the file + delete file; + + // make sure all the bytes were read + if (readPos != isizeof(header)) + return false; + + // validate the token + if (header.token != TAG_TREE) + return false; + + // validate the version number + if (header.version < TAG_0004 || header.version > TAG_0004) + return false; + + return true; +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchTree::SearchTree(int priority, const char *fileName) +: SearchNode(priority), + m_treeFileName(NULL), + m_treeFile(NULL), + m_version(0), + m_numberOfFiles(0), + m_fileNames(NULL), + m_tableOfContents(NULL) +{ + NOT_NULL(fileName); + + // set to the the current name of the tree file that is fileName + m_treeFileName = DuplicateString(fileName); + + m_treeFile = FileStreamer::open(m_treeFileName, true); + DEBUG_FATAL(!m_treeFile, ("failed to open TreeFile %s", m_treeFileName)); + + // read the header (the first 32 bytes of the tree file) + Header header; + m_treeFile->read(0, &header, sizeof(header), AbstractFile::PriorityData); + DEBUG_FATAL(header.token != TAG_TREE, ("file does not look like a tree file")); + + // set to the number of files that has been compressed within the tree file + m_numberOfFiles = static_cast(header.numberOfFiles); + + int readPosition = header.tocOffset; + m_version = header.version; + switch (m_version) + { + case TAG_0004: + case TAG_0005: + { + m_tableOfContents = new TableOfContentsEntry[m_numberOfFiles]; + m_fileNames = new char [header.uncompSizeOfNameBlock]; + + // prepare table of contents by zeroing out the total size of data to be stored + const int tableOfContentsSize = isizeof(TableOfContentsEntry) * m_numberOfFiles; + memset(m_tableOfContents, 0, tableOfContentsSize); + + if (isCompressed(header.tocCompressor)) + { + // create temp buffer to store the compressed TOC entry data + byte *entryBuffer = new byte[static_cast(header.sizeOfTOC)]; + + // read the compressed table of contents data into buffer + const int bytesRead = m_treeFile->read(readPosition, entryBuffer, header.sizeOfTOC, AbstractFile::PriorityData); + DEBUG_FATAL(bytesRead != static_cast(header.sizeOfTOC), ("failed to read tree file TOC entries")); + readPosition += bytesRead; + + // decompress data into toc + static_cast(ZlibCompressor().expand(entryBuffer, header.sizeOfTOC, m_tableOfContents, tableOfContentsSize)); + + delete [] entryBuffer; + } + else + { + // read the uncompressed table of contents data + const int bytesRead = m_treeFile->read(header.tocOffset, m_tableOfContents, tableOfContentsSize, AbstractFile::PriorityData); + DEBUG_FATAL(bytesRead != tableOfContentsSize, ("failed to read tree file tableOfContents entries")); + readPosition += bytesRead; + } + + if (header.blockCompressor) + { + // create temp buffer to store the compressed name block data + byte *nameBuffer = new byte[static_cast(header.sizeOfNameBlock)]; + + // read the compressed table of contents data into buffer + const int bytesRead = m_treeFile->read(readPosition, nameBuffer, header.sizeOfNameBlock, AbstractFile::PriorityData); + UNREF(bytesRead); + DEBUG_FATAL(bytesRead != static_cast(header.sizeOfNameBlock), ("failed to read tree file name block")); + + // decompress data into tocFileNames + static_cast(ZlibCompressor().expand(nameBuffer, header.sizeOfNameBlock, m_fileNames, header.uncompSizeOfNameBlock)); + + delete [] nameBuffer; + } + else + { + // read the uncompressed name block data + const int bytesRead = m_treeFile->read(readPosition, m_fileNames, header.uncompSizeOfNameBlock, AbstractFile::PriorityData); + UNREF(bytesRead); + DEBUG_FATAL(bytesRead != static_cast(header.uncompSizeOfNameBlock), ("failed to read tree file name block")); + } + } + break; + + default: + { + delete m_treeFile; + +#if PRODUCTION + FATAL(true, ("TreeFile corruption detected. Please do a \"Full Scan\" from the launchpad. (%08x %s)", m_version, m_treeFileName)); +#else + FATAL(true, ("unsupported version %d in %s", m_version, m_treeFileName)); +#endif + + } + break; + } +} + +// ---------------------------------------------------------------------- +/** + * Look for a file in the SearchTree. + * + * If index is passed, the table of contents index is assigned to it. + * + * @return True if found, otherwise false. + */ + +bool TreeFile::SearchTree::localExists(const char *fileName, int *index, bool &deleted) const +{ + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH,("file name too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + + const uint32 crc = Crc::calculate(fileName); + + // try a binary search through the tree file to find the file + bool found = false; + int left = 0; + int right = m_numberOfFiles - 1; + int mid = 0; + while (!found && (left <= right)) + { + mid = (left + right) / 2; + + if (m_tableOfContents[mid].crc < crc) + left = mid + 1; + else + if (m_tableOfContents[mid].crc > crc) + right = mid - 1; + else + { + const int res = _stricmp(m_fileNames + m_tableOfContents[mid].fileNameOffset, fileName); + + if (res < 0) + left = mid + 1; + else + if (res > 0) + right = mid - 1; + else + found = true; + } + } + + // return the found index if desired + if (found) + { + if (m_tableOfContents[mid].length == 0) + { + deleted = true; + return false; + } + + if (index) + *index = mid; + } + + return found; +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchTree::~SearchTree(void) +{ + delete [] m_treeFileName; + delete [] m_tableOfContents; + delete [] m_fileNames; + delete m_treeFile; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchTree::debugPrint(void) +{ + DEBUG_REPORT_PRINT(true, (" %d=priority %s=tree\n", getPriority(), m_treeFileName)); + DEBUG_OUTPUT_STATIC_VIEW("Foundation\\Treefile", (" %d=priority %s=tree\n", getPriority(), m_treeFileName)); +} + +// ---------------------------------------------------------------------- +/** + * Look for a file in the SearchTree. + * + * @return True if found, otherwise false. + */ + +bool TreeFile::SearchTree::exists(const char *fileName, bool &deleted) const +{ + NOT_NULL(fileName); + return localExists(fileName, NULL, deleted); +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchTree::getFileSize(const char *fileName, bool &deleted) const +{ + NOT_NULL(fileName); + int tableOfContentsIndex = 0; + if (!localExists(fileName, &tableOfContentsIndex, deleted)) + return -1; + + return m_tableOfContents[tableOfContentsIndex].length; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchTree::getPathName(const char *fileName, char *pathName, int pathNameLength) const +{ + NOT_NULL(fileName); + NOT_NULL(pathName); + UNREF(pathNameLength); + +#ifdef _DEBUG + bool deleted = false; + DEBUG_FATAL(!exists(fileName, deleted), ("fileName does not exist")); + + int stringLength = istrlen(m_treeFileName) + 1 + istrlen(fileName) + 1 + 1; + DEBUG_FATAL(stringLength > pathNameLength, ("file name to long %d/%d", stringLength, pathNameLength)); +#endif + + // make a pseudo-path name for the tree file + strcpy(pathName, m_treeFileName); + strcat(pathName, "["); + strcat(pathName, fileName); + strcat(pathName, "]"); +} + +// ---------------------------------------------------------------------- + +AbstractFile *TreeFile::SearchTree::open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted) +{ + NOT_NULL(fileName); + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH,("file name too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + + int tableOfContentsIndex = -1; + if (localExists(fileName, &tableOfContentsIndex, deleted)) + { + const TableOfContentsEntry &entry = m_tableOfContents[tableOfContentsIndex]; + + if (!TreeFile::SearchTree::isCompressed(entry.compressor)) + return new FileStreamerFile(priority, *m_treeFile, entry.offset, entry.length); + + byte * compressedBuffer = new byte[entry.compressedLength]; + + const int bytesRead = m_treeFile->read(entry.offset, compressedBuffer, entry.compressedLength, priority); + DEBUG_FATAL(bytesRead != entry.compressedLength, ("error reading compressed data into buffer")); + UNREF(bytesRead); + + return new ZlibFile(entry.length, compressedBuffer, entry.compressedLength, true); + } + + return NULL; +} + +// ====================================================================== + +bool TreeFile::SearchTOC::validate(const char *fileName) +{ + // open the file + FileStreamer::File *file = FileStreamer::open(fileName); + if (!file) + return false; + + // read the header + Header header; + const int readPos = file->read(0, &header, sizeof(header), AbstractFile::PriorityData); + + // close the file + delete file; + + // make sure all the bytes were read + if (readPos != isizeof(header)) + return false; + + // validate the token + if (header.token != TAG_TOC) + return false; + + // validate the version number + if (header.version != TAG_0001) + return false; + + return true; +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchTOC::SearchTOC(int priority, const char *fileName) +: SearchNode(priority), + m_TOCFileName(NULL), + m_TOCFile(NULL), + m_treeFiles(NULL), + m_numberOfFiles(0), + m_treeFileNames(NULL), + m_treeFileNamePointers(NULL), + m_tableOfContents(NULL), + m_fileNames(NULL) +{ + NOT_NULL(fileName); + + // set to the the current name of the TOC file to the fileName + m_TOCFileName = DuplicateString(fileName); + + m_TOCFile = FileStreamer::open(m_TOCFileName, true); + DEBUG_FATAL(!m_TOCFile, ("failed to open TOCFile %s", m_TOCFileName)); + + // read the header + Header header; + m_TOCFile->read(0, &header, sizeof(header), AbstractFile::PriorityData); + DEBUG_FATAL(header.token != TAG_TOC, ("file does not look like a table of contents file")); + + // grab the number of files + m_numberOfFiles = header.numberOfFiles; + + // grab the number of tree files + m_numberOfTreeFiles = header.numberOfTreeFiles; + + // set the read position to after the header (beginning of patch tree names) + int readPosition = isizeof(Header); + uint32 version = header.version; + switch (version) + { + case TAG_0001: + { + m_tableOfContents = new TableOfContentsEntry [m_numberOfFiles]; + m_fileNames = new char [header.uncompSizeOfNameBlock]; + m_treeFileNames = new char [header.sizeOfTreeFileNameBlock]; + m_treeFiles = new FileStreamer::File* [header.numberOfTreeFiles]; + m_treeFileNamePointers = new char* [header.numberOfTreeFiles]; + + { + // get any paths we need to check to open the tree files + std::vector treePaths; + char *treePathBuffer = new char[Os::MAX_PATH_LENGTH]; + + // add on the current path (an empty string) to the list of paths + char *emptyPath = new char('\0'); + treePaths.push_back(emptyPath); + + // add on all paths in config file + const char * result; + for (int index = 0; (result = ConfigFile::getKeyString("SharedFile", "TOCTreePath", index, NULL)) != NULL; ++index) + treePaths.push_back(result); + + // read in the tree file names and open the files + const int bytesRead = m_TOCFile->read(readPosition, m_treeFileNames, header.sizeOfTreeFileNameBlock, AbstractFile::PriorityData); + DEBUG_FATAL(bytesRead != static_cast(header.sizeOfTreeFileNameBlock), ("failed to read tree file name entries")); + readPosition += bytesRead; + + for (int treeFileNameIndex = 0, treeFileNameReadPosition = 0; treeFileNameIndex < static_cast(header.numberOfTreeFiles); treeFileNameIndex++) + { + m_treeFileNamePointers[treeFileNameIndex] = (m_treeFileNames + treeFileNameReadPosition); + m_treeFiles[treeFileNameIndex] = NULL; + + // try to open the tree file in each of the relative paths + for (std::vector::const_iterator pathIter = treePaths.begin(); pathIter != treePaths.end(); ++pathIter) + { + strcpy(treePathBuffer, *pathIter); + strcat(treePathBuffer, (m_treeFileNames + treeFileNameReadPosition)); + + if (FileStreamer::exists (treePathBuffer)) + { + m_treeFiles[treeFileNameIndex] = FileStreamer::open(treePathBuffer, true); + break; + } + } + + FATAL(!m_treeFiles[treeFileNameIndex], ("failed to open tree file index %d, offset %d, name %s", treeFileNameIndex, treeFileNameReadPosition, m_treeFileNames + treeFileNameReadPosition)); + + treeFileNameReadPosition += (strlen(m_treeFileNames + treeFileNameReadPosition) + 1); + } + + delete [] treePathBuffer; + delete emptyPath; + } + + // prepare table of contents by zeroing out the total size of data to be stored + const int tableOfContentsSize = isizeof(TableOfContentsEntry) * m_numberOfFiles; + if (isCompressed(header.tocCompressor)) + { + // create temp buffer to store the compressed TOC entry data + byte *entryBuffer = new byte[header.sizeOfTOC]; + + // read the compressed table of contents data into buffer + const int bytesRead = m_TOCFile->read(readPosition, entryBuffer, header.sizeOfTOC, AbstractFile::PriorityData); + DEBUG_FATAL(bytesRead != static_cast(header.sizeOfTOC), ("failed to read tableOfContents entries")); + readPosition += bytesRead; + + // decompress data into toc + static_cast(ZlibCompressor().expand(entryBuffer, header.sizeOfTOC, m_tableOfContents, tableOfContentsSize)); + + delete [] entryBuffer; + } + else + { + // read the uncompressed table of contents data + const int bytesRead = m_TOCFile->read(readPosition, m_tableOfContents, tableOfContentsSize, AbstractFile::PriorityData); + DEBUG_FATAL(bytesRead != tableOfContentsSize, ("failed to read tableOfContents entries")); + readPosition += bytesRead; + } + + // After the TableOfContents is read into memory, the fileNameLengths must be changed to fileNameOffsets + { + int currentFileNameOffset = 0; + int currentFileNameLength = 0; + + for (uint32 i = 0; i < m_numberOfFiles; ++i) + { + currentFileNameLength = m_tableOfContents[i].fileNameOffset; + m_tableOfContents[i].fileNameOffset = currentFileNameOffset; + // + 1 for the null termination + currentFileNameOffset += (currentFileNameLength + 1); + } + } + + if (isCompressed(header.fileNameBlockCompressor)) + { + // create temp buffer to store the compressed name block data + byte *nameBuffer = new byte[header.uncompSizeOfNameBlock]; + + // read the compressed table of contents data into buffer + const int bytesRead = m_TOCFile->read(readPosition, nameBuffer, header.sizeOfNameBlock, AbstractFile::PriorityData); + UNREF(bytesRead); + DEBUG_FATAL(bytesRead != static_cast(header.sizeOfNameBlock), ("failed to read file name block")); + + // decompress data into tocFileNames + static_cast(ZlibCompressor().expand(nameBuffer, header.sizeOfNameBlock, m_fileNames, header.uncompSizeOfNameBlock)); + + delete [] nameBuffer; + } + else + { + // read the uncompressed name block data + const int bytesRead = m_TOCFile->read(readPosition, m_fileNames, header.sizeOfNameBlock, AbstractFile::PriorityData); + UNREF(bytesRead); + DEBUG_FATAL(bytesRead != static_cast(header.sizeOfNameBlock), ("failed to read file name block")); + } + + } + break; + + default: + { + delete m_TOCFile; + +#if PRODUCTION + FATAL(true, ("Table of Contents File corruption detected. Please do a \"Full Scan\" from the launchpad. (%08x %s)", version, m_TOCFileName)); +#else + FATAL(true, ("unsupported version %d in %s", version, m_TOCFileName)); +#endif + + } + break; + } + +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchTOC::~SearchTOC(void) +{ + delete [] m_TOCFileName; + delete [] m_treeFileNames; + delete [] m_tableOfContents; + delete [] m_fileNames; + delete [] m_treeFileNamePointers; + + // clear out FileStreamer::File pointers + for (uint32 i = 0; i < m_numberOfTreeFiles; i++) + delete m_treeFiles[i]; + delete [] m_treeFiles; + + delete m_TOCFile; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchTOC::debugPrint(void) +{ + DEBUG_REPORT_PRINT(true, (" %d=priority %s=tree\n", getPriority(), m_TOCFileName)); + DEBUG_OUTPUT_STATIC_VIEW("Foundation\\TOCfile", (" %d=priority %s=tree\n", getPriority(), m_TOCFileName)); +} + +// ---------------------------------------------------------------------- + +bool TreeFile::SearchTOC::localExists(const char *fileName, int *index) const +{ + NOT_NULL(fileName); + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH,("file name too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + + const uint32 crc = Crc::calculate(fileName); + + // try a binary search through the tree file to find the file + bool found = false; + int left = 0; + int right = m_numberOfFiles - 1; + int mid = 0; + while (!found && (left <= right)) + { + mid = (left + right) / 2; + + if (m_tableOfContents[mid].crc < crc) + left = mid + 1; + else + if (m_tableOfContents[mid].crc > crc) + right = mid - 1; + else + { + const int res = _stricmp(m_fileNames + m_tableOfContents[mid].fileNameOffset, fileName); + + if (res < 0) + left = mid + 1; + else + if (res > 0) + right = mid - 1; + else + found = true; + } + } + + // return the found index if desired + if (found) + { + if (m_tableOfContents[mid].length == 0) + { + return false; + } + else if (m_tableOfContents[mid].offset == 0) + { + // sanity check - if the file has a length, but the offset is zero, the pointer in the TOC is invalid + return false; + } + + if (index) + *index = mid; + } + + return found; +} + +// ---------------------------------------------------------------------- + +bool TreeFile::SearchTOC::exists(const char *fileName, bool &deleted) const +{ + NOT_NULL(fileName); + deleted = false; + return localExists(fileName, NULL); +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchTOC::getFileSize(const char *fileName, bool &deleted) const +{ + NOT_NULL(fileName); + deleted = false; + int tableOfContentsIndex = 0; + if (!localExists(fileName, &tableOfContentsIndex)) + return -1; + + return m_tableOfContents[tableOfContentsIndex].length; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchTOC::getPathName(const char *fileName, char *pathName, int pathNameLength) const +{ + NOT_NULL(fileName); + NOT_NULL(pathName); + UNREF(pathNameLength); + + // make a pseudo-path name for the tree file + int tableOfContentsIndex = 0; + if(!localExists(fileName, &tableOfContentsIndex)) + return; + + char* treeFileName = m_treeFileNamePointers[m_tableOfContents[tableOfContentsIndex].treeFileIndex]; + +#ifdef _DEBUG + bool deleted; + DEBUG_FATAL(!exists(fileName, deleted), ("fileName does not exist")); + + int stringLength = istrlen(treeFileName) + 1 + istrlen(fileName) + 1 + 1; + DEBUG_FATAL(stringLength > pathNameLength, ("file name to long %d/%d", stringLength, pathNameLength)); +#endif + + strcpy(pathName, treeFileName); + strcat(pathName, "["); + strcat(pathName, fileName); + strcat(pathName, "]"); +} + +// ---------------------------------------------------------------------- + +AbstractFile *TreeFile::SearchTOC::open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted) +{ + NOT_NULL(fileName); + DEBUG_FATAL(strlen(fileName) + 1 > Os::MAX_PATH_LENGTH,("file name too long %d/%d", strlen(fileName) + 1, Os::MAX_PATH_LENGTH)); + deleted = false; + + int tableOfContentsIndex = -1; + if (localExists(fileName, &tableOfContentsIndex)) + { + const TableOfContentsEntry &entry = m_tableOfContents[tableOfContentsIndex]; + + if (!isCompressed(entry.compressor)) + return new FileStreamerFile(priority, *m_treeFiles[entry.treeFileIndex], entry.offset, entry.length); + + byte * compressedBuffer = new byte[entry.compressedLength]; + + const uint32 bytesRead = m_treeFiles[entry.treeFileIndex]->read(entry.offset, compressedBuffer, entry.compressedLength, priority); + DEBUG_FATAL(bytesRead != entry.compressedLength, ("error reading compressed data into buffer")); + UNREF(bytesRead); + + return new ZlibFile(entry.length, compressedBuffer, entry.compressedLength, true); + } + + return NULL; +} + +// ====================================================================== + +class TreeFile::SearchCache::CachedFile +{ +public: + + explicit CachedFile(char const * fileName, AbstractFile * abstractFile); + ~CachedFile(); + + CrcString const * getCrcString () const; + bool getCompressed() const; + int getUncompressedLength() const; + int getCompressedLength() const; + AbstractFile * createAbstractFile() const; + +private: + + CachedFile(); + CachedFile(CachedFile const &); + CachedFile & operator= (CachedFile const &); + +private: + + PersistentCrcString const m_name; + + byte * m_buffer; + int m_length; + bool m_compressed; + int m_uncompressedLength; +}; + +// ---------------------------------------------------------------------- + +TreeFile::SearchCache::CachedFile::CachedFile(char const * fileName, AbstractFile * abstractFile) : + m_name(fileName, true), + m_buffer(0), + m_length(0), + m_compressed(false), + m_uncompressedLength(abstractFile->length()) +{ + if (abstractFile->isZlibCompressed()) + { + abstractFile->getZlibCompressedDataAndClose(m_buffer, m_length); + m_compressed = true; + } + else + { + m_buffer = abstractFile->readEntireFileAndClose(); + m_length = m_uncompressedLength; + } +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchCache::CachedFile::~CachedFile() +{ + delete [] m_buffer; + m_buffer = 0; +} + +// ---------------------------------------------------------------------- + +CrcString const * TreeFile::SearchCache::CachedFile::getCrcString () const +{ + return &m_name; +} + +// ---------------------------------------------------------------------- + +bool TreeFile::SearchCache::CachedFile::getCompressed() const +{ + return m_compressed; +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchCache::CachedFile::getUncompressedLength() const +{ + return m_compressed ? m_uncompressedLength : m_length; +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchCache::CachedFile::getCompressedLength() const +{ + return m_length; +} + +// ---------------------------------------------------------------------- + +AbstractFile * TreeFile::SearchCache::CachedFile::createAbstractFile() const +{ + if (m_compressed) + { + return new ZlibFile(m_uncompressedLength, m_buffer, m_length, false); + } + else + { + byte * const uncompressedData = new byte[m_length]; + memcpy(uncompressedData, m_buffer, m_length); + return new MemoryFile(uncompressedData, m_length); + } +} + +// ====================================================================== + +namespace TreeFileSearchCacheNamespace +{ + bool ms_logSearchCache; +} + +using namespace TreeFileSearchCacheNamespace; + +// ---------------------------------------------------------------------- + +TreeFile::SearchCache::SearchCache(int const priority) : + SearchNode(priority), + m_cachedFileMap(new CachedFileMap) +{ +#if PRODUCTION == 0 + DebugFlags::registerFlag(ms_logSearchCache, "SharedFile", "logSearchCache"); +#endif +} + +// ---------------------------------------------------------------------- + +TreeFile::SearchCache::~SearchCache() +{ + std::for_each (m_cachedFileMap->begin(), m_cachedFileMap->end(), PointerDeleterPairSecond()); + m_cachedFileMap->clear(); + delete m_cachedFileMap; + +#if PRODUCTION == 0 + DebugFlags::unregisterFlag(ms_logSearchCache); +#endif +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchCache::addCachedFile(char const * const fileName) +{ + bool deleted = false; + int fileSize = 0; + if (exists(fileName, deleted)) + DEBUG_WARNING(true, ("Skipping existing cached file %s", fileName)); + else + { + AbstractFile * const abstractFile = TreeFile::open(fileName, AbstractFile::PriorityData, false); + CachedFile * const cachedFile = new CachedFile(fileName, abstractFile); + fileSize = cachedFile->getCompressedLength(); + delete abstractFile; + + bool const result = (m_cachedFileMap->insert(CachedFileMap::value_type (cachedFile->getCrcString(), cachedFile))).second; + if (result) + { +#if PRODUCTION == 0 + if (ms_logSearchCache) + { + if (cachedFile->getCompressed()) + REPORT_LOG(true, ("Adding cached file %s, compressed, %i/%i bytes\n", fileName, cachedFile->getCompressedLength(), cachedFile->getUncompressedLength())); + else + REPORT_LOG(true, ("Adding cached file %s, uncompressed, %i bytes\n", fileName, cachedFile->getUncompressedLength())); + } +#endif + } + else + { + DEBUG_FATAL(!result, ("TreeFile::SearchCache::addCachedFile: insert failed for %s (possibly a duplicate entry)", fileName)); + delete cachedFile; + } + } + + return fileSize; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchCache::debugPrint() +{ + DEBUG_REPORT_PRINT(true, (" %d=priority SearchCache [%i]\n", getPriority(), m_cachedFileMap->size())); + DEBUG_OUTPUT_STATIC_VIEW("Foundation\\Treefile", (" %d=priority SearchCache [%i]\n", getPriority(), m_cachedFileMap->size())); +} + +// ---------------------------------------------------------------------- + +bool TreeFile::SearchCache::exists(char const * const fileName, bool & deleted) const +{ + deleted = false; + + TemporaryCrcString const crcString(fileName, true); + return m_cachedFileMap->find(&crcString) != m_cachedFileMap->end(); +} + +// ---------------------------------------------------------------------- + +int TreeFile::SearchCache::getFileSize(char const * const fileName, bool & deleted) const +{ + deleted = false; + + TemporaryCrcString const crcString(fileName, true); + CachedFileMap::iterator iter = m_cachedFileMap->find(&crcString); + if (iter != m_cachedFileMap->end()) + return iter->second->getUncompressedLength(); + + return -1; +} + +// ---------------------------------------------------------------------- + +void TreeFile::SearchCache::getPathName(char const * const fileName, char * const pathName, int const pathNameLength) const +{ + NOT_NULL(fileName); + NOT_NULL(pathName); + UNREF(pathNameLength); + +#ifdef _DEBUG + bool deleted = false; + DEBUG_FATAL(!exists(fileName, deleted), ("fileName does not exist")); + + int const stringLength = 13 + istrlen(fileName) + 1 + 1; + DEBUG_FATAL(stringLength > pathNameLength, ("file name to long %d/%d", stringLength, pathNameLength)); +#endif + + // make a pseudo-path name for the tree file + strcpy(pathName, "SearchCache["); + strcat(pathName, fileName); + strcat(pathName, "]"); +} + +// ---------------------------------------------------------------------- + +AbstractFile * TreeFile::SearchCache::open(char const * const fileName, AbstractFile::PriorityType const, bool & deleted) +{ + deleted = false; + + TemporaryCrcString const crcString(fileName, true); + CachedFileMap::iterator iter = m_cachedFileMap->find(&crcString); + if (iter != m_cachedFileMap->end()) + return iter->second->createAbstractFile(); + + return 0; +} + +// ====================================================================== + diff --git a/engine/shared/library/sharedFile/src/shared/TreeFile_SearchNode.h b/engine/shared/library/sharedFile/src/shared/TreeFile_SearchNode.h new file mode 100644 index 00000000..798f85c8 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/TreeFile_SearchNode.h @@ -0,0 +1,346 @@ +// ====================================================================== +// +// TreeFile_SearchNode.h +// Portions copyright 1998 Bootprint Entertainment +// Portions copyright 2001-2002 Sony Online Entertainment +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_TreeFile_SearchNode_H +#define INCLUDED_TreeFile_SearchNode_H + +// ====================================================================== + +class CrcString; +class MemoryBlockManager; + +#include "sharedFile/TreeFile.h" +#include "sharedFile/FileStreamer.h" +#include "sharedFoundation/LessPointerComparator.h" +#include "sharedFoundation/Os.h" +#include "sharedFoundation/Tag.h" + +// ====================================================================== + +/* This module defines the inner classes of Treefile, to help keep TreeFile.h clean and easy to read. + Since all of these classes are private, only TreeFile.h/cpp needs to even know these files exist. +*/ + +class TreeFile::SearchNode +{ +public: + + explicit SearchNode(int priority); + virtual ~SearchNode(); + + int getPriority() const; + + virtual void debugPrint() = 0; + virtual bool exists(const char *fileName, bool &deleted) const = 0; + virtual int getFileSize(const char *fileName, bool &deleted) const = 0; + virtual void getPathName(const char *fileName, char *pathName, int pathNameLength) const = 0; + virtual AbstractFile *open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted) = 0; + +private: + + SearchNode(); + SearchNode(const SearchNode &); + SearchNode &operator =(const SearchNode &); + +private: + + const int m_priority; +}; + +// ====================================================================== + +inline int TreeFile::SearchNode::getPriority() const +{ + return m_priority; +} + +// ====================================================================== + +class TreeFile::SearchPath : public TreeFile::SearchNode +{ +public: + + SearchPath(int priority, const char *path); + virtual ~SearchPath(); + + virtual void debugPrint(); + virtual bool exists(const char *fileName, bool &deleted) const; + virtual int getFileSize(const char *fileName, bool &deleted) const; + virtual void getPathName(const char *fileName, char *pathName, int pathNameLength) const; + virtual AbstractFile *open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted); + + const char *getPathName() const; //lint !e1411 // Warning -- member with different signature hides virtual member (bug in PC-Lint, incorrect warning) + +private: + + SearchPath(); + SearchPath(const SearchPath &); + SearchPath &operator =(const SearchPath &); + + void makeAbsolutePath(const char *fileName, char *buffer) const; + +private: + + char *m_pathName; + int m_pathNameLength; +}; + +// ====================================================================== + +inline const char *TreeFile::SearchPath::getPathName() const +{ + return m_pathName; +} + +// ====================================================================== + +class TreeFile::SearchAbsolute : public TreeFile::SearchNode +{ +public: + + explicit SearchAbsolute(int priority); + virtual ~SearchAbsolute(); + + virtual void debugPrint(); + virtual bool exists(const char *fileName, bool &deleted) const; + virtual int getFileSize(const char *fileName, bool &deleted) const; + virtual void getPathName(const char *fileName, char *pathName, int pathNameLength) const; + virtual AbstractFile *open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted); + +private: + + SearchAbsolute(); + SearchAbsolute(const SearchAbsolute &); + SearchAbsolute &operator =(const SearchAbsolute &); +}; + +// ====================================================================== + +class TreeFile::SearchTree : public TreeFile::SearchNode +{ + // these friend classes are from the treefile tool + friend class TOC; + friend class FileEntry; + friend class TreeFileBuilder; + friend class TreeFileExtractor; + +public: + + static bool validate(const char *fileName); + static bool isCompressed(int compressorIndex); + +public: + + SearchTree(int priority, const char *fileName); + virtual ~SearchTree(); + + virtual void debugPrint(); + virtual bool exists(const char *fileName, bool &deleted) const; + virtual int getFileSize(const char *fileName, bool &deleted) const; + virtual void getPathName(const char *fileName, char *pathName, int pathNameLength) const; + virtual AbstractFile *open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted); + +private: + + // disabled + SearchTree(); + + // disabled + SearchTree(const SearchTree &); + + // disabled + SearchTree &operator =(const SearchTree &); + +private: + + bool localExists(const char *fileName, int *index, bool &deleted) const; + +private: + + enum CompressorType + { + CT_none, + CT_deprecated, + CT_zlib, + CT_max + }; + + struct Header + { + Tag token; + Tag version; + uint32 numberOfFiles; + uint32 tocOffset; + uint32 tocCompressor; + uint32 sizeOfTOC; + uint32 blockCompressor; + uint32 sizeOfNameBlock; + uint32 uncompSizeOfNameBlock; + }; + +public: + + struct TableOfContentsEntry + { + uint32 crc; + int length; + int offset; + int compressor; + int compressedLength; + int fileNameOffset; + }; + +private: + + char *m_treeFileName; + FileStreamer::File *m_treeFile; + uint32 m_version; + int m_numberOfFiles; + char *m_fileNames; + TableOfContentsEntry *m_tableOfContents; +}; + +// ====================================================================== + +inline bool TreeFile::SearchTree::isCompressed(int compressorIndex) +{ + DEBUG_FATAL(compressorIndex == CT_deprecated, ("No longer supported compressor")); + return compressorIndex != static_cast(CT_none); +} + +// ====================================================================== + +class TreeFile::SearchTOC : public TreeFile::SearchNode +{ +public: + + static bool validate(const char *fileName); + static bool isCompressed(int compressorIndex); + +public: + + SearchTOC(int priority, const char *fileName); + virtual ~SearchTOC(); + + virtual void debugPrint(); + virtual bool exists(const char *fileName, bool &deleted) const; + virtual int getFileSize(const char *fileName, bool &deleted) const; + virtual void getPathName(const char *fileName, char *pathName, int pathNameLength) const; + virtual AbstractFile *open(const char *fileName, AbstractFile::PriorityType priority, bool &deleted); + +private: + + // disabled + SearchTOC(); + + // disabled + SearchTOC(const SearchTOC &); + + // disabled + SearchTOC &operator =(const SearchTOC &); + +private: + + bool TreeFile::SearchTOC::localExists(const char *fileName, int *index) const; + +private: + + enum CompressorType + { + CT_none, + CT_deprecated, + CT_zlib, + CT_max + }; + + struct Header + { + Tag token; + Tag version; + uint8 tocCompressor; + uint8 fileNameBlockCompressor; + uint8 unusedOne; + uint8 unusedTwo; + uint32 numberOfFiles; + uint32 sizeOfTOC; + uint32 sizeOfNameBlock; + uint32 uncompSizeOfNameBlock; + uint32 numberOfTreeFiles; + uint32 sizeOfTreeFileNameBlock; + }; + +public: + + // unused entry is there to verify 32 bit word alignment + struct TableOfContentsEntry + { + uint8 compressor; + uint8 unused; + uint16 treeFileIndex; + uint32 crc; + uint32 fileNameOffset; + uint32 offset; + uint32 length; + uint32 compressedLength; + }; + +private: + + char *m_TOCFileName; + FileStreamer::File *m_TOCFile; + FileStreamer::File **m_treeFiles; + uint32 m_numberOfTreeFiles; + uint32 m_numberOfFiles; + char *m_treeFileNames; + char **m_treeFileNamePointers; + TableOfContentsEntry *m_tableOfContents; + char *m_fileNames; +}; + +// ====================================================================== + +inline bool TreeFile::SearchTOC::isCompressed(int compressorIndex) +{ + DEBUG_FATAL(compressorIndex == CT_deprecated, ("No longer supported compressor")); + return compressorIndex != static_cast(CT_none); +} + +// ====================================================================== + +class TreeFile::SearchCache : public TreeFile::SearchNode +{ +public: + + explicit SearchCache(int priority); + virtual ~SearchCache(); + + int addCachedFile(char const * fileName); + + virtual void debugPrint(); + virtual bool exists(char const * fileName, bool & deleted) const; + virtual int getFileSize(char const * fileName, bool & deleted) const; + virtual void getPathName(char const * fileName, char * pathName, int pathNameLength) const; + virtual AbstractFile *open(char const * fileName, AbstractFile::PriorityType priority, bool & deleted); + +private: + + SearchCache(); + SearchCache(const SearchCache &); + SearchCache &operator =(const SearchCache &); + +private: + + class CachedFile; + typedef stdmap::fwd CachedFileMap; + CachedFileMap * const m_cachedFileMap; +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/shared/ZlibFile.cpp b/engine/shared/library/sharedFile/src/shared/ZlibFile.cpp new file mode 100644 index 00000000..7c3778c7 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/ZlibFile.cpp @@ -0,0 +1,213 @@ +// ====================================================================== +// +// ZlibFile.cpp +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/ZlibFile.h" + +#include "sharedFoundation/ExitChain.h" +#include "sharedFoundation/MemoryBlockManager.h" +#include "sharedCompression/ZlibCompressor.h" +#include "sharedFile/MemoryFile.h" + +// ====================================================================== + +MemoryBlockManager *ZlibFile::ms_memoryBlockManager; + +// ====================================================================== + +void ZlibFile::install() +{ + DEBUG_FATAL(ms_memoryBlockManager, ("ZlibFile::install already installed")); + ms_memoryBlockManager = new MemoryBlockManager("ZlibFile::memoryBlockManager", true, sizeof(ZlibFile), 0, 0, 0); + ExitChain::add(&remove, ("ZlibFile::remove")); +} + +// ---------------------------------------------------------------------- + +void ZlibFile::remove() +{ + DEBUG_FATAL(!ms_memoryBlockManager,("ZlibFile is not installed")); + + delete ms_memoryBlockManager; + ms_memoryBlockManager = 0; +} + +// ---------------------------------------------------------------------- + +void *ZlibFile::operator new(size_t size) +{ + DEBUG_FATAL(!ms_memoryBlockManager,("ZlibFile is not installed")); + + // do not try to alloc a descendant class with this allocator + DEBUG_FATAL(size != sizeof(ZlibFile),("bad size")); + UNREF(size); + + return ms_memoryBlockManager->allocate(); +} + +// ---------------------------------------------------------------------- + +void ZlibFile::operator delete(void *pointer) +{ + DEBUG_FATAL(!ms_memoryBlockManager,("ZlibFile is not installed")); + ms_memoryBlockManager->free(pointer); +} + +// ====================================================================== + +ZlibFile::ZlibFile(int uncompressedLength, byte *compressedBuffer, int compressedBufferLength, bool ownsCompressedBuffer) +: AbstractFile(PriorityData), + m_uncompressedLength(uncompressedLength), + m_compressedBuffer(compressedBuffer), + m_compressedBufferLength(compressedBufferLength), + m_ownsCompressedBuffer(ownsCompressedBuffer), + m_decompressedMemoryFile(NULL) +{ +} + +// ---------------------------------------------------------------------- + +ZlibFile::~ZlibFile() +{ + close(); +} + +// ---------------------------------------------------------------------- + +bool ZlibFile::isOpen() const +{ + return m_compressedBuffer || m_decompressedMemoryFile; +} + +// ---------------------------------------------------------------------- + +int ZlibFile::length() const +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + return m_uncompressedLength; +} + +// ---------------------------------------------------------------------- + +int ZlibFile::tell() const +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + if (!m_decompressedMemoryFile) + return 0; + + return m_decompressedMemoryFile->tell(); +} + +// ---------------------------------------------------------------------- + +bool ZlibFile::seek(SeekType seekType, int offset) +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + if (!m_decompressedMemoryFile) + createDecompressedMemoryFile(); + return m_decompressedMemoryFile->seek(seekType, offset); +} + +// ---------------------------------------------------------------------- + +int ZlibFile::read(void *destinationBuffer, int numberOfBytes) +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + if (!m_decompressedMemoryFile) + createDecompressedMemoryFile(); + return m_decompressedMemoryFile->read(destinationBuffer, numberOfBytes); +} + +// ---------------------------------------------------------------------- + +int ZlibFile::write(int, const void *) +{ + DEBUG_FATAL(true, ("writing to a zlib file is not supported")); + return 0; +} + +// ---------------------------------------------------------------------- + +void ZlibFile::close() +{ + if (m_ownsCompressedBuffer) + delete [] m_compressedBuffer; + m_compressedBuffer = NULL; + + delete m_decompressedMemoryFile; + m_decompressedMemoryFile = NULL; +} + +// ---------------------------------------------------------------------- + +byte * ZlibFile::decompress() const +{ + byte * uncompressedBuffer = new byte[m_uncompressedLength]; + + static_cast(ZlibCompressor().expand(m_compressedBuffer, m_compressedBufferLength, uncompressedBuffer, m_uncompressedLength)); + + return uncompressedBuffer; +} + +// ---------------------------------------------------------------------- + +void ZlibFile::createDecompressedMemoryFile() +{ + MemoryFile * memoryFile = new MemoryFile(decompress(), m_uncompressedLength); + close(); + m_decompressedMemoryFile = memoryFile; +} + +// ---------------------------------------------------------------------- + +byte *ZlibFile::readEntireFileAndClose() +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + byte *result = decompress(); + close(); + return result; +} + +// ---------------------------------------------------------------------- + +bool ZlibFile::isZlibCompressed() const +{ + return true; +} + +// ---------------------------------------------------------------------- + +int ZlibFile::getZlibCompressedLength() const +{ + return m_compressedBufferLength; +} + +// ---------------------------------------------------------------------- + +void ZlibFile::getZlibCompressedDataAndClose(byte *& compressedBuffer, int & compressedBufferLength) +{ + DEBUG_FATAL(!isOpen(), ("file is not open")); + DEBUG_FATAL(m_decompressedMemoryFile, ("cannot get compressed data after another non-compressed function has been used")); + + if (m_ownsCompressedBuffer) + { + compressedBuffer = m_compressedBuffer; + m_compressedBuffer = NULL; + compressedBufferLength = m_compressedBufferLength; + } + else + { + compressedBuffer = new byte[m_compressedBufferLength]; + memcpy(compressedBuffer, m_compressedBuffer, m_compressedBufferLength); + compressedBufferLength = m_compressedBufferLength; + } + + close(); +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/shared/ZlibFile.h b/engine/shared/library/sharedFile/src/shared/ZlibFile.h new file mode 100644 index 00000000..100db855 --- /dev/null +++ b/engine/shared/library/sharedFile/src/shared/ZlibFile.h @@ -0,0 +1,76 @@ +// ====================================================================== +// +// ZlibFile.h +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_ZlibFile_H +#define INCLUDED_ZlibFile_H + +// ====================================================================== + +class MemoryBlockManager; +class MemoryFile; + +#include "fileInterface/AbstractFile.h" + +// ====================================================================== + +class ZlibFile : public AbstractFile +{ +public: + + static void install(); + static void *operator new(size_t size); + static void operator delete(void* pointer); + +public: + + ZlibFile(int uncompressedLength, byte *compressedBuffer, int compressedLength, bool ownsCompressedBuffer); + virtual ~ZlibFile(); + + virtual bool isOpen() const; + virtual int length() const; + virtual int tell() const; + virtual bool seek(SeekType seekType, int offset); + virtual int read(void *destinationBuffer, int numberOfBytes); + virtual int write(int numberOfBytes, const void *sourceBuffer); + virtual void close(); + + virtual byte *readEntireFileAndClose(); + + virtual bool isZlibCompressed() const; + virtual int getZlibCompressedLength() const; + virtual void getZlibCompressedDataAndClose(byte *& compressedBuffer, int & compressedBufferLength); + +private: + + static void remove(); + +private: + + ZlibFile(); + ZlibFile(const ZlibFile &); + ZlibFile &operator =(const ZlibFile &); + + byte * decompress() const; + void createDecompressedMemoryFile(); + +private: + + static MemoryBlockManager *ms_memoryBlockManager; + +private: + + const int m_uncompressedLength; + byte *m_compressedBuffer; + const int m_compressedBufferLength; + bool m_ownsCompressedBuffer; + mutable MemoryFile *m_decompressedMemoryFile; +}; + +// ====================================================================== + +#endif diff --git a/engine/shared/library/sharedFile/src/win32/FirstSharedFile.cpp b/engine/shared/library/sharedFile/src/win32/FirstSharedFile.cpp new file mode 100644 index 00000000..038a7931 --- /dev/null +++ b/engine/shared/library/sharedFile/src/win32/FirstSharedFile.cpp @@ -0,0 +1,8 @@ +// ====================================================================== +// +// FirstFile.cpp +// copyright (c) 2001 Sony Online Entertainment +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" diff --git a/engine/shared/library/sharedFile/src/win32/OsFile.cpp b/engine/shared/library/sharedFile/src/win32/OsFile.cpp new file mode 100644 index 00000000..64acc1a7 --- /dev/null +++ b/engine/shared/library/sharedFile/src/win32/OsFile.cpp @@ -0,0 +1,195 @@ +// ====================================================================== +// +// OsFile.cpp +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#include "sharedFile/FirstSharedFile.h" +#include "sharedFile/OsFile.h" + +#ifdef _DEBUG +#include "sharedDebug/PerformanceTimer.h" +#endif + +namespace OsFileNamespace +{ + float ms_time; +} +using namespace OsFileNamespace; + +// ====================================================================== + +void OsFile::install() +{ + +} + +// ---------------------------------------------------------------------- + +float OsFile::getSpentTime() +{ + float const result = ms_time; + ms_time = 0.0f; + return result; +} + +// ---------------------------------------------------------------------- + +bool OsFile::exists(const char *fileName) +{ + NOT_NULL(fileName); + DWORD attributes = GetFileAttributes(fileName); + +#if _MSC_VER < 1300 + const DWORD INVALID_FILE_ATTRIBUTES = 0xffffffff; +#endif + return (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0); +} + +// ---------------------------------------------------------------------- + +int OsFile::getFileSize(const char *fileName) +{ + NOT_NULL(fileName); + +#ifdef _DEBUG + PerformanceTimer t; + t.start(); +#endif + + HANDLE handle = CreateFile(fileName, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle == INVALID_HANDLE_VALUE) + return -1; + + int const size = GetFileSize(handle, NULL); + CloseHandle(handle); + +#ifdef _DEBUG + t.stop(); + ms_time += t.getElapsedTime(); +#endif + return size; +} + +// ---------------------------------------------------------------------- + +OsFile *OsFile::open(const char *fileName, bool randomAccess) +{ +#ifdef _DEBUG + PerformanceTimer t; + t.start(); +#endif + + // attempt to open the file + HANDLE handle = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | (randomAccess ? FILE_FLAG_RANDOM_ACCESS : 0), NULL); + + // check to make sure the file opened sucessfully + if (handle == INVALID_HANDLE_VALUE) + return NULL; + +#ifdef _DEBUG + t.stop(); + ms_time += t.getElapsedTime(); +#endif + + return new OsFile(handle); +} + +// ---------------------------------------------------------------------- + +OsFile::OsFile(HANDLE handle) +: m_handle(handle), + m_offset(0) +{ +} + +// ---------------------------------------------------------------------- + +OsFile::~OsFile() +{ +#ifdef _DEBUG + PerformanceTimer t; + t.start(); +#endif + + CloseHandle(m_handle); + +#ifdef _DEBUG + t.stop(); + ms_time+= t.getElapsedTime(); +#endif +} + +// ---------------------------------------------------------------------- + +int OsFile::length() const +{ + return GetFileSize(m_handle, NULL); +} + +// ---------------------------------------------------------------------- + +void OsFile::seek(int newFilePosition) +{ +#ifdef _DEBUG + PerformanceTimer t; + t.start(); +#endif + + if (m_offset != newFilePosition) + { + const DWORD result = SetFilePointer(m_handle, newFilePosition, NULL, FILE_BEGIN); + DEBUG_FATAL(static_cast(result) != newFilePosition, ("SetFilePointer failed")); + UNREF(result); + m_offset = newFilePosition; + } + +#ifdef _DEBUG + t.stop(); + ms_time += t.getElapsedTime(); +#endif +} + +// ---------------------------------------------------------------------- + +int OsFile::read(void *destinationBuffer, int numberOfBytes) +{ +#ifdef _DEBUG + PerformanceTimer t; + t.start(); +#endif + + DWORD amountReadDword; + BOOL result = ReadFile(m_handle, destinationBuffer, static_cast(numberOfBytes), &amountReadDword, NULL); + +// miles crasher hack +#if 0 + FATAL(!result, ("FileStreamerThread::processRead ReadFile failed to read '%d' bytes with error '%d'", static_cast(numberOfBytes), GetLastError())); +#else + if(!result) + { + if(GetLastError() == 998) // access violation - buffer coming from miles hosed + { + WARNING(true,("FileStreamerThread::processRead ReadFile failed to read '%d' bytes with error '%d'", static_cast(numberOfBytes), GetLastError())); + return 0; + } + else + { + FATAL(true, ("FileStreamerThread::processRead ReadFile failed to read '%d' bytes with error '%d'", static_cast(numberOfBytes), GetLastError())); + } + } +#endif +// end miles crasher hack + +#ifdef _DEBUG + t.stop(); + ms_time += t.getElapsedTime(); +#endif + + m_offset += static_cast(amountReadDword); + return static_cast(amountReadDword); +} + +// ====================================================================== diff --git a/engine/shared/library/sharedFile/src/win32/OsFile.h b/engine/shared/library/sharedFile/src/win32/OsFile.h new file mode 100644 index 00000000..1f3e6ba9 --- /dev/null +++ b/engine/shared/library/sharedFile/src/win32/OsFile.h @@ -0,0 +1,48 @@ +// ====================================================================== +// +// OsFile.h +// Copyright 2002, Sony Online Entertainment Inc. +// All Rights Reserved. +// +// ====================================================================== + +#ifndef INCLUDED_OsFile_H +#define INCLUDED_OsFile_H + +// ====================================================================== + +class OsFile +{ +public: + + static void install(); + + static float getSpentTime(); + + static bool exists(const char *fileName); + static int getFileSize(const char *fileName); + static OsFile *open(const char *fileName, bool randomAccess=false); + +public: + + ~OsFile(); + + int length() const; + int tell() const; + void seek(int newFilePosition); + int read(void *destinationBuffer, int numberOfBytes); + +private: + + OsFile(HANDLE handle); + OsFile(const char *fileName); + +private: + + HANDLE m_handle; + int m_offset; +}; + +// ====================================================================== + +#endif