#include "BFPlatform.h" #ifdef BF_WWISE_ENABLED ////////////////////////////////////////////////////////////////////// // // AkFilePackageLUT.cpp // // This class parses the header of file packages that were created with the // AkFilePackager utility app (located in ($WWISESDK)/samples/FilePackager/), // and looks-up files at run-time. // // The header of these file packages contains look-up tables that describe the // internal offset of each file it references, their block size (required alignment), // and their language. Each combination of AkFileID and Language ID is unique. // // The language was created dynamically when the package was created. The header // also contains a map of language names (strings) to their ID, so that the proper // language-specific version of files can be resolved. The language name that is stored // matches the name of the directory that is created by the Wwise Bank Manager, // except for the trailing slash. // // Copyright (c) 2007-2009 Audiokinetic Inc. / All Rights Reserved // ////////////////////////////////////////////////////////////////////// #include "Common.h" #include "AkFilePackageLUT.h" #include #include // For string hash. #include #include #ifdef _DEBUG template struct AkCompileTimeAssert; template<> struct AkCompileTimeAssert { }; #define AK_STATIC_ASSERT(e) (AkCompileTimeAssert<(e) != 0>()) #else #define AK_STATIC_ASSERT(e) #endif #define AK_MAX_EXTERNAL_NAME_SIZE 260 CAkFilePackageLUT::CAkFilePackageLUT() :m_curLangID( AK_INVALID_LANGUAGE_ID ) ,m_pLangMap( NULL ) ,m_pSoundBanks( NULL ) ,m_pStmFiles( NULL ) ,m_pExternals( NULL ) { AK_STATIC_ASSERT(sizeof(AkFileEntry) == 20); AK_STATIC_ASSERT(sizeof(AkFileEntry) == 24); } CAkFilePackageLUT::~CAkFilePackageLUT() { } // Create a new LUT from a packaged file header. // The LUT sets pointers to appropriate location inside header data (in_pData). AKRESULT CAkFilePackageLUT::Setup( AkUInt8 * in_pData, // Header data. AkUInt32 in_uHeaderSize // Size of file package header. ) { struct FileHeaderFormat { char headerDefinition[AKPK_HEADER_CHUNK_DEF_SIZE]; AkUInt32 uVersion; AkUInt32 uLanguageMapSize; AkUInt32 uSoundBanksLUTSize; AkUInt32 uStmFilesLUTSize; AkUInt32 uExternalsLUTSize; }; FileHeaderFormat * pHeader = (FileHeaderFormat*)in_pData; // Check header size, if ( in_uHeaderSize < sizeof(FileHeaderFormat) + pHeader->uLanguageMapSize + pHeader->uSoundBanksLUTSize + pHeader->uStmFilesLUTSize + pHeader->uExternalsLUTSize) { return AK_Fail; } // Check version. if ( pHeader->uVersion < AKPK_CURRENT_VERSION ) return AK_Fail; // Get address of maps and LUTs. in_pData += sizeof(FileHeaderFormat); m_pLangMap = (StringMap*)in_pData; in_pData += pHeader->uLanguageMapSize; m_pSoundBanks = (FileLUT*)in_pData; in_pData += pHeader->uSoundBanksLUTSize; m_pStmFiles = (FileLUT*)in_pData; in_pData += pHeader->uStmFilesLUTSize; m_pExternals = (FileLUT*)in_pData; return AK_Success; } // Find a file entry by ID. const CAkFilePackageLUT::AkFileEntry * CAkFilePackageLUT::LookupFile( AkFileID in_uID, // File ID. AkFileSystemFlags * in_pFlags // Special flags. Do not pass NULL. ) { AKASSERT( in_pFlags && in_pFlags->uCompanyID == AKCOMPANYID_AUDIOKINETIC ); if ( in_pFlags->uCodecID == AKCODECID_BANK && m_pSoundBanks && m_pSoundBanks->HasFiles() ) { return LookupFile( in_uID, m_pSoundBanks, in_pFlags->bIsLanguageSpecific ); } else if ( m_pStmFiles && m_pStmFiles->HasFiles() ) { // We assume that the file is a streamed audio file. return LookupFile( in_uID, m_pStmFiles, in_pFlags->bIsLanguageSpecific ); } // No table loaded. return NULL; } // Find a file entry by ID. const CAkFilePackageLUT::AkFileEntry * CAkFilePackageLUT::LookupFile( AkUInt64 in_uID, // File ID. AkFileSystemFlags * in_pFlags // Special flags. Do not pass NULL. ) { AKASSERT( in_pFlags ); if ( in_pFlags->uCompanyID == AKCOMPANYID_AUDIOKINETIC_EXTERNAL && m_pExternals && m_pExternals->HasFiles() ) { return LookupFile( in_uID, m_pExternals, in_pFlags->bIsLanguageSpecific ); } // No table loaded. return NULL; } // Set current language. // Returns AK_InvalidLanguage if a package is loaded but the language string cannot be found. // Returns AK_Success otherwise. AKRESULT CAkFilePackageLUT::SetCurLanguage( const AkOSChar* in_pszLanguage // Language string. ) { m_curLangID = AK_INVALID_LANGUAGE_ID; if ( m_pLangMap && in_pszLanguage ) { AkUInt16 uLangID = (AkUInt16)m_pLangMap->GetID( in_pszLanguage ); if ( uLangID == AK_INVALID_UNIQUE_ID && m_pLangMap->GetNumStrings() > 1 ) // Do not return AK_InvalidLanguage if package contains only SFX data. { return AK_InvalidLanguage; } m_curLangID = uLangID; } return AK_Success; } void CAkFilePackageLUT::RemoveFileExtension( AkOSChar* in_pstring ) { while( *in_pstring != 0 ) { if( *in_pstring == AKTEXT('.') ) { *in_pstring = 0; return; } ++in_pstring; } } // Find a soundbank ID by its name. // Returns AK_INVALID_FILE_ID if no soundbank LUT is loaded. AkFileID CAkFilePackageLUT::GetSoundBankID( const AkOSChar* in_pszBankName // Soundbank name. ) { // Remove the file extension if it was used. AkUInt32 stringSize = (AkUInt32)AKPLATFORM::OsStrLen( in_pszBankName ) + 1; AkOSChar* pStringWithoutExtension = (AkOSChar *)AkAlloca( (stringSize) * sizeof( AkOSChar ) ); AKPLATFORM::SafeStrCpy( pStringWithoutExtension, in_pszBankName, stringSize ); RemoveFileExtension( pStringWithoutExtension ); // Hash return AK::SoundEngine::GetIDFromString( pStringWithoutExtension ); } AkUInt64 CAkFilePackageLUT::GetExternalID( const AkOSChar* in_pszExternalName // External Source name. ) { char* szString; CONVERT_OSCHAR_TO_CHAR(in_pszExternalName, szString); size_t stringSize = strlen( szString ); // 1- Make lower case. _MakeLowerA( szString, stringSize ); AK::FNVHash64 MainHash; return MainHash.Compute( (const unsigned char *) szString, (unsigned int)stringSize ); } void CAkFilePackageLUT::_MakeLowerA( char* in_pString, size_t in_strlen ) { for( size_t i = 0; i < in_strlen; ++i ) { if( in_pString[i] >= 'A' && in_pString[i] <= 'Z' ) { in_pString[i] += 0x20; } } } void CAkFilePackageLUT::_MakeLower( AkOSChar* in_pString ) { size_t uStrlen = AKPLATFORM::OsStrLen( in_pString ); const AkOSChar CaseDiff = AKTEXT('a') - AKTEXT('A'); for( size_t i = 0; i < uStrlen; ++i ) { if( in_pString[i] >= AKTEXT('A') && in_pString[i] <= AKTEXT('Z') ) { in_pString[i] += CaseDiff; } } } AkUInt32 CAkFilePackageLUT::StringMap::GetID( const AkOSChar* in_pszString ) { // Make string lower case. size_t uStrLen = AKPLATFORM::OsStrLen(in_pszString)+1; AkOSChar * pszLowerCaseString = (AkOSChar*)AkAlloca(uStrLen*sizeof(AkOSChar)); AKASSERT( pszLowerCaseString ); AKPLATFORM::SafeStrCpy(pszLowerCaseString, in_pszString, uStrLen ); _MakeLower( pszLowerCaseString ); // 'this' is m_uNumStrings. +1 points to the beginning of the StringEntry array. StringEntry * pTable = (StringEntry*)((AkUInt32*)this + 1); // Binary search: strings are sorted (case sensitive). AkInt32 uTop = 0, uBottom = m_uNumStrings-1; do { AkInt32 uThis = ( uBottom - uTop ) / 2 + uTop; AkOSChar * pString = (AkOSChar*)((AkUInt8*)this + pTable[ uThis ].uOffset); int iCmp = AKPLATFORM::OsStrCmp( pString, pszLowerCaseString ); if ( 0 == iCmp ) return pTable[uThis].uID; else if ( iCmp > 0 ) //in_pTable[ uThis ].pString > pszLowerCaseString uBottom = uThis - 1; else //in_pTable[ uThis ].pString < pszLowerCaseString uTop = uThis + 1; } while ( uTop <= uBottom ); // ID not found. return AK_INVALID_UNIQUE_ID; } #endif