mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-08 11:38:21 +02:00
468 lines
17 KiB
C++
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;
|
|
}
|
|
|
|
|