1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-08 11:38:21 +02:00
Beef/BeefySysLib/sound/Common/AkFilePackageLowLevelIO.inl
2019-08-23 11:56:54 -07:00

468 lines
17 KiB
C++

//////////////////////////////////////////////////////////////////////
//
// 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 <AK/Tools/Common/AkPlatformFuncs.h>
template <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::CAkFilePackageLowLevelIO()
: m_bRegisteredToLangChg( false )
{
}
template <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::~CAkFilePackageLowLevelIO()
{
}
// Initialize/terminate.
template <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
void CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AkUInt32 CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
void CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
template <class T_FILEID>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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<T_FILEID> * 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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::_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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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 <class T_LLIOHOOK_FILELOC, class T_PACKAGE>
AKRESULT CAkFilePackageLowLevelIO<T_LLIOHOOK_FILELOC,T_PACKAGE>::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;
}