1212 lines
27 KiB
C++
1212 lines
27 KiB
C++
|
// stream.cpp
|
||
|
// Copyright (c) 1997-2001 Microsoft Corporation
|
||
|
//
|
||
|
// @doc EXTERNAL
|
||
|
|
||
|
#include <objbase.h>
|
||
|
#include "debug.h"
|
||
|
|
||
|
#include "debug.h"
|
||
|
#include "dmusicc.h" // Using specific path for now, since the headers are changing.
|
||
|
#include "dmusici.h" // Using specific path for now, since the headers are changing.
|
||
|
#include "loader.h"
|
||
|
#include <initguid.h>
|
||
|
#include "riff.h"
|
||
|
|
||
|
#ifndef UNDER_CE
|
||
|
#include <regstr.h>
|
||
|
#include <share.h>
|
||
|
|
||
|
extern BOOL g_fIsUnicode;
|
||
|
|
||
|
CFileStream::CFileStream( CLoader *pLoader)
|
||
|
|
||
|
{
|
||
|
assert(pLoader);
|
||
|
m_cRef = 1;
|
||
|
m_pFile = NULL;
|
||
|
m_pLoader = pLoader;
|
||
|
if (pLoader)
|
||
|
{
|
||
|
pLoader->AddRefP();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CFileStream::~CFileStream()
|
||
|
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
m_pLoader->ReleaseP();
|
||
|
}
|
||
|
Close();
|
||
|
}
|
||
|
|
||
|
HRESULT CFileStream::Open(WCHAR * lpFileName,DWORD dwDesiredAccess)
|
||
|
|
||
|
{
|
||
|
Close();
|
||
|
wcscpy(m_wszFileName,lpFileName);
|
||
|
assert(dwDesiredAccess == GENERIC_READ || dwDesiredAccess == GENERIC_WRITE);
|
||
|
if( dwDesiredAccess == GENERIC_READ )
|
||
|
{
|
||
|
if (g_fIsUnicode)
|
||
|
{
|
||
|
m_pFile = _wfsopen( lpFileName, L"rb", _SH_DENYWR );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
char path[MAX_PATH];
|
||
|
wcstombs( path, lpFileName, MAX_PATH );
|
||
|
m_pFile = _fsopen( path, "rb", _SH_DENYWR );
|
||
|
}
|
||
|
}
|
||
|
else if( dwDesiredAccess == GENERIC_WRITE )
|
||
|
{
|
||
|
if (g_fIsUnicode)
|
||
|
{
|
||
|
m_pFile = _wfsopen( lpFileName, L"wb", _SH_DENYNO );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
char path[MAX_PATH];
|
||
|
wcstombs( path, lpFileName, MAX_PATH );
|
||
|
m_pFile = _fsopen( path, "wb", _SH_DENYNO );
|
||
|
}
|
||
|
}
|
||
|
if (m_pFile == NULL)
|
||
|
{
|
||
|
Trace(1, "Warning: The file %S couldn't be opened: %s. Try another path.\n", lpFileName, _strerror(NULL));
|
||
|
return DMUS_E_LOADER_FAILEDOPEN;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT CFileStream::Close()
|
||
|
|
||
|
{
|
||
|
if (m_pFile)
|
||
|
{
|
||
|
fclose(m_pFile);
|
||
|
}
|
||
|
m_pFile = NULL;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::QueryInterface( const IID &riid, void **ppvObj )
|
||
|
{
|
||
|
if (riid == IID_IUnknown || riid == IID_IStream) {
|
||
|
*ppvObj = static_cast<IStream*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
else if (riid == IID_IDirectMusicGetLoader)
|
||
|
{
|
||
|
*ppvObj = static_cast<IDirectMusicGetLoader*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppvObj = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::GetLoader(
|
||
|
IDirectMusicLoader ** ppLoader) // @parm Returns an AddRef'd pointer to the loader.
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
return m_pLoader->QueryInterface( IID_IDirectMusicLoader,(void **) ppLoader );
|
||
|
}
|
||
|
assert(false);
|
||
|
*ppLoader = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFileStream::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_cRef);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFileStream::Release()
|
||
|
{
|
||
|
if (!InterlockedDecrement(&m_cRef))
|
||
|
{
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
return m_cRef;
|
||
|
}
|
||
|
|
||
|
/* IStream methods */
|
||
|
STDMETHODIMP CFileStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
|
||
|
{
|
||
|
size_t dw;
|
||
|
dw = fread( pv, sizeof(char), cb, m_pFile );
|
||
|
if( cb == dw )
|
||
|
{
|
||
|
if( pcbRead != NULL )
|
||
|
{
|
||
|
*pcbRead = cb;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
return E_FAIL ;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
|
||
|
{
|
||
|
if( cb == fwrite( pv, sizeof(char), cb, m_pFile ))
|
||
|
{
|
||
|
if( pcbWritten != NULL )
|
||
|
{
|
||
|
*pcbWritten = cb;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
Trace(1, "Error: An error occurred writing to %S.", m_wszFileName);
|
||
|
return STG_E_MEDIUMFULL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition )
|
||
|
{
|
||
|
// fseek can't handle a LARGE_INTEGER seek...
|
||
|
long lOffset;
|
||
|
|
||
|
lOffset = dlibMove.LowPart;
|
||
|
|
||
|
int i = fseek( m_pFile, lOffset, dwOrigin );
|
||
|
if( i )
|
||
|
{
|
||
|
Trace(1, "Error: An error occurred while seeking in the file %S.\n", m_wszFileName);
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
if( plibNewPosition != NULL )
|
||
|
{
|
||
|
plibNewPosition->QuadPart = ftell( m_pFile );
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::SetSize( ULARGE_INTEGER /*libNewSize*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::CopyTo( IStream* /*pstm */, ULARGE_INTEGER /*cb*/,
|
||
|
ULARGE_INTEGER* /*pcbRead*/,
|
||
|
ULARGE_INTEGER* /*pcbWritten*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Commit( DWORD /*grfCommitFlags*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Revert()
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::LockRegion( ULARGE_INTEGER /*libOffset*/, ULARGE_INTEGER /*cb*/,
|
||
|
DWORD /*dwLockType*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::UnlockRegion( ULARGE_INTEGER /*libOffset*/, ULARGE_INTEGER /*cb*/,
|
||
|
DWORD /*dwLockType*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Stat( STATSTG* /*pstatstg*/, DWORD /*grfStatFlag*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Clone( IStream** pStream )
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
CFileStream *pNewStream = new CFileStream( m_pLoader );
|
||
|
if (pNewStream)
|
||
|
{
|
||
|
hr = pNewStream->Open(m_wszFileName,GENERIC_READ);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
LARGE_INTEGER dlibMove;
|
||
|
dlibMove.QuadPart = 0;
|
||
|
ULARGE_INTEGER libNewPosition;
|
||
|
Seek( dlibMove, STREAM_SEEK_CUR, &libNewPosition );
|
||
|
dlibMove.QuadPart = libNewPosition.QuadPart;
|
||
|
pNewStream->Seek(dlibMove,STREAM_SEEK_SET,NULL);
|
||
|
*pStream = (IStream *) pNewStream;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pNewStream->Release();
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
CFileStream::CFileStream(CLoader *pLoader)
|
||
|
{
|
||
|
m_cRef = 1;
|
||
|
m_hFile = NULL;
|
||
|
m_pLoader = pLoader;
|
||
|
if (pLoader)
|
||
|
{
|
||
|
pLoader->AddRefP();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CFileStream::~CFileStream()
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
m_pLoader->ReleaseP();
|
||
|
}
|
||
|
Close();
|
||
|
}
|
||
|
|
||
|
HRESULT CFileStream::Open(WCHAR * lpFileName, DWORD dwDesiredAccess)
|
||
|
{
|
||
|
Close();
|
||
|
m_hFile = CreateFile(lpFileName, dwDesiredAccess, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
if(m_hFile == NULL || m_hFile == INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
#ifdef DBG
|
||
|
LPVOID lpMsgBuf;
|
||
|
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||
|
NULL,
|
||
|
GetLastError(),
|
||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||
|
(LPTSTR) &lpMsgBuf,
|
||
|
0,
|
||
|
NULL))
|
||
|
{
|
||
|
Trace(1, "Error: The file %S couldn't be opened: %S\n", lpFileName, lpMsgBuf);
|
||
|
LocalFree( lpMsgBuf );
|
||
|
}
|
||
|
#endif
|
||
|
return DMUS_E_LOADER_FAILEDOPEN;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT CFileStream::Close()
|
||
|
{
|
||
|
if(m_hFile)
|
||
|
{
|
||
|
CloseHandle(m_hFile);
|
||
|
}
|
||
|
m_hFile = NULL;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::QueryInterface(const IID &riid, void **ppvObj)
|
||
|
{
|
||
|
if(riid == IID_IUnknown || riid == IID_IStream)
|
||
|
{
|
||
|
*ppvObj = static_cast<IStream*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
else if(riid == IID_IDirectMusicGetLoader)
|
||
|
{
|
||
|
*ppvObj = static_cast<IDirectMusicGetLoader*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppvObj = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::GetLoader(
|
||
|
IDirectMusicLoader ** ppLoader)
|
||
|
{
|
||
|
if(m_pLoader)
|
||
|
{
|
||
|
return m_pLoader->QueryInterface(IID_IDirectMusicLoader,(void **) ppLoader);
|
||
|
}
|
||
|
assert(false);
|
||
|
*ppLoader = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFileStream::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_cRef);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFileStream::Release()
|
||
|
{
|
||
|
if(!InterlockedDecrement(&m_cRef))
|
||
|
{
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
return m_cRef;
|
||
|
}
|
||
|
|
||
|
/* IStream methods */
|
||
|
STDMETHODIMP CFileStream::Read(void* pv, ULONG cb, ULONG* pcbRead)
|
||
|
{
|
||
|
if(ReadFile(m_hFile, pv, cb, pcbRead, NULL))
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Write(const void* pv, ULONG cb, ULONG* pcbWritten)
|
||
|
{
|
||
|
if(WriteFile(m_hFile, pv, cb, pcbWritten, NULL))
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
Trace(1, "Error: An error occurred writing to %S.", m_wszFileName);
|
||
|
return STG_E_MEDIUMFULL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition)
|
||
|
{
|
||
|
DWORD dw;
|
||
|
|
||
|
dw = SetFilePointer(m_hFile,dlibMove.LowPart, &dlibMove.HighPart, dwOrigin);
|
||
|
if(dw == 0xffffffff && GetLastError() != NO_ERROR)
|
||
|
{
|
||
|
Trace(1, "Error: An error occurred while seeking in the file %S.\n", m_wszFileName);
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
if(plibNewPosition != NULL)
|
||
|
{
|
||
|
plibNewPosition->LowPart = dw;
|
||
|
plibNewPosition->HighPart = dlibMove.HighPart;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::SetSize(ULARGE_INTEGER /*libNewSize*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::CopyTo(IStream* /*pstm */, ULARGE_INTEGER /*cb*/,
|
||
|
ULARGE_INTEGER* /*pcbRead*/,
|
||
|
ULARGE_INTEGER* /*pcbWritten*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Commit(DWORD /*grfCommitFlags*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Revert()
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::LockRegion(ULARGE_INTEGER /*libOffset*/, ULARGE_INTEGER /*cb*/,
|
||
|
DWORD /*dwLockType*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::UnlockRegion(ULARGE_INTEGER /*libOffset*/, ULARGE_INTEGER /*cb*/,
|
||
|
DWORD /*dwLockType*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Stat(STATSTG* /*pstatstg*/, DWORD /*grfStatFlag*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFileStream::Clone(IStream** /*ppstm*/)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
CFileStream *pNewStream = new CFileStream( m_pLoader );
|
||
|
if (pNewStream)
|
||
|
{
|
||
|
hr = pNewStream->Open(m_wszFileName,GENERIC_READ);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
LARGE_INTEGER dlibMove;
|
||
|
dlibMove.QuadPart = 0;
|
||
|
ULARGE_INTEGER libNewPosition;
|
||
|
Seek( dlibMove, STREAM_SEEK_CUR, &libNewPosition );
|
||
|
dlibMove.QuadPart = libNewPosition.QuadPart;
|
||
|
pNewStream->Seek(dlibMove,STREAM_SEEK_SET,NULL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pNewStream->Release();
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
CMemStream::CMemStream( CLoader *pLoader)
|
||
|
|
||
|
{
|
||
|
m_cRef = 1;
|
||
|
m_pbData = NULL;
|
||
|
m_llLength = 0;
|
||
|
m_llPosition = 0;
|
||
|
m_pLoader = pLoader;
|
||
|
if (pLoader)
|
||
|
{
|
||
|
pLoader->AddRefP();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CMemStream::CMemStream( CLoader *pLoader,
|
||
|
LONGLONG llLength,
|
||
|
LONGLONG llPosition,
|
||
|
BYTE *pbData)
|
||
|
|
||
|
{
|
||
|
m_cRef = 1;
|
||
|
m_pbData = pbData;
|
||
|
m_llLength = llLength;
|
||
|
m_llPosition = llPosition;
|
||
|
m_pLoader = pLoader;
|
||
|
if (pLoader)
|
||
|
{
|
||
|
pLoader->AddRefP();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CMemStream::~CMemStream()
|
||
|
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
m_pLoader->ReleaseP();
|
||
|
}
|
||
|
Close();
|
||
|
}
|
||
|
|
||
|
HRESULT CMemStream::Open(BYTE *pbData, LONGLONG llLength)
|
||
|
|
||
|
{
|
||
|
Close();
|
||
|
m_pbData = pbData;
|
||
|
m_llLength = llLength;
|
||
|
m_llPosition = 0;
|
||
|
if ((pbData == NULL) || (llLength == 0))
|
||
|
{
|
||
|
#ifdef DBG
|
||
|
if (pbData)
|
||
|
{
|
||
|
Trace(1, "Error: Attempt to load an object from an invalid block of memory. A DMUS_OBJECTDESC has DMUS_OBJ_MEMORY set but pbMemData is NULL.");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Trace(1, "Error: Attempt to load an object from an invalid block of memory. A DMUS_OBJECTDESC has DMUS_OBJ_MEMORY set but llMemLength is 0.");
|
||
|
}
|
||
|
#endif
|
||
|
return DMUS_E_LOADER_FAILEDOPEN;
|
||
|
}
|
||
|
if (IsBadReadPtr(pbData, (DWORD) llLength))
|
||
|
{
|
||
|
m_pbData = NULL;
|
||
|
m_llLength = 0;
|
||
|
#ifdef DBG
|
||
|
DWORD dwLength = (DWORD) llLength;
|
||
|
Trace(1, "Error: Attempt to load an object from an invalid block of memory. A DMUS_OBJECTDESC has DMUS_OBJ_MEMORY, pbMemData=0x%08x, llMemLength=%lu, which isn't a block that can be read.", pbData, dwLength);
|
||
|
#endif
|
||
|
return DMUS_E_LOADER_FAILEDOPEN;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT CMemStream::Close()
|
||
|
|
||
|
{
|
||
|
m_pbData = NULL;
|
||
|
m_llLength = 0;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::QueryInterface( const IID &riid, void **ppvObj )
|
||
|
{
|
||
|
if (riid == IID_IUnknown || riid == IID_IStream) {
|
||
|
*ppvObj = static_cast<IStream*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
else if (riid == IID_IDirectMusicGetLoader)
|
||
|
{
|
||
|
*ppvObj = static_cast<IDirectMusicGetLoader*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppvObj = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP CMemStream::GetLoader(
|
||
|
IDirectMusicLoader ** ppLoader)
|
||
|
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
return m_pLoader->QueryInterface( IID_IDirectMusicLoader,(void **) ppLoader );
|
||
|
}
|
||
|
assert(false);
|
||
|
*ppLoader = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CMemStream::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_cRef);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CMemStream::Release()
|
||
|
{
|
||
|
if (!InterlockedDecrement(&m_cRef))
|
||
|
{
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
return m_cRef;
|
||
|
}
|
||
|
|
||
|
/* IStream methods */
|
||
|
STDMETHODIMP CMemStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
|
||
|
{
|
||
|
if ((cb + m_llPosition) <= m_llLength)
|
||
|
{
|
||
|
memcpy(pv,&m_pbData[m_llPosition],cb);
|
||
|
m_llPosition += cb;
|
||
|
if( pcbRead != NULL )
|
||
|
{
|
||
|
*pcbRead = cb;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
#ifdef DBG
|
||
|
Trace(1, "Error: Unexpected end of data reading object from memory. Memory length is %ld, attempting to read to %ld\n",
|
||
|
(long) m_llLength, (long) (cb + m_llPosition));
|
||
|
#endif
|
||
|
return E_FAIL ;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition )
|
||
|
{
|
||
|
// Since we only parse RIFF data, we can't have a file over
|
||
|
// DWORD in length, so disregard high part of LARGE_INTEGER.
|
||
|
|
||
|
LONGLONG llOffset;
|
||
|
|
||
|
llOffset = dlibMove.QuadPart;
|
||
|
if (dwOrigin == STREAM_SEEK_CUR)
|
||
|
{
|
||
|
llOffset += m_llPosition;
|
||
|
}
|
||
|
else if (dwOrigin == STREAM_SEEK_END)
|
||
|
{
|
||
|
llOffset += m_llLength;
|
||
|
}
|
||
|
if ((llOffset >= 0) && (llOffset <= m_llLength))
|
||
|
{
|
||
|
m_llPosition = llOffset;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifdef DBG
|
||
|
Trace(1, "Error: Seek request %ld past end of memory file, size %ld.\n",
|
||
|
(long) llOffset, (long) m_llLength);
|
||
|
#endif
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
if( plibNewPosition != NULL )
|
||
|
{
|
||
|
plibNewPosition->QuadPart = m_llPosition;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::SetSize( ULARGE_INTEGER /*libNewSize*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::CopyTo( IStream* /*pstm */, ULARGE_INTEGER /*cb*/,
|
||
|
ULARGE_INTEGER* /*pcbRead*/,
|
||
|
ULARGE_INTEGER* /*pcbWritten*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::Commit( DWORD /*grfCommitFlags*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::Revert()
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::LockRegion( ULARGE_INTEGER /*libOffset*/, ULARGE_INTEGER /*cb*/,
|
||
|
DWORD /*dwLockType*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::UnlockRegion( ULARGE_INTEGER /*libOffset*/, ULARGE_INTEGER /*cb*/,
|
||
|
DWORD /*dwLockType*/)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::Stat( STATSTG* /*pstatstg*/, DWORD /*grfStatFlag*/ )
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CMemStream::Clone( IStream** ppStream )
|
||
|
{
|
||
|
*ppStream = (IStream *) new CMemStream( m_pLoader, m_llLength, m_llPosition, m_pbData );
|
||
|
if (*ppStream)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
STDAPI AllocRIFFStream( IStream* pStream, IRIFFStream** ppRiff )
|
||
|
{
|
||
|
if( ( *ppRiff = (IRIFFStream*) new CRIFFStream( pStream ) ) == NULL )
|
||
|
{
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP CRIFFStream::Descend(LPMMCKINFO lpck, LPMMCKINFO lpckParent, UINT wFlags)
|
||
|
{
|
||
|
assert(lpck);
|
||
|
|
||
|
FOURCC ckidFind; // Chunk ID to find (or NULL)
|
||
|
FOURCC fccTypeFind; // Form/list type to find (or NULL)
|
||
|
|
||
|
// Figure out what chunk id and form/list type for which to search
|
||
|
if(wFlags & MMIO_FINDCHUNK)
|
||
|
{
|
||
|
ckidFind = lpck->ckid;
|
||
|
fccTypeFind = NULL;
|
||
|
}
|
||
|
else if(wFlags & MMIO_FINDRIFF)
|
||
|
{
|
||
|
ckidFind = FOURCC_RIFF;
|
||
|
fccTypeFind = lpck->fccType;
|
||
|
}
|
||
|
else if(wFlags & MMIO_FINDLIST)
|
||
|
{
|
||
|
ckidFind = FOURCC_LIST;
|
||
|
fccTypeFind = lpck->fccType;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ckidFind = fccTypeFind = NULL;
|
||
|
}
|
||
|
|
||
|
lpck->dwFlags = 0L;
|
||
|
|
||
|
for(;;)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
LARGE_INTEGER li;
|
||
|
ULARGE_INTEGER uli;
|
||
|
ULONG cbRead;
|
||
|
|
||
|
// Read the chunk header
|
||
|
hr = m_pStream->Read(lpck, 2 * sizeof(DWORD), &cbRead);
|
||
|
|
||
|
if (FAILED(hr) || (cbRead != 2 * sizeof(DWORD)))
|
||
|
{
|
||
|
return DMUS_E_DESCEND_CHUNK_FAIL;
|
||
|
}
|
||
|
|
||
|
// Store the offset of the data part of the chunk
|
||
|
li.QuadPart = 0;
|
||
|
hr = m_pStream->Seek(li, STREAM_SEEK_CUR, &uli);
|
||
|
|
||
|
if(FAILED(hr))
|
||
|
{
|
||
|
Trace(1, "Error: Read error - unable to seek in a stream.\n");
|
||
|
return DMUS_E_CANNOTSEEK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lpck->dwDataOffset = uli.LowPart;
|
||
|
}
|
||
|
|
||
|
// See if the chunk is within the parent chunk (if given)
|
||
|
if((lpckParent != NULL) &&
|
||
|
(lpck->dwDataOffset - 8L >=
|
||
|
lpckParent->dwDataOffset + lpckParent->cksize))
|
||
|
{
|
||
|
return DMUS_E_DESCEND_CHUNK_FAIL;
|
||
|
}
|
||
|
|
||
|
// If the chunk is a 'RIFF' or 'LIST' chunk, read the
|
||
|
// form type or list type
|
||
|
if((lpck->ckid == FOURCC_RIFF) || (lpck->ckid == FOURCC_LIST))
|
||
|
{
|
||
|
hr = m_pStream->Read(&lpck->fccType, sizeof(DWORD), &cbRead);
|
||
|
|
||
|
if(FAILED(hr) || (cbRead != sizeof(DWORD)))
|
||
|
{
|
||
|
return DMUS_E_DESCEND_CHUNK_FAIL;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lpck->fccType = NULL;
|
||
|
}
|
||
|
|
||
|
// If this is the chunk we're looking for, stop looking
|
||
|
if(((ckidFind == NULL) || (ckidFind == lpck->ckid)) &&
|
||
|
((fccTypeFind == NULL) || (fccTypeFind == lpck->fccType)))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Ascend out of the chunk and try again
|
||
|
HRESULT w = Ascend(lpck, 0);
|
||
|
if(FAILED(w))
|
||
|
{
|
||
|
return w;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// CRIFFStream::Ascend
|
||
|
|
||
|
STDMETHODIMP CRIFFStream::Ascend(LPMMCKINFO lpck, UINT /*wFlags*/)
|
||
|
{
|
||
|
assert(lpck);
|
||
|
|
||
|
HRESULT hr;
|
||
|
LARGE_INTEGER li;
|
||
|
ULARGE_INTEGER uli;
|
||
|
|
||
|
if (lpck->dwFlags & MMIO_DIRTY)
|
||
|
{
|
||
|
// <lpck> refers to a chunk created by CreateChunk();
|
||
|
// check that the chunk size that was written when
|
||
|
// CreateChunk() was called is the real chunk size;
|
||
|
// if not, fix it
|
||
|
LONG lOffset; // current offset in file
|
||
|
LONG lActualSize; // actual size of chunk data
|
||
|
|
||
|
li.QuadPart = 0;
|
||
|
hr = m_pStream->Seek(li, STREAM_SEEK_CUR, &uli);
|
||
|
|
||
|
if(FAILED(hr))
|
||
|
{
|
||
|
Trace(1, "Error: Read error - unable to seek in a stream.\n");
|
||
|
return DMUS_E_CANNOTSEEK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lOffset = uli.LowPart;
|
||
|
}
|
||
|
|
||
|
if((lActualSize = lOffset - lpck->dwDataOffset) < 0)
|
||
|
{
|
||
|
Trace(1, "Error: Unable to write to a stream.\n");
|
||
|
return DMUS_E_CANNOTWRITE;
|
||
|
}
|
||
|
|
||
|
if(LOWORD(lActualSize) & 1)
|
||
|
{
|
||
|
ULONG cbWritten;
|
||
|
|
||
|
// Chunk size is odd -- write a null pad byte
|
||
|
hr = m_pStream->Write("\0", 1, &cbWritten);
|
||
|
|
||
|
if(FAILED(hr) || cbWritten != 1)
|
||
|
{
|
||
|
Trace(1, "Error: Unable to write to a stream.\n");
|
||
|
return DMUS_E_CANNOTWRITE;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if(lpck->cksize == (DWORD)lActualSize)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
// Fix the chunk header
|
||
|
lpck->cksize = lActualSize;
|
||
|
|
||
|
li.QuadPart = lpck->dwDataOffset - sizeof(DWORD);
|
||
|
hr = m_pStream->Seek(li, STREAM_SEEK_SET, &uli);
|
||
|
|
||
|
if(FAILED(hr))
|
||
|
{
|
||
|
Trace(1, "Error: Read error - unable to seek in a stream.\n");
|
||
|
return DMUS_E_CANNOTSEEK;
|
||
|
}
|
||
|
|
||
|
ULONG cbWritten;
|
||
|
|
||
|
hr = m_pStream->Write(&lpck->cksize, sizeof(DWORD), &cbWritten);
|
||
|
|
||
|
if(FAILED(hr) || cbWritten != sizeof(DWORD))
|
||
|
{
|
||
|
Trace(1, "Error: Unable to write to a stream.\n");
|
||
|
return DMUS_E_CANNOTWRITE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Seek to the end of the chunk, past the null pad byte
|
||
|
// (which is only there if chunk size is odd)
|
||
|
li.QuadPart = lpck->dwDataOffset + lpck->cksize + (lpck->cksize & 1L);
|
||
|
hr = m_pStream->Seek(li, STREAM_SEEK_SET, &uli);
|
||
|
|
||
|
if(FAILED(hr))
|
||
|
{
|
||
|
Trace(1, "Error: Read error - unable to seek in a stream.\n");
|
||
|
return DMUS_E_CANNOTSEEK;
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// CRIFFStream::CreateChunk
|
||
|
|
||
|
STDMETHODIMP CRIFFStream::CreateChunk(LPMMCKINFO lpck, UINT wFlags)
|
||
|
{
|
||
|
assert(lpck);
|
||
|
|
||
|
UINT iBytes; // Bytes to write
|
||
|
LONG lOffset; // Current offset in file
|
||
|
|
||
|
// Store the offset of the data part of the chunk
|
||
|
LARGE_INTEGER li;
|
||
|
ULARGE_INTEGER uli;
|
||
|
|
||
|
li.QuadPart = 0;
|
||
|
HRESULT hr = m_pStream->Seek(li, STREAM_SEEK_CUR, &uli);
|
||
|
|
||
|
if(FAILED(hr))
|
||
|
{
|
||
|
Trace(1, "Error: Read error - unable to seek in a stream.\n");
|
||
|
return DMUS_E_CANNOTSEEK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lOffset = uli.LowPart;
|
||
|
}
|
||
|
|
||
|
lpck->dwDataOffset = lOffset + 2 * sizeof(DWORD);
|
||
|
|
||
|
// figure out if a form/list type needs to be written
|
||
|
if(wFlags & MMIO_CREATERIFF)
|
||
|
{
|
||
|
lpck->ckid = FOURCC_RIFF, iBytes = 3 * sizeof(DWORD);
|
||
|
}
|
||
|
else if(wFlags & MMIO_CREATELIST)
|
||
|
{
|
||
|
lpck->ckid = FOURCC_LIST, iBytes = 3 * sizeof(DWORD);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
iBytes = 2 * sizeof(DWORD);
|
||
|
}
|
||
|
|
||
|
// Write the chunk header
|
||
|
ULONG cbWritten;
|
||
|
|
||
|
hr = m_pStream->Write(lpck, iBytes, &cbWritten);
|
||
|
|
||
|
if(FAILED(hr) || cbWritten != iBytes)
|
||
|
{
|
||
|
Trace(1, "Error: Unable to write to a stream.\n");
|
||
|
return DMUS_E_CANNOTWRITE;
|
||
|
}
|
||
|
|
||
|
lpck->dwFlags = MMIO_DIRTY;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
CStream::CStream( CLoader *pLoader, IStream *pStream )
|
||
|
|
||
|
{
|
||
|
m_cRef = 1;
|
||
|
m_pIStream = pStream;
|
||
|
if (pStream)
|
||
|
{
|
||
|
pStream->AddRef();
|
||
|
}
|
||
|
m_pLoader = pLoader;
|
||
|
if (pLoader)
|
||
|
{
|
||
|
pLoader->AddRefP();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CStream::CStream( CLoader *pLoader)
|
||
|
|
||
|
{
|
||
|
m_cRef = 1;
|
||
|
m_pIStream = NULL;
|
||
|
m_pLoader = pLoader;
|
||
|
if (pLoader)
|
||
|
{
|
||
|
pLoader->AddRefP();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CStream::~CStream()
|
||
|
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
m_pLoader->ReleaseP();
|
||
|
}
|
||
|
Close();
|
||
|
}
|
||
|
|
||
|
HRESULT CStream::Open(IStream *pIStream,LARGE_INTEGER liStartPosition)
|
||
|
|
||
|
{
|
||
|
Close();
|
||
|
|
||
|
m_pIStream = pIStream;
|
||
|
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
// Need to seek to point that was where we were in the stream
|
||
|
// when the stream was first provided via GetObject or SetObject.
|
||
|
pIStream->Seek(liStartPosition,STREAM_SEEK_SET,NULL);
|
||
|
pIStream->AddRef();
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT CStream::Close()
|
||
|
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
m_pIStream->Release();
|
||
|
}
|
||
|
m_pIStream = NULL;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::QueryInterface( const IID &riid, void **ppvObj )
|
||
|
{
|
||
|
if (riid == IID_IUnknown || riid == IID_IStream) {
|
||
|
*ppvObj = static_cast<IStream*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
else if (riid == IID_IDirectMusicGetLoader)
|
||
|
{
|
||
|
*ppvObj = static_cast<IDirectMusicGetLoader*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppvObj = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP CStream::GetLoader(
|
||
|
IDirectMusicLoader ** ppLoader)
|
||
|
|
||
|
{
|
||
|
if (m_pLoader)
|
||
|
{
|
||
|
return m_pLoader->QueryInterface( IID_IDirectMusicLoader,(void **) ppLoader );
|
||
|
}
|
||
|
assert(false);
|
||
|
*ppLoader = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CStream::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_cRef);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CStream::Release()
|
||
|
{
|
||
|
if (!InterlockedDecrement(&m_cRef))
|
||
|
{
|
||
|
Close();
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
return m_cRef;
|
||
|
}
|
||
|
|
||
|
/* IStream methods */
|
||
|
STDMETHODIMP CStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->Read(pv, cb, pcbRead);
|
||
|
}
|
||
|
Trace(1, "Error: Attempt to read from a NULL stream.\n");
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->Write(pv, cb, pcbWritten);
|
||
|
}
|
||
|
Trace(1, "Error: Attempt to write to a NULL stream.\n");
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition )
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->Seek(dlibMove, dwOrigin, plibNewPosition);
|
||
|
}
|
||
|
Trace(1, "Error: Read error - attempt to seek in a NULL stream.\n");
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::SetSize( ULARGE_INTEGER libNewSize)
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->SetSize(libNewSize);
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::CopyTo( IStream* pstm, ULARGE_INTEGER cb,
|
||
|
ULARGE_INTEGER* pcbRead,
|
||
|
ULARGE_INTEGER* pcbWritten)
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->CopyTo(pstm, cb, pcbRead, pcbWritten);
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::Commit( DWORD grfCommitFlags)
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->Commit(grfCommitFlags);
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::Revert()
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->Revert();
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::LockRegion( ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
|
||
|
DWORD dwLockType)
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->LockRegion(libOffset, cb, dwLockType);
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::UnlockRegion( ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
|
||
|
DWORD dwLockType)
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->UnlockRegion(libOffset, cb, dwLockType);
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::Stat( STATSTG* pstatstg, DWORD grfStatFlag)
|
||
|
{
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
return m_pIStream->Stat(pstatstg, grfStatFlag);
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CStream::Clone( IStream** ppStream)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
if (m_pIStream)
|
||
|
{
|
||
|
IStream *pNewIStream = NULL;
|
||
|
hr = m_pIStream->Clone(&pNewIStream);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
CStream *pNewCStream = new CStream( m_pLoader, pNewIStream );
|
||
|
if (pNewCStream)
|
||
|
{
|
||
|
*ppStream = (IStream *) pNewCStream;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
pNewIStream->Release();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Trace(1, "Error: Attempt to clone a NULL stream.\n");
|
||
|
}
|
||
|
return hr;
|
||
|
}
|