diff --git a/public/scenefilecache/SceneImageFile.h b/public/scenefilecache/SceneImageFile.h index 26cce48..99d099c 100644 --- a/public/scenefilecache/SceneImageFile.h +++ b/public/scenefilecache/SceneImageFile.h @@ -14,7 +14,7 @@ #include "tier1/checksum_crc.h" #define SCENE_IMAGE_ID MAKEID( 'V','S','I','F' ) -#define SCENE_IMAGE_VERSION 2 +#define SCENE_IMAGE_VERSION 3 // scene summary: cached calcs for commmon startup queries, variable sized struct SceneImageSummary_t diff --git a/scenefilecache/SceneFileCache.cpp b/scenefilecache/SceneFileCache.cpp new file mode 100644 index 0000000..ce208ff --- /dev/null +++ b/scenefilecache/SceneFileCache.cpp @@ -0,0 +1,335 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Cache for VCDs. PC async loads and uses the datacache to manage. +// 360 uses a baked resident image of aggregated compiled VCDs. +// +//============================================================================= + +#include "scenefilecache/ISceneFileCache.h" +#include "filesystem.h" +#include "tier1/utldict.h" +#include "tier1/utlbuffer.h" +#include "tier1/lzmaDecoder.h" +#include "scenefilecache/SceneImageFile.h" +#include "choreoscene.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IFileSystem *filesystem = NULL; + +bool IsBufferBinaryVCD( char *pBuffer, int bufferSize ) +{ + if ( bufferSize > 4 && *(int *)pBuffer == SCENE_BINARY_TAG ) + { + return true; + } + + return false; +} + +class CSceneFileCache : public CBaseAppSystem< ISceneFileCache > +{ +public: + // IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // ISceneFileCache + // Physically reloads image from disk + virtual void Reload(); + + virtual size_t GetSceneBufferSize( char const *pFilename ); + virtual bool GetSceneData( char const *pFilename, byte *buf, size_t bufsize ); + + // alternate resident image implementation + virtual bool GetSceneCachedData( char const *pFilename, SceneCachedData_t *pData ); + virtual short GetSceneCachedSound( int iScene, int iSound ); + virtual const char *GetSceneString( short stringId ); + +private: + // alternate implementation - uses a resident baked image of the file cache, contains all the compiled VCDs + // single i/o read at startup to mount the image + int FindSceneInImage( const char *pSceneName ); + bool GetSceneDataFromImage( const char *pSceneName, int iIndex, byte *pData, size_t *pLength ); + +private: + CUtlBuffer m_SceneImageFile; +}; + +bool CSceneFileCache::Connect( CreateInterfaceFn factory ) +{ + if ( (filesystem = (IFileSystem *)factory( FILESYSTEM_INTERFACE_VERSION,NULL )) == NULL ) + { + return false; + } + + return true; +} + +void CSceneFileCache::Disconnect() +{ +} + +InitReturnVal_t CSceneFileCache::Init() +{ + const char *pSceneImageName = IsX360() ? "scenes/scenes.360.image" : "scenes/scenes.image"; + + if ( m_SceneImageFile.TellMaxPut() == 0 ) + { + MEM_ALLOC_CREDIT(); + + if ( filesystem->ReadFile( pSceneImageName, "GAME", m_SceneImageFile ) ) + { + SceneImageHeader_t *pHeader = (SceneImageHeader_t *)m_SceneImageFile.Base(); + if ( pHeader->nId != SCENE_IMAGE_ID || + pHeader->nVersion != SCENE_IMAGE_VERSION ) + { + Error( "CSceneFileCache: Bad scene image file %s\n", pSceneImageName ); + } + } + else + { + if ( IsX360() ) + { + if ( filesystem->GetDVDMode() == DVDMODE_STRICT ) + { + // mandatory + Error( "CSceneFileCache: Failed to load %s\n", pSceneImageName ); + } + else + { + // relaxed + Warning( "CSceneFileCache: Failed to load %s, scene playback disabled.\n", pSceneImageName ); + return INIT_OK; + } + } + + m_SceneImageFile.Purge(); + } + } + + return INIT_OK; +} + +void CSceneFileCache::Shutdown() +{ + m_SceneImageFile.Purge(); +} + +// Physically reloads image from disk +void CSceneFileCache::Reload() +{ + Shutdown(); + Init(); +} + +size_t CSceneFileCache::GetSceneBufferSize( char const *pFilename ) +{ + size_t returnSize = 0; + + char fn[MAX_PATH]; + Q_strncpy( fn, pFilename, sizeof( fn ) ); + Q_FixSlashes( fn ); + Q_strlower( fn ); + + GetSceneDataFromImage( pFilename, FindSceneInImage( fn ), NULL, &returnSize ); + return returnSize; +} + +bool CSceneFileCache::GetSceneData( char const *pFilename, byte *buf, size_t bufsize ) +{ + Assert( pFilename ); + Assert( buf ); + Assert( bufsize > 0 ); + + char fn[MAX_PATH]; + Q_strncpy( fn, pFilename, sizeof( fn ) ); + Q_FixSlashes( fn ); + Q_strlower( fn ); + + size_t nLength = bufsize; + return GetSceneDataFromImage( pFilename, FindSceneInImage( fn ), buf, &nLength ); +} + +bool CSceneFileCache::GetSceneCachedData( char const *pFilename, SceneCachedData_t *pData ) +{ + int iScene = FindSceneInImage( pFilename ); + SceneImageHeader_t *pHeader = (SceneImageHeader_t *)m_SceneImageFile.Base(); + if ( !pHeader || iScene < 0 || iScene >= pHeader->nNumScenes ) + { + // not available + pData->sceneId = -1; + pData->msecs = 0; + pData->numSounds = 0; + return false; + } + + // get scene summary + SceneImageEntry_t *pEntries = (SceneImageEntry_t *)( (byte *)pHeader + pHeader->nSceneEntryOffset ); + SceneImageSummary_t *pSummary = (SceneImageSummary_t *)( (byte *)pHeader + pEntries[iScene].nSceneSummaryOffset ); + + pData->sceneId = iScene; + pData->msecs = pSummary->msecs; + pData->numSounds = pSummary->numSounds; + + return true; +} + +short CSceneFileCache::GetSceneCachedSound( int iScene, int iSound ) +{ + SceneImageHeader_t *pHeader = (SceneImageHeader_t *)m_SceneImageFile.Base(); + if ( !pHeader || iScene < 0 || iScene >= pHeader->nNumScenes ) + { + // huh?, image file not present or bad index + return -1; + } + + SceneImageEntry_t *pEntries = (SceneImageEntry_t *)( (byte *)pHeader + pHeader->nSceneEntryOffset ); + SceneImageSummary_t *pSummary = (SceneImageSummary_t *)( (byte *)pHeader + pEntries[iScene].nSceneSummaryOffset ); + if ( iSound < 0 || iSound >= pSummary->numSounds ) + { + // bad index + Assert( 0 ); + return -1; + } + + return pSummary->soundStrings[iSound]; +} + +const char *CSceneFileCache::GetSceneString( short stringId ) +{ + SceneImageHeader_t *pHeader = (SceneImageHeader_t *)m_SceneImageFile.Base(); + if ( !pHeader || stringId < 0 || stringId >= pHeader->nNumStrings ) + { + // huh?, image file not present, or index bad + return NULL; + } + + return pHeader->String( stringId ); +} + +//----------------------------------------------------------------------------- +// Returns -1 if not found, otherwise [0..n] index. +//----------------------------------------------------------------------------- +int CSceneFileCache::FindSceneInImage( const char *pSceneName ) +{ + SceneImageHeader_t *pHeader = (SceneImageHeader_t *)m_SceneImageFile.Base(); + if ( !pHeader ) + { + return -1; + } + SceneImageEntry_t *pEntries = (SceneImageEntry_t *)( (byte *)pHeader + pHeader->nSceneEntryOffset ); + + char szCleanName[MAX_PATH]; + + V_strncpy( szCleanName, pSceneName, sizeof( szCleanName ) ); + V_strlower( szCleanName ); +#ifdef POSIX + V_FixSlashes( szCleanName, '\\' ); +#else + V_FixSlashes( szCleanName ); +#endif + V_SetExtension( szCleanName, ".vcd", sizeof( szCleanName ) ); + + CRC32_t crcFilename = CRC32_ProcessSingleBuffer( szCleanName, strlen( szCleanName ) ); + + // use binary search, entries are sorted by ascending crc + int nLowerIdx = 1; + int nUpperIdx = pHeader->nNumScenes; + for ( ;; ) + { + if ( nUpperIdx < nLowerIdx ) + { + return -1; + } + else + { + int nMiddleIndex = ( nLowerIdx + nUpperIdx )/2; + CRC32_t nProbe = pEntries[nMiddleIndex-1].crcFilename; + if ( crcFilename < nProbe ) + { + nUpperIdx = nMiddleIndex - 1; + } + else + { + if ( crcFilename > nProbe ) + { + nLowerIdx = nMiddleIndex + 1; + } + else + { + return nMiddleIndex - 1; + } + } + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Returns true if success, false otherwise. Caller must free ouput scene data +//----------------------------------------------------------------------------- +bool CSceneFileCache::GetSceneDataFromImage( const char *pFileName, int iScene, byte *pSceneData, size_t *pSceneLength ) +{ + SceneImageHeader_t *pHeader = (SceneImageHeader_t *)m_SceneImageFile.Base(); + if ( !pHeader || iScene < 0 || iScene >= pHeader->nNumScenes ) + { + if ( pSceneData ) + { + *pSceneData = NULL; + } + if ( pSceneLength ) + { + *pSceneLength = 0; + } + return false; + } + + SceneImageEntry_t *pEntries = (SceneImageEntry_t *)( (byte *)pHeader + pHeader->nSceneEntryOffset ); + unsigned char *pData = (unsigned char *)pHeader + pEntries[iScene].nDataOffset; + bool bIsCompressed; + bIsCompressed = CLZMA::IsCompressed( pData ); + if ( bIsCompressed ) + { + int originalSize = CLZMA::GetActualSize( pData ); + if ( pSceneData ) + { + int nMaxLen = *pSceneLength; + if ( originalSize <= nMaxLen ) + { + CLZMA::Uncompress( pData, pSceneData ); + } + else + { + unsigned char *pOutputData = (unsigned char *)malloc( originalSize ); + CLZMA::Uncompress( pData, pOutputData ); + V_memcpy( pSceneData, pOutputData, nMaxLen ); + free( pOutputData ); + } + } + if ( pSceneLength ) + { + *pSceneLength = originalSize; + } + } + else + { + if ( pSceneData ) + { + size_t nCountToCopy = min(*pSceneLength, (size_t)pEntries[iScene].nDataLength ); + V_memcpy( pSceneData, pData, nCountToCopy ); + } + if ( pSceneLength ) + { + *pSceneLength = (size_t)pEntries[iScene].nDataLength; + } + } + return true; +} + +static CSceneFileCache g_SceneFileCache; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSceneFileCache, ISceneFileCache, SCENE_FILE_CACHE_INTERFACE_VERSION, g_SceneFileCache ); diff --git a/scenefilecache/scenefilecache.vpc b/scenefilecache/scenefilecache.vpc new file mode 100644 index 0000000..584d9d7 --- /dev/null +++ b/scenefilecache/scenefilecache.vpc @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// SCENEFILECACHE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Linker + { + $SystemLibraries "iconv" [$OSXALL] + } + $Compiler + { + $AdditionalIncludeDirectories ".\;$BASE;$SRCDIR\game\shared" + $PreprocessorDefinitions "$BASE;_WINDOWS;PROTECTED_THINGS_ENABLE" + } +} + +$Project "SceneFileCache" +{ + $Folder "Source Files" + { + $File "SceneFileCache.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\scenefilecache\ISceneFileCache.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } +} diff --git a/scenefilecache/xbox/xbox.def b/scenefilecache/xbox/xbox.def new file mode 100644 index 0000000..fec2e70 --- /dev/null +++ b/scenefilecache/xbox/xbox.def @@ -0,0 +1,3 @@ +LIBRARY SceneFileCache_360.dll +EXPORTS + CreateInterface @1 diff --git a/vpc_scripts/groups.vgc b/vpc_scripts/groups.vgc index b05ddbc..da0e250 100644 --- a/vpc_scripts/groups.vgc +++ b/vpc_scripts/groups.vgc @@ -25,6 +25,7 @@ $Group "game" "engine" "materialsystem" "particles" + "scenefilecache" "studiorender" "shaderapidx9" "shaderapidx11" @@ -72,6 +73,7 @@ $Group "everything" "qc_eyes" "server" "serverplugin_empty" + "scenefilecache" "shaderapidx9" "shaderapidx11" "stdshader_dbg" diff --git a/vpc_scripts/projects.vgc b/vpc_scripts/projects.vgc index ea11eeb..702601b 100644 --- a/vpc_scripts/projects.vgc +++ b/vpc_scripts/projects.vgc @@ -90,6 +90,11 @@ $Project "serverplugin_empty" "utils\serverplugin_sample\serverplugin_empty.vpc" [$WIN32||$POSIX] } +$Project "scenefilecache" +{ + "scenefilecache\scenefilecache.vpc" [$WIN32||$POSIX] +} + $Project "shaderapidx9" { "materialsystem\shaderapidx9\shaderapidx9.vpc"