/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ #include "Common/SimplePlayer.h" #include "Common/URLLaunch.h" /////////////////////////////////////////////////////////////////////////////// CSimplePlayer::CSimplePlayer( HRESULT* phr ) { m_cRef = 1; m_cBuffersOutstanding = 0; m_pReader = NULL; m_pHeader = NULL; m_hwo = NULL; m_fEof = FALSE; m_pszUrl = NULL; *phr = S_OK; m_hOpenEvent = CreateEvent( NULL, FALSE, FALSE, SIMPLE_PLAYER_OPEN_EVENT ); if ( NULL == m_hOpenEvent ) { *phr = E_OUTOFMEMORY; } m_hCloseEvent = CreateEvent( NULL, FALSE, FALSE, SIMPLE_PLAYER_CLOSE_EVENT ); if ( NULL == m_hCloseEvent ) { *phr = E_OUTOFMEMORY; } m_hrOpen = S_OK; m_hCompletionEvent = NULL; InitializeCriticalSection( &m_CriSec ); m_whdrHead = NULL; } /////////////////////////////////////////////////////////////////////////////// CSimplePlayer::~CSimplePlayer() { DEBUG_ASSERTCRASH( 0 == m_cBuffersOutstanding,("CSimplePlayer destructor m_cBuffersOutstanding != 0") ); Close(); // // final remove of everything in the wave header list // RemoveWaveHeaders(); DeleteCriticalSection( &m_CriSec ); if( m_pHeader != NULL ) { m_pHeader->Release(); m_pHeader = NULL; } if( m_pReader != NULL ) { m_pReader->Release(); m_pReader = NULL; } if( m_hwo != NULL ) { waveOutClose( m_hwo ); } delete [] m_pszUrl; if ( m_hOpenEvent ) { CloseHandle( m_hOpenEvent ); } if ( m_hCloseEvent ) { CloseHandle( m_hCloseEvent ); } } /////////////////////////////////////////////////////////////////////////////// HRESULT STDMETHODCALLTYPE CSimplePlayer::QueryInterface( REFIID riid, void **ppvObject ) { return( E_NOINTERFACE ); } /////////////////////////////////////////////////////////////////////////////// ULONG STDMETHODCALLTYPE CSimplePlayer::AddRef() { return( InterlockedIncrement( &m_cRef ) ); } /////////////////////////////////////////////////////////////////////////////// ULONG STDMETHODCALLTYPE CSimplePlayer::Release() { ULONG uRet = InterlockedDecrement( &m_cRef ); if( 0 == uRet ) { delete this; } return( uRet ); } /////////////////////////////////////////////////////////////////////////////// HRESULT STDMETHODCALLTYPE CSimplePlayer::OnSample( /* [in] */ DWORD dwOutputNum, /* [in] */ QWORD cnsSampleTime, /* [in] */ QWORD cnsSampleDuration, /* [in] */ DWORD dwFlags, /* [in] */ INSSBuffer __RPC_FAR *pSample, /* [in] */ VOID *pvContext ) { if( 0 != dwOutputNum ) { return( S_OK ); } HRESULT hr = S_OK; BYTE *pData; DWORD cbData; // // first UnprepareHeader and remove everthing in the ready list // RemoveWaveHeaders( ); hr = pSample->GetBufferAndLength( &pData, &cbData ); if ( FAILED( hr ) ) { return( E_UNEXPECTED ); } DEBUG_LOG(( " New Sample of length %d and PS time %d ms\n", cbData, ( DWORD ) ( cnsSampleTime / 10000 ) )); LPWAVEHDR pwh = (LPWAVEHDR) new BYTE[ sizeof( WAVEHDR ) + cbData ]; if( NULL == pwh ) { DEBUG_LOG(( "OnSample OUT OF MEMORY! \n")); *m_phrCompletion = E_OUTOFMEMORY; SetEvent( m_hCompletionEvent ); return( E_UNEXPECTED ); } pwh->lpData = (LPSTR)&pwh[1]; pwh->dwBufferLength = cbData; pwh->dwBytesRecorded = cbData; pwh->dwUser = 0; pwh->dwLoops = 0; pwh->dwFlags = 0; CopyMemory( pwh->lpData, pData, cbData ); MMRESULT mmr; mmr = waveOutPrepareHeader( m_hwo, pwh, sizeof(WAVEHDR) ); mmr = MMSYSERR_NOERROR; if( mmr != MMSYSERR_NOERROR ) { DEBUG_LOG(( "failed to prepare wave buffer, error=%lu\n" , mmr )); *m_phrCompletion = E_UNEXPECTED; SetEvent( m_hCompletionEvent ); return( E_UNEXPECTED ); } mmr = waveOutWrite( m_hwo, pwh, sizeof(WAVEHDR) ); mmr = MMSYSERR_NOERROR; if( mmr != MMSYSERR_NOERROR ) { delete pwh; DEBUG_LOG(( "failed to write wave sample, error=%lu\n" , mmr )); *m_phrCompletion = E_UNEXPECTED; SetEvent( m_hCompletionEvent ); return( E_UNEXPECTED ); } InterlockedIncrement( &m_cBuffersOutstanding ); return( S_OK ); } /////////////////////////////////////////////////////////////////////////////// HRESULT CSimplePlayer::Play( LPCWSTR pszUrl, DWORD dwSecDuration, HANDLE hCompletionEvent, HRESULT *phrCompletion ) { HRESULT hr; // // If the URL is not a UNC path, a full path, or an Internet-style URL then assume it is // a relative local file name that needs to be expanded to a full path. // WCHAR wszFullUrl[ MAX_PATH ]; if( ( 0 == wcsstr( pszUrl, L"\\\\" ) ) && ( 0 == wcsstr( pszUrl, L":\\" ) ) && ( 0 == wcsstr( pszUrl, L"://" ) ) ) { // // Expand to a full path name // LPWSTR pszCheck = _wfullpath( wszFullUrl, pszUrl, MAX_PATH ); if( NULL == pszCheck ) { DEBUG_LOG(( "internal error %lu\n" , GetLastError() )); return E_UNEXPECTED ; } pszUrl = wszFullUrl; } // // Save a copy of the URL // delete[] m_pszUrl; m_pszUrl = new WCHAR[ wcslen( pszUrl ) + 1 ]; if( NULL == m_pszUrl ) { DEBUG_LOG(( "insufficient Memory\n" )) ; return( E_OUTOFMEMORY ); } wcscpy( m_pszUrl, pszUrl ); // // Attempt to open the URL // m_hCompletionEvent = hCompletionEvent; m_phrCompletion = phrCompletion; #ifdef SUPPORT_DRM hr = WMCreateReader( NULL, WMT_RIGHT_PLAYBACK, &m_pReader ); #else hr = WMCreateReader( NULL, 0, &m_pReader ); #endif if( FAILED( hr ) ) { DEBUG_LOG(( "failed to create audio reader (hr=0x%08x)\n" , hr )); return( hr ); } // // Open the file // hr = m_pReader->Open( m_pszUrl, this, NULL ); if ( SUCCEEDED( hr ) ) { WaitForSingleObject( m_hOpenEvent, INFINITE ); hr = m_hrOpen; } if ( NS_E_NO_STREAM == hr ) { DEBUG_LOG(( "Waiting for transmission to begin...\n" )); WaitForSingleObject( m_hOpenEvent, INFINITE ); hr = m_hrOpen; } if ( FAILED( hr ) ) { DEBUG_LOG(( "failed to open (hr=0x%08x)\n", hr )); return( hr ); } // // It worked! Display various attributes // hr = m_pReader->QueryInterface( IID_IWMHeaderInfo, ( VOID ** )&m_pHeader ); if ( FAILED( hr ) ) { DEBUG_LOG(( "failed to qi for header interface (hr=0x%08x)\n" , hr )); return( hr ); } WORD i, wAttrCnt; hr = m_pHeader->GetAttributeCount( 0, &wAttrCnt ); if ( FAILED( hr ) ) { DEBUG_LOG(( "GetAttributeCount Failed (hr=0x%08x)\n" , hr )); return( hr ); } WCHAR *pwszName = NULL; BYTE *pValue = NULL; for ( i = 0; i < wAttrCnt ; i++ ) { WORD wStream = 0; WORD cchNamelen = 0; WMT_ATTR_DATATYPE type; WORD cbLength = 0; hr = m_pHeader->GetAttributeByIndex( i, &wStream, NULL, &cchNamelen, &type, NULL, &cbLength ); if ( FAILED( hr ) ) { DEBUG_LOG(( "GetAttributeByIndex Failed (hr=0x%08x)\n" , hr )); break; } pwszName = new WCHAR[ cchNamelen ]; pValue = new BYTE[ cbLength ]; if( NULL == pwszName || NULL == pValue ) { hr = E_OUTOFMEMORY; break; } hr = m_pHeader->GetAttributeByIndex( i, &wStream, pwszName, &cchNamelen, &type, pValue, &cbLength ); if ( FAILED( hr ) ) { DEBUG_LOG(( "GetAttributeByIndex Failed (hr=0x%08x)\n" , hr )); break; } switch ( type ) { case WMT_TYPE_DWORD: DEBUG_LOG(("%ws: %u\n" , pwszName, *((DWORD *) pValue) )); break; case WMT_TYPE_STRING: DEBUG_LOG(("%ws: %ws\n" , pwszName, (WCHAR *) pValue )); break; case WMT_TYPE_BINARY: DEBUG_LOG(("%ws: Type = Binary of Length %u\n" , pwszName, cbLength )); break; case WMT_TYPE_BOOL: DEBUG_LOG(("%ws: %s\n" , pwszName, ( * ( ( BOOL * ) pValue) ? _T( "true" ) : _T( "false" ) ) )); break; case WMT_TYPE_WORD: DEBUG_LOG(("%ws: %hu\n" , pwszName, *((WORD *) pValue) )); break; case WMT_TYPE_QWORD: DEBUG_LOG(("%ws: %I64u\n" , pwszName, *((QWORD *) pValue) )); break; case WMT_TYPE_GUID: DEBUG_LOG(("%ws: %I64x%I64x\n" , pwszName, *((QWORD *) pValue), *((QWORD *) pValue + 1) )); break; default: DEBUG_LOG(("%ws: Type = %d, Length %u\n" , pwszName, type, cbLength )); break; } delete pwszName; pwszName = NULL; delete pValue; pValue = NULL; } delete pwszName; pwszName = NULL; delete pValue; pValue = NULL; if ( FAILED( hr ) ) { return( hr ); } // // Make sure we're audio only // DWORD cOutputs; hr = m_pReader->GetOutputCount( &cOutputs ); if ( FAILED( hr ) ) { DEBUG_LOG(( "failed GetOutputCount(), (hr=0x%08x)\n" , hr )); return( hr ); } if ( cOutputs != 1 ) { DEBUG_LOG(( "Not audio only (cOutputs = %d).\n" , cOutputs )); // return( E_UNEXPECTED ); } IWMOutputMediaProps *pProps; hr = m_pReader->GetOutputProps( 0, &pProps ); if ( FAILED( hr ) ) { DEBUG_LOG(( "failed GetOutputProps(), (hr=0x%08x)\n" , hr )); return( hr ); } DWORD cbBuffer = 0; hr = pProps->GetMediaType( NULL, &cbBuffer ); if ( FAILED( hr ) ) { pProps->Release( ); DEBUG_LOG(( "GetMediaType failed (hr=0x%08x)\n" , hr )); return( hr ); } WM_MEDIA_TYPE *pMediaType = ( WM_MEDIA_TYPE * ) new BYTE[cbBuffer] ; hr = pProps->GetMediaType( pMediaType, &cbBuffer ); if ( FAILED( hr ) ) { pProps->Release( ); DEBUG_LOG(( "GetMediaType failed (hr=0x%08x)\n" , hr )); return( hr ); } pProps->Release( ); if ( pMediaType->majortype != WMMEDIATYPE_Audio ) { delete[] (BYTE *) pMediaType ; DEBUG_LOG(( "Not audio only (major type mismatch).\n" )); return( E_UNEXPECTED ); } // // Set up for audio playback // WAVEFORMATEX *pwfx = ( WAVEFORMATEX * )pMediaType->pbFormat; memcpy( &m_wfx, pwfx, sizeof( WAVEFORMATEX ) + pwfx->cbSize ); delete[] (BYTE *)pMediaType ; pMediaType = NULL ; MMRESULT mmr; mmr = waveOutOpen( &m_hwo, WAVE_MAPPER, &m_wfx, (DWORD)WaveProc, (DWORD)this, CALLBACK_FUNCTION ); mmr = MMSYSERR_NOERROR; if( mmr != MMSYSERR_NOERROR ) { DEBUG_LOG(( "failed to open wav output device, error=%lu\n" , mmr )); return( E_UNEXPECTED ); } // // Start reading the data (and rendering the audio) // QWORD cnsDuration = ( QWORD ) dwSecDuration * 10000000; hr = m_pReader->Start( 0, cnsDuration, 1.0, NULL ); if( FAILED( hr ) ) { DEBUG_LOG(( "failed Start(), (hr=0x%08x)\n" , hr )); return( hr ); } return( hr ); } /////////////////////////////////////////////////////////////////////////////// HRESULT STDMETHODCALLTYPE CSimplePlayer::OnStatus( /* [in] */ WMT_STATUS Status, /* [in] */ HRESULT hr, /* [in] */ WMT_ATTR_DATATYPE dwType, /* [in] */ BYTE __RPC_FAR *pValue, /* [in] */ void __RPC_FAR *pvContext) { switch( Status ) { case WMT_OPENED: DEBUG_LOG(( "OnStatus( WMT_OPENED )\n" )); m_hrOpen = hr; SetEvent( m_hOpenEvent ); break; case WMT_SOURCE_SWITCH: DEBUG_LOG(( "OnStatus( WMT_SOURCE_SWITCH )\n" )); m_hrOpen = hr; SetEvent( m_hOpenEvent ); break; case WMT_ERROR: DEBUG_LOG(( "OnStatus( WMT_ERROR )\n" )); break; case WMT_STARTED: DEBUG_LOG(( "OnStatus( WMT_STARTED )\n" )); break; case WMT_STOPPED: DEBUG_LOG(( "OnStatus( WMT_STOPPED )\n" )); break; case WMT_BUFFERING_START: DEBUG_LOG(( "OnStatus( WMT_BUFFERING START)\n" )); break; case WMT_BUFFERING_STOP: DEBUG_LOG(( "OnStatus( WMT_BUFFERING STOP)\n" )); break; case WMT_EOF: DEBUG_LOG(( "OnStatus( WMT_EOF )\n" )); // // cleanup and exit // m_fEof = TRUE; if( 0 == m_cBuffersOutstanding ) { SetEvent( m_hCompletionEvent ); } break; case WMT_END_OF_SEGMENT: DEBUG_LOG(( "OnStatus( WMT_END_OF_SEGMENT )\n" )); // // cleanup and exit // m_fEof = TRUE; if( 0 == m_cBuffersOutstanding ) { SetEvent( m_hCompletionEvent ); } break; case WMT_LOCATING: DEBUG_LOG(( "OnStatus( WMT_LOCATING )\n" )); break; case WMT_CONNECTING: DEBUG_LOG(( "OnStatus( WMT_CONNECTING )\n" )); break; case WMT_NO_RIGHTS: { LPWSTR pwszEscapedURL = NULL; hr = MakeEscapedURL( m_pszUrl, &pwszEscapedURL ); if( SUCCEEDED( hr ) ) { WCHAR wszURL[ 0x1000 ]; swprintf( wszURL, L"%s&filename=%s&embedded=false", pValue, pwszEscapedURL ); hr = LaunchURL( wszURL ); if( FAILED( hr ) ) { DEBUG_LOG(( "Unable to launch web browser to retrieve playback license (hr=0x%08x)\n" , hr )); } delete [] pwszEscapedURL; pwszEscapedURL = NULL ; } } break; case WMT_MISSING_CODEC: { DEBUG_LOG(( "Missing codec: (hr=0x%08x)\n" , hr )); break; } case WMT_CLOSED: SetEvent( m_hCloseEvent ); break; }; return( S_OK ); } /////////////////////////////////////////////////////////////////////////////// HRESULT CSimplePlayer::Close() { HRESULT hr = S_OK; if( NULL != m_pReader ) { hr = m_pReader->Close(); if( SUCCEEDED( hr ) ) { WaitForSingleObject( m_hCloseEvent, INFINITE ); } } return( hr ); } /////////////////////////////////////////////////////////////////////////////// void CSimplePlayer::OnWaveOutMsg( UINT uMsg, DWORD dwParam1, DWORD dwParam2 ) { if( WOM_DONE == uMsg ) { // // add the wave header to ready-to-free list for the caller // to pick up and free in the next OnSample call // AddWaveHeader( ( LPWAVEHDR )dwParam1 ); InterlockedDecrement( &m_cBuffersOutstanding ); if( m_fEof && ( 0 == m_cBuffersOutstanding ) ) { SetEvent( m_hCompletionEvent ); } } } /////////////////////////////////////////////////////////////////////////////// void CALLBACK CSimplePlayer::WaveProc( HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 ) { CSimplePlayer *pThis = (CSimplePlayer*)dwInstance; pThis->OnWaveOutMsg( uMsg, dwParam1, dwParam2 ); } ////////////////////////////////////////////////////////////////////////////// HRESULT CSimplePlayer::AddWaveHeader( LPWAVEHDR pwh ) { WAVEHDR_LIST *tmp = new WAVEHDR_LIST; if( NULL == tmp ) { return( E_OUTOFMEMORY ); } tmp->pwh = pwh; EnterCriticalSection( &m_CriSec ); tmp->next = m_whdrHead; m_whdrHead = tmp; LeaveCriticalSection( &m_CriSec ); return( S_OK ); } ////////////////////////////////////////////////////////////////////////////// void CSimplePlayer::RemoveWaveHeaders( ) { WAVEHDR_LIST *tmp; EnterCriticalSection( &m_CriSec ); while( NULL != m_whdrHead ) { tmp = m_whdrHead->next; DEBUG_ASSERTCRASH( m_whdrHead->pwh->dwFlags & WHDR_DONE, ("RemoveWaveHeaders!") ); waveOutUnprepareHeader( m_hwo, m_whdrHead->pwh, sizeof( WAVEHDR ) ); delete m_whdrHead->pwh; delete m_whdrHead; m_whdrHead = tmp; } LeaveCriticalSection( &m_CriSec ); }