////////////////////////////////////////////////////////////////////// // // AkFilePackageLowLevelIO.h // // CAkFilePackageLowLevelIO extends a Low-Level I/O device by providing // the ability to reference files that are part of a file package. // // It can extend either blocking or deferred I/O hooks (both inheriting from // AK::StreamMgr::IAkLowLevelIOHook), since its base class is templated. // In either case, the base class must also implement // AK::StreamMgr::IAkFileLocationResolver. This interface defines both overloads // for Open(), and this is where the package's look-up table is searched. // If no match is found, then it falls back on the base implementation. // // Clients of devices that use this class' functionnality simply need to call // LoadFilePackage(), which loads and parses file packages that were created with // the AkFilePackager utility app (located in ($WWISESDK)/samples/FilePackager/). // 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. // // LoadFilePackage() returns a package ID that can be used to unload it. Any number // of packages can be loaded simultaneously. When Open() is called, the last package // loaded is searched first, then the previous one, and so on. // // The language ID 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. // // The type of package is also a template argument. By default, it is a disk package // (see AkDiskPackage.h). // // Copyright (c) 2006 Audiokinetic Inc. / All Rights Reserved // ////////////////////////////////////////////////////////////////////// #include "AkFilePackageLowLevelIO.h" #include "AkFileHelpers.h" #include template CAkFilePackageLowLevelIO::CAkFilePackageLowLevelIO() : m_bRegisteredToLangChg( false ) { } template CAkFilePackageLowLevelIO::~CAkFilePackageLowLevelIO() { } // Initialize/terminate. template void CAkFilePackageLowLevelIO::Term() { UnloadAllFilePackages(); m_packages.Term(); if ( m_bRegisteredToLangChg ) AK::StreamMgr::RemoveLanguageChangeObserver( this ); T_LLIOHOOK_FILELOC::Term(); } // Override Open (string): Search file in each LUT first. If it cannot be found, use base class services. // If the file is found in the LUTs, open is always synchronous. template AKRESULT CAkFilePackageLowLevelIO::Open( const AkOSChar* in_pszFileName, // File name. AkOpenMode in_eOpenMode, // Open mode. AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL. bool & io_bSyncOpen, // If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred. AkFileDesc & out_fileDesc // Returned file descriptor. ) { // If the file is an AK sound bank, try to find the identifier in the lookup table first. if ( in_eOpenMode == AK_OpenModeRead && in_pFlags ) { if( in_pFlags->uCompanyID == AKCOMPANYID_AUDIOKINETIC && in_pFlags->uCodecID == AKCODECID_BANK ) { // Search file in each package. ListFilePackages::Iterator it = m_packages.Begin(); while ( it != m_packages.End() ) { AkFileID fileID = (*it)->lut.GetSoundBankID( in_pszFileName ); if ( FindPackagedFile( (T_PACKAGE*)(*it), fileID, in_pFlags, out_fileDesc ) == AK_Success ) { // Found the ID in the lut. io_bSyncOpen = true; // File is opened, now. return AK_Success; } ++it; } } else if ( in_pFlags->uCompanyID == AKCOMPANYID_AUDIOKINETIC_EXTERNAL ) { // Search file in each package. ListFilePackages::Iterator it = m_packages.Begin(); while ( it != m_packages.End() ) { AkUInt64 fileID = (*it)->lut.GetExternalID( in_pszFileName ); if ( FindPackagedFile( (T_PACKAGE*)(*it), fileID, in_pFlags, out_fileDesc ) == AK_Success ) { // Found the ID in the lut. io_bSyncOpen = true; // File is opened, now. return AK_Success; } ++it; } } } // It is not a soundbank, or it is not in the file package LUT. Use default implementation. return T_LLIOHOOK_FILELOC::Open( in_pszFileName, in_eOpenMode, in_pFlags, io_bSyncOpen, out_fileDesc); } // Override Open (ID): Search file in each LUT first. If it cannot be found, use base class services. // If the file is found in the LUTs, open is always synchronous. template AKRESULT CAkFilePackageLowLevelIO::Open( AkFileID in_fileID, // File ID. AkOpenMode in_eOpenMode, // Open mode. AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL. bool & io_bSyncOpen, // If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred. AkFileDesc & out_fileDesc // Returned file descriptor. ) { // Try to find the identifier in the lookup table first. if ( in_eOpenMode == AK_OpenModeRead && in_pFlags && in_pFlags->uCompanyID == AKCOMPANYID_AUDIOKINETIC) { // Search file in each package. ListFilePackages::Iterator it = m_packages.Begin(); while ( it != m_packages.End() ) { if ( FindPackagedFile( (T_PACKAGE*)(*it), in_fileID, in_pFlags, out_fileDesc ) == AK_Success ) { // File found. Return now. io_bSyncOpen = true; // File is opened, now. return AK_Success; } ++it; } } else if ( in_pFlags->uCompanyID == AKCOMPANYID_AUDIOKINETIC_EXTERNAL ) { // Search file in each package. ListFilePackages::Iterator it = m_packages.Begin(); while ( it != m_packages.End() ) { AkOSChar szFileName[20]; AK_OSPRINTF(szFileName, 20, AKTEXT("%u.wem"), (unsigned int)in_fileID); AkUInt64 fileID = (*it)->lut.GetExternalID(szFileName); if ( FindPackagedFile( (T_PACKAGE*)(*it), fileID, in_pFlags, out_fileDesc ) == AK_Success ) { // Found the ID in the lut. io_bSyncOpen = true; // File is opened, now. return AK_Success; } ++it; } } // If it the fileID is not in the LUT, perform standard path concatenation logic. return T_LLIOHOOK_FILELOC::Open( in_fileID, in_eOpenMode, in_pFlags, io_bSyncOpen, out_fileDesc); } // Override Close: Do not close handle if file descriptor is part of the current packaged file. template AKRESULT CAkFilePackageLowLevelIO::Close( AkFileDesc & in_fileDesc // File descriptor. ) { // Do not close handle if it is that of the file package (closed only in UnloadFilePackage()). if ( !IsInPackage( in_fileDesc ) ) return T_LLIOHOOK_FILELOC::Close( in_fileDesc ); return AK_Success; } // Override GetBlockSize: Get the block size of the LUT if a file package is loaded. template AkUInt32 CAkFilePackageLowLevelIO::GetBlockSize( AkFileDesc & in_fileDesc // File descriptor. ) { if ( IsInPackage( in_fileDesc ) ) { // This file is part of a package. At Open(), we used the // AkFileDesc.uCustomParamSize field to store the block size. return in_fileDesc.uCustomParamSize; } return T_LLIOHOOK_FILELOC::GetBlockSize( in_fileDesc ); } // Updates language of all loaded packages. Packages keep a language ID to help them find // language-specific assets quickly. template void CAkFilePackageLowLevelIO::OnLanguageChange( const AkOSChar * const in_pLanguageName // New language name. ) { // Set language on all loaded packages. ListFilePackages::Iterator it = m_packages.Begin(); while ( it != m_packages.End() ) { (*it)->lut.SetCurLanguage( in_pLanguageName ); ++it; } } // Searches the LUT to find the file data associated with the FileID. // Returns AK_Success if the file is found. template template AKRESULT CAkFilePackageLowLevelIO::FindPackagedFile( T_PACKAGE * in_pPackage, // Package to search into. T_FILEID in_fileID, // File ID. AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL. AkFileDesc & out_fileDesc // Returned file descriptor. ) { AKASSERT( in_pPackage && in_pFlags ); const CAkFilePackageLUT::AkFileEntry * pEntry = in_pPackage->lut.LookupFile( in_fileID, in_pFlags ); if ( pEntry ) { // Fill file descriptor. out_fileDesc.deviceID = T_LLIOHOOK_FILELOC::m_deviceID; in_pPackage->GetHandleForFileDesc( out_fileDesc.hFile ); out_fileDesc.iFileSize = pEntry->uFileSize; out_fileDesc.uSector = pEntry->uStartBlock; out_fileDesc.pCustomParam = NULL; // NOTE: We use the uCustomParamSize to store the block size. // We will determine whether this file was opened from a package by comparing // uCustomParamSize with 0 (see IsInPackage()). out_fileDesc.uCustomParamSize = pEntry->uBlockSize; return AK_Success; } return AK_FileNotFound; } // File package loading: // Opens a package file, parses its header, fills LUT. // Overrides of Open() will search files in loaded LUTs first, then use default Low-Level I/O // services if they cannot be found. // Any number of packages can be loaded at a time. Each LUT is searched until a match is found. // Returns AK_Success if successful, AK_InvalidLanguage if the current language // does not exist in the LUT (not necessarily an error), AK_Fail for any other reason. // Also returns a package ID which can be used to unload it (see UnloadFilePackage()). template AKRESULT CAkFilePackageLowLevelIO::LoadFilePackage( const AkOSChar * in_pszFilePackageName, // File package name. AkUInt32 & out_uPackageID, // Returned package ID. AkMemPoolId in_memPoolID /*= AK_DEFAULT_POOL_ID */ // Memory pool in which the LUT is written. Passing AK_DEFAULT_POOL_ID will create a new pool automatically. ) { // Open package file. AkFilePackageReader filePackageReader; AKRESULT eRes = filePackageReader.Open( in_pszFilePackageName, true ); // Open from SFX-only directory. if ( eRes != AK_Success ) return eRes; filePackageReader.SetName( in_pszFilePackageName ); T_PACKAGE * pPackage; eRes = _LoadFilePackage( in_pszFilePackageName, filePackageReader, AK_DEFAULT_PRIORITY, in_memPoolID, pPackage ); if ( eRes == AK_Success || eRes == AK_InvalidLanguage ) { AKASSERT( pPackage ); // Add to packages list. m_packages.AddFirst( pPackage ); out_uPackageID = pPackage->ID(); } return eRes; } // Loads a file package, with a given file package reader. template AKRESULT CAkFilePackageLowLevelIO::_LoadFilePackage( const AkOSChar* in_pszFilePackageName, // File package name. AkFilePackageReader & in_reader, // File package reader. AkPriority in_readerPriority, // File package reader priority heuristic. AkMemPoolId in_memPoolID, // Memory pool in which the LUT is written. Passing AK_DEFAULT_POOL_ID will create a new pool automatically. T_PACKAGE *& out_pPackage // Returned package ) { // Read header chunk definition. struct AkFilePackageHeader { AkUInt32 uFileFormatTag; AkUInt32 uHeaderSize; }; AkUInt32 uReadBufferSize = AkMax( 2 * in_reader.GetBlockSize(), sizeof(AkFilePackageHeader) ); AkUInt8 * pBufferForHeader = (AkUInt8*)AkAlloca( uReadBufferSize ); AkUInt32 uSizeToRead; bool bAligned = ( sizeof(AkFilePackageHeader) % in_reader.GetBlockSize() ) > 0; if ( bAligned ) { // Header size is not a multiple of the required block size. Allocate an aligned buffer on the stack. pBufferForHeader += ( in_reader.GetBlockSize() - (AkUIntPtr)pBufferForHeader % in_reader.GetBlockSize() ); uSizeToRead = in_reader.GetBlockSize(); } else { // Header size is a multiple of the required block size. uSizeToRead = sizeof(AkFilePackageHeader); } AkUInt32 uSizeRead; AKRESULT eRes = in_reader.Read( pBufferForHeader, uSizeToRead, uSizeRead, in_readerPriority ); if ( eRes != AK_Success || uSizeRead < sizeof(AkFilePackageHeader) ) { AKASSERT( !"Could not read package, or package is invalid" ); in_reader.Close(); return AK_Fail; } const AkFilePackageHeader & uFileHeader = *(AkFilePackageHeader*)pBufferForHeader; if ( uFileHeader.uFileFormatTag != AKPK_FILE_FORMAT_TAG || 0 == uFileHeader.uHeaderSize ) { AKASSERT( !"Invalid file package header" ); in_reader.Close(); return AK_Fail; } // Create file package. AkUInt32 uReservedHeaderSize; AkUInt8 * pFilePackageHeader; out_pPackage = T_PACKAGE::Create( in_reader, in_pszFilePackageName, in_memPoolID, uFileHeader.uHeaderSize + AKPK_HEADER_CHUNK_DEF_SIZE, // NOTE: The header size written in the file package excludes the AKPK_HEADER_CHUNK_DEF_SIZE. uReservedHeaderSize, pFilePackageHeader ); if ( !out_pPackage ) { AKASSERT( !"Could not create file package" ); in_reader.Close(); return AK_Fail; } AkUInt32 uHeaderSize = uFileHeader.uHeaderSize; AkUInt32 uHeaderReadOffset = AKPK_HEADER_CHUNK_DEF_SIZE; // If we had already read more than sizeof(AkFilePackageHeader), copy the rest now. if ( uSizeRead > sizeof(AkFilePackageHeader) ) { pBufferForHeader += sizeof(AkFilePackageHeader); AkUInt32 uSizeToCopy = uSizeRead - sizeof(AkFilePackageHeader); AKPLATFORM::AkMemCpy( pFilePackageHeader+AKPK_HEADER_CHUNK_DEF_SIZE, pBufferForHeader, uSizeToCopy ); // Adjust header size and read offset. if ( uSizeToCopy > uHeaderSize ) uSizeToCopy = uHeaderSize; uHeaderSize -= uSizeToCopy; uHeaderReadOffset += uSizeToCopy; // Round it up to required block size. It should be equal to the size that was reserved (minus what was already read). uHeaderSize = ( ( uHeaderSize + in_reader.GetBlockSize() - 1 ) / in_reader.GetBlockSize() ) * in_reader.GetBlockSize(); AKASSERT( uHeaderSize == uReservedHeaderSize - uSizeRead ); } // Stream in remaining of the header. if ( uHeaderSize > 0 ) { AKASSERT( uHeaderReadOffset % in_reader.GetBlockSize() == 0 ); if ( in_reader.Read( pFilePackageHeader+uHeaderReadOffset, uHeaderSize, uSizeRead, in_readerPriority ) != AK_Success || uSizeRead < uHeaderSize ) { AKASSERT( !"Could not read file package" ); out_pPackage->Destroy(); return AK_Fail; } } // Parse LUT. eRes = out_pPackage->lut.Setup( pFilePackageHeader, uFileHeader.uHeaderSize + AKPK_HEADER_CHUNK_DEF_SIZE ); if ( eRes != AK_Success ) { out_pPackage->Destroy(); return eRes; } // Register to language change notifications if it wasn't already done if ( !m_bRegisteredToLangChg ) { if ( AK::StreamMgr::AddLanguageChangeObserver( LanguageChangeHandler, this ) != AK_Success ) { out_pPackage->Destroy(); return AK_Fail; } m_bRegisteredToLangChg = true; } // Use the current language path (if defined) to set the language ID, // for language specific file mapping. return out_pPackage->lut.SetCurLanguage( AK::StreamMgr::GetCurrentLanguage() ); } // Unload a file package. template AKRESULT CAkFilePackageLowLevelIO::UnloadFilePackage( AkUInt32 in_uPackageID // Package ID. ) { ListFilePackages::IteratorEx it = m_packages.BeginEx(); while ( it != m_packages.End() ) { if ( (*it)->ID() == in_uPackageID ) { CAkFilePackage * pPackage = (*it); it = m_packages.Erase( it ); // Destroy package. pPackage->Destroy(); return AK_Success; } else ++it; } AKASSERT( !"Invalid package ID" ); return AK_Fail; } // Unload all file packages. template AKRESULT CAkFilePackageLowLevelIO::UnloadAllFilePackages() { ListFilePackages::IteratorEx it = m_packages.BeginEx(); while ( it != m_packages.End() ) { CAkFilePackage * pPackage = (*it); it = m_packages.Erase( it ); // Destroy package. pPackage->Destroy(); } return AK_Success; }