340 lines
8.9 KiB
C++
340 lines
8.9 KiB
C++
|
//====== Copyright <20> 1996-2005, 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 = "scenes/scenes" PLATFORM_EXT ".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 * RESTRICT 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->m_fLastSpeakSecs = 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->m_fLastSpeakSecs = pSummary->GetDurToSpeechEnd();
|
|||
|
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
|
|||
|
// Many vcd's in CSGO have a '.' in the filename, which breaks this call
|
|||
|
// We're going to assume that all filenames have the correct extension
|
|||
|
// 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;
|
|||
|
CLZMA lzma;
|
|||
|
bool bIsCompressed;
|
|||
|
bIsCompressed = lzma.IsCompressed( pData );
|
|||
|
if ( bIsCompressed )
|
|||
|
{
|
|||
|
int originalSize = lzma.GetActualSize( pData );
|
|||
|
if ( pSceneData )
|
|||
|
{
|
|||
|
int nMaxLen = *pSceneLength;
|
|||
|
if ( originalSize <= nMaxLen )
|
|||
|
{
|
|||
|
lzma.Uncompress( pData, pSceneData );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
unsigned char *pOutputData = (unsigned char *)malloc( originalSize );
|
|||
|
lzma.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 );
|