//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: X360 Application Chooser // //===========================================================================// #if !defined( _X360 ) #include #endif #include "appframework/iappsystemgroup.h" #include "appframework/appframework.h" #include "tier0/dbg.h" #include "tier1/interface.h" #include "tier1/KeyValues.h" #include "filesystem.h" #include "vstdlib/cvar.h" #include "filesystem_init.h" #include "tier1/utlbuffer.h" #include "icommandline.h" #include "datacache/idatacache.h" #include "datacache/imdlcache.h" #include "studio.h" #include "utlbuffer.h" #include "tier2/utlstreambuffer.h" #include "tier2/tier2.h" #include "appframework/tier3app.h" #include "mathlib/mathlib.h" #include "inputsystem/iinputsystem.h" #include "vphysics_interface.h" #include "istudiorender.h" #include "studio.h" #include "vgui/IVGui.h" #include "VGuiMatSurface/IMatSystemSurface.h" #include "matsys_controls/matsyscontrols.h" #include "vgui/ILocalize.h" #include "vgui_controls/panel.h" #include "vgui_controls/Label.h" #include "vgui_controls/imagepanel.h" #include "vgui_controls/AnimationController.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/imesh.h" #include "materialsystem/materialsystem_config.h" #include "materialsystem/MaterialSystemUtil.h" #include "materialsystem/ishaderapi.h" #include "materialsystem/itexture.h" #include "filesystem/IQueuedLoader.h" #include "xwvfile.h" #if defined( _X360 ) #include "hl2orange.spa.h" #include "xbox/xbox_console.h" #include "xbox/xbox_win32stubs.h" #include "xbox/xbox_launch.h" #include #include #include #endif #if !defined( _X360 ) #include "xbox/xboxstubs.h" #endif #include "tier0/memdbgon.h" #define INACTIVITY_TIMEOUT 120 #define MOVIE_PATH "d:\\movies" bool g_bActive = true; // user background music can force this to be zero static float g_TargetMovieVolume = 1.0f; extern SpewOutputFunc_t g_DefaultSpewFunc; typedef void (*MovieEndCallback_t)(); struct game_t { const wchar_t *pName; const char *pGameDir; bool bEnabled; }; game_t g_Games[] = { { L"HALF-LIFE 2", "hl2", true }, { L"HALF-LIFE 2:\nEPISODE ONE", "episodic", true }, { L" HALF-LIFE 2:\nEPISODE TWO", "ep2", true }, { L"PORTAL", "portal", true }, { L" TEAM\nFORTRESS 2", "tf", true }, }; struct StartupMovie_t { const char *pMovieName; bool bUserCanSkip; }; // played in order on initial startup StartupMovie_t g_StartupMovies[] = { { "valve_legalese.wmv", false }, }; const char *g_DemoMovies[] = { "demo.wmv", "teaser_l4d.wmv", }; class CImage : public vgui::Panel { DECLARE_CLASS_SIMPLE( CImage, vgui::Panel ); public: CImage( vgui::Panel *pParent, const char *pName, const char *pImageName ) : BaseClass( pParent, pName ) { m_imageID = vgui::surface()->CreateNewTextureID(); vgui::surface()->DrawSetTextureFile( m_imageID, pImageName, false, false ); m_Color = Color( 255, 255, 255, 255 ); SetPaintBackgroundEnabled( true ); m_AspectRatio = 1.0f; m_bLetterbox = false; } CImage( vgui::Panel *pParent, const char *pName, IMaterial *pMaterial ) : BaseClass( pParent, pName ) { m_imageID = vgui::surface()->CreateNewTextureID(); g_pMatSystemSurface->DrawSetTextureMaterial( m_imageID, pMaterial ); m_Color = Color( 255, 255, 255, 255 ); SetPaintBackgroundEnabled( true ); m_AspectRatio = 1.0f; m_bLetterbox = false; } void SetAspectRatio( float aspectRatio, bool bLetterbox ) { if ( aspectRatio > 1.0f ) { m_AspectRatio = aspectRatio; m_bLetterbox = bLetterbox; } } void SetColor( int r, int g, int b ) { // set rgb only m_Color.SetColor( r, g, b, m_Color.a() ); } void SetAlpha( int alpha ) { // set alpha only alpha = clamp( alpha, 0, 255 ); m_Color.SetColor( m_Color.r(), m_Color.g(), m_Color.b(), alpha ); } int GetAlpha() const { return m_Color.a(); } virtual void PaintBackground( void ) { if ( m_Color.a() != 0 ) { int panelWidth, panelHeight; GetSize( panelWidth, panelHeight ); float s0 = 0.0f; float s1 = 1.0f; int y = 0; if ( m_AspectRatio > 1.0f ) { if ( m_bLetterbox ) { // horizontal letterbox float adjustedHeight = (float)panelWidth / m_AspectRatio; float bandHeight = ( (float)panelHeight - adjustedHeight ) / 2; vgui::surface()->DrawSetColor( Color( 0, 0, 0, m_Color.a() ) ); vgui::surface()->DrawFilledRect( 0, 0, panelWidth, bandHeight ); vgui::surface()->DrawFilledRect( 0, panelHeight - bandHeight, panelWidth, panelHeight ); y = bandHeight; panelHeight = adjustedHeight; } else { // hold the panel's height constant, determine the corresponding aspect corrected image width float imageWidth = (float)panelHeight * m_AspectRatio; // adjust the image width as a percentage of the panel's width // scale and center; s1 = (float)panelWidth / imageWidth; s0 = ( 1 - s1 ) / 2.0f; s1 = s0 + s1; } } vgui::surface()->DrawSetColor( m_Color ); vgui::surface()->DrawSetTexture( m_imageID ); vgui::surface()->DrawTexturedSubRect( 0, y, panelWidth, y+panelHeight, s0, 0.0f, s1, 1.0f ); } } int m_imageID; Color m_Color; float m_AspectRatio; bool m_bLetterbox; }; class CMovieImage : public vgui::Panel { DECLARE_CLASS_SIMPLE( CMovieImage, vgui::Panel ); public: CMovieImage( vgui::Panel *pParent, const char *pName, const char *pUniqueName, int nDecodeWidth, int nDecodeHeight, float fadeDelay = 1.0f ) : BaseClass( pParent, pName ) { // decoupled from actual panel bounds, can be decoded at any power of two resolution m_nDecodeWidth = nDecodeWidth; m_nDecodeHeight = nDecodeHeight; char materialName[MAX_PATH]; V_snprintf( materialName, sizeof( materialName ), "MovieMaterial_%s.vmt", pUniqueName ); char textureName[MAX_PATH]; V_snprintf( textureName, sizeof( textureName ), "MovieFrame_%s", pUniqueName ); // create a texture for use as movie frame grab m_pTexture = g_pMaterialSystem->CreateProceduralTexture( textureName, TEXTURE_GROUP_OTHER, m_nDecodeWidth, m_nDecodeHeight, g_pMaterialSystem->GetBackBufferFormat(), TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); KeyValues *pVMTKeyValues = new KeyValues( "screenspace_general" ); pVMTKeyValues->SetInt( "$ignorez", 1 ); pVMTKeyValues->SetInt( "$linearread_basetexture", 1 ); pVMTKeyValues->SetInt( "$X360APPCHOOSER", 1 ); pVMTKeyValues->SetString( "$PIXSHADER", "appchooser360movie_ps20b" ); pVMTKeyValues->SetString( "$basetexture", textureName ); m_pMaterial = g_pMaterialSystem->CreateMaterial( materialName, pVMTKeyValues ); m_imageID = vgui::surface()->CreateNewTextureID(); g_pMatSystemSurface->DrawSetTextureMaterial( m_imageID, m_pMaterial ); SetPaintBackgroundEnabled( true ); // initially invisible m_Color = Color( 255, 255, 255, 0 ); m_FadeDelay = fadeDelay; m_pXMVPlayer = NULL; m_bStopping = false; m_StartTime = 0; m_StopTime = 0; m_pMovieEndCallback = NULL; m_bLooped = false; m_AspectRatio = 1.0f; m_bLetterbox = false; } void SetAspectRatio( float aspectRatio, bool bLetterbox ) { if ( aspectRatio > 1.0f ) { m_AspectRatio = aspectRatio; m_bLetterbox = bLetterbox; } } void SetColor( int r, int g, int b ) { // set rgb only m_Color.SetColor( r, g, b, m_Color.a() ); } // -1 means never have any audio, otherwise set to current target void InitUserAudioMix( bool bAudio ) { m_CurrentVolume = bAudio ? g_TargetMovieVolume : -1; } // fade in/out based on user interaction with dashboard music system void UpdateMovieVolume( bool bForce, float frametime = 1.0f ) { // m_CurrentVolume < 0 means this movie never plays audio if ( !m_pXMVPlayer || m_CurrentVolume < 0 ) return; // forced update or new volume, ramp & set if ( bForce || g_TargetMovieVolume != m_CurrentVolume ) { if ( bForce ) { frametime = 1.0f; } m_CurrentVolume = Approach( g_TargetMovieVolume, m_CurrentVolume, frametime * 0.5f ); // UNDONE: Under what conditions can this fail? If it fails it could cause audible pops IXAudioSourceVoice *pVoice = NULL; HRESULT hr = m_pXMVPlayer->GetSourceVoice( &pVoice ); if ( !FAILED( hr ) && pVoice ) { pVoice->SetVolume( m_CurrentVolume ); } } } bool StartMovieFromMemory( const void *pBuffer, int bufferSize, bool bAudio, bool bLoop, MovieEndCallback_t pMovieEndCallback = NULL ) { if ( m_pXMVPlayer || m_bStopping ) { // already started or currently stopping return false; } if ( !pBuffer || !bufferSize ) { return false; } XMEDIA_XMV_CREATE_PARAMETERS xmvParameters; V_memset( &xmvParameters, 0, sizeof( xmvParameters ) ); xmvParameters.dwFlags = XMEDIA_CREATE_CPU_AFFINITY; if ( bLoop ) { xmvParameters.dwFlags |= XMEDIA_CREATE_FOR_LOOP; m_bLooped = true; } if ( !bAudio ) { xmvParameters.dwAudioStreamId = (DWORD)XMEDIA_STREAM_ID_DONT_USE; } InitUserAudioMix(bAudio); xmvParameters.dwVideoDecoderCpu = 2; xmvParameters.dwVideoRendererCpu = 2; xmvParameters.dwAudioDecoderCpu = 4; xmvParameters.dwAudioRendererCpu = 4; xmvParameters.createType = XMEDIA_CREATE_FROM_MEMORY; xmvParameters.createFromMemory.pvBuffer = (PVOID)pBuffer; xmvParameters.createFromMemory.dwBufferSize = bufferSize; IDirect3DDevice9 *pD3DDevice = (IDirect3DDevice9 *)g_pMaterialSystem->GetD3DDevice(); HRESULT hr = XMediaCreateXmvPlayer( pD3DDevice, &xmvParameters, &m_pXMVPlayer ); if ( FAILED( hr ) ) { return false; } m_pMovieEndCallback = pMovieEndCallback; RECT rect; rect.left = 0; rect.top = 0; rect.right = m_nDecodeWidth; rect.bottom = m_nDecodeHeight; m_pXMVPlayer->SetRectangle( &rect ); UpdateMovieVolume( true ); SetAlpha( 0 ); m_StartTime = Plat_FloatTime() + m_FadeDelay; m_StopTime = 0; return true; } bool StartMovieFromFile( const char *pMovieName, bool bAudio, bool bLoop, MovieEndCallback_t pMovieEndCallback = NULL ) { if ( m_pXMVPlayer || m_bStopping ) { // already started or currently stopping return false; } XMEDIA_XMV_CREATE_PARAMETERS xmvParameters; V_memset( &xmvParameters, 0, sizeof( xmvParameters ) ); xmvParameters.dwFlags = XMEDIA_CREATE_CPU_AFFINITY; if ( bLoop ) { xmvParameters.dwFlags |= XMEDIA_CREATE_FOR_LOOP; m_bLooped = true; } if ( !bAudio ) { xmvParameters.dwAudioStreamId = (DWORD)XMEDIA_STREAM_ID_DONT_USE; } InitUserAudioMix( bAudio ); char szFilename[MAX_PATH]; V_ComposeFileName( MOVIE_PATH, pMovieName, szFilename, sizeof( szFilename ) ); xmvParameters.dwVideoDecoderCpu = 2; xmvParameters.dwVideoRendererCpu = 2; xmvParameters.dwAudioDecoderCpu = 4; xmvParameters.dwAudioRendererCpu = 4; xmvParameters.createType = XMEDIA_CREATE_FROM_FILE; xmvParameters.createFromFile.szFileName = szFilename; IDirect3DDevice9 *pD3DDevice = (IDirect3DDevice9 *)g_pMaterialSystem->GetD3DDevice(); HRESULT hr = XMediaCreateXmvPlayer( pD3DDevice, &xmvParameters, &m_pXMVPlayer ); if ( FAILED( hr ) ) { return false; } m_pMovieEndCallback = pMovieEndCallback; RECT rect; rect.left = 0; rect.top = 0; rect.right = m_nDecodeWidth; rect.bottom = m_nDecodeHeight; m_pXMVPlayer->SetRectangle( &rect ); UpdateMovieVolume( true ); SetAlpha( 0 ); m_StartTime = Plat_FloatTime() + m_FadeDelay; m_StopTime = 0; return true; } void StopMovie() { if ( !m_pXMVPlayer || m_bStopping ) { // already stopped or currently stopping return; } m_bLooped = false; m_bStopping = true; m_pXMVPlayer->Stop( XMEDIA_STOP_IMMEDIATE ); SetAlpha( 255 ); m_StartTime = 0; m_StopTime = Plat_FloatTime() + m_FadeDelay; } virtual void PaintBackground( void ) { if ( m_StartTime ) { // fade up goes from [0..1] and holds on 1 float t = ( Plat_FloatTime() - m_StartTime ) * 2.0f; t = clamp( t, 0.0f, 1.0f ); SetAlpha( t * 255.0f ); } if ( m_StopTime ) { // fade out goes from [1..0] and holds on 0 float t = ( Plat_FloatTime() - m_StopTime ) * 2.0f; t = 1.0f - clamp( t, 0.0f, 1.0f ); SetAlpha( t * 255.0f ); if ( m_Color.a() == 0 ) { if ( m_bStopping && m_pMovieEndCallback ) { m_pMovieEndCallback(); } m_bStopping = false; } } if ( m_Color.a() != 0 ) { int panelWidth, panelHeight; GetSize( panelWidth, panelHeight ); float s0 = 0.0f; float s1 = 1.0f; int y = 0; if ( m_AspectRatio > 1.0f ) { if ( m_bLetterbox ) { // horizontal letterbox float adjustedHeight = (float)panelWidth / m_AspectRatio; float bandHeight = ( (float)panelHeight - adjustedHeight ) / 2; vgui::surface()->DrawSetColor( Color( 0, 0, 0, m_Color.a() ) ); vgui::surface()->DrawFilledRect( 0, 0, panelWidth, bandHeight ); vgui::surface()->DrawFilledRect( 0, panelHeight - bandHeight, panelWidth, panelHeight ); y = bandHeight; panelHeight = adjustedHeight; } else { // hold the panel's height constant, determine the corresponding aspect corrected image width float imageWidth = (float)panelHeight * m_AspectRatio; // adjust the image width as a percentage of the panel's width // scale and center; s1 = (float)panelWidth / imageWidth; s0 = ( 1 - s1 ) / 2.0f; s1 = s0 + s1; } } vgui::surface()->DrawSetColor( m_Color ); vgui::surface()->DrawSetTexture( m_imageID ); vgui::surface()->DrawTexturedSubRect( 0, y, panelWidth, y+panelHeight, s0, 0.0f, s1, 1.0f ); } } bool IsFullyReleased() { // fully stopped and released when object no longer exists return ( m_pXMVPlayer == NULL ) && ( m_bStopping == false ); } bool RenderVideoFrame() { if ( !m_pXMVPlayer ) { return false; } // If RenderNextFrame does not return S_OK then the frame was not // rendered (perhaps because it was cancelled) so a regular frame // buffer should be rendered before calling present. bool bRenderedFrame = true; HRESULT hr = m_pXMVPlayer->RenderNextFrame( 0, NULL ); if ( FAILED( hr ) || hr == XMEDIA_W_EOF ) { bRenderedFrame = false; if ( !m_bLooped ) { // Release the movie object m_pXMVPlayer->Release(); m_pXMVPlayer = NULL; if ( !m_bStopping ) { m_bStopping = true; SetAlpha( 255 ); m_StartTime = 0; m_StopTime = Plat_FloatTime() + m_FadeDelay; } } } // UNDONE: Need a frametime here. Assume it's 30fps. // NOTE: This is only used to time audio fades, so if it's wrong by 2X it's not // going to be noticeable. Probably fine to ship this. UpdateMovieVolume( false, 1.0f / 30.0f ); // Reset our cached view of what pixel and vertex shaders are set, because // it is no longer accurate, since XMV will have set their own shaders. // This avoids problems when the shader cache thinks it knows what shader // is set and it is wrong. IDirect3DDevice9 *pD3DDevice = (IDirect3DDevice9 *)g_pMaterialSystem->GetD3DDevice(); pD3DDevice->SetVertexShader( NULL ); pD3DDevice->SetPixelShader( NULL ); pD3DDevice->SetVertexDeclaration( NULL ); pD3DDevice->SetRenderState( D3DRS_VIEWPORTENABLE, TRUE ); if ( bRenderedFrame ) { CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); Rect_t rect; rect.x = 0; rect.y = 0; rect.width = m_nDecodeWidth; rect.height = m_nDecodeHeight; pRenderContext->CopyRenderTargetToTextureEx( m_pTexture, 0, &rect ); } return bRenderedFrame; } private: void SetAlpha( int alpha ) { // set alpha only alpha = clamp( alpha, 0, 255 ); m_Color.SetColor( m_Color.r(), m_Color.g(), m_Color.b(), alpha ); } int m_imageID; Color m_Color; IXMediaXmvPlayer *m_pXMVPlayer; ITexture *m_pTexture; IMaterial *m_pMaterial; bool m_bStopping; bool m_bLooped; float m_StartTime; float m_StopTime; int m_nDecodeWidth; int m_nDecodeHeight; float m_FadeDelay; float m_AspectRatio; float m_CurrentVolume; bool m_bLetterbox; MovieEndCallback_t m_pMovieEndCallback; }; class CShadowLabel : public vgui::Label { DECLARE_CLASS_SIMPLE( CShadowLabel, vgui::Label ); public: CShadowLabel( vgui::Panel *pParent, const char *pName, const wchar_t *pText ) : BaseClass( pParent, pName, pText ) { } virtual void Paint( void ) { BaseClass::Paint(); BaseClass::Paint(); BaseClass::Paint(); BaseClass::Paint(); BaseClass::Paint(); } }; class CGamePanel : public vgui::Panel { DECLARE_CLASS_SIMPLE( CGamePanel, vgui::Panel ); public: CGamePanel( vgui::Panel *pParent, const char *pName, game_t *pGame, bool bIsWidescreen, KeyValues *pKVSettings ) : BaseClass( pParent, pName ) { m_bEnabled = pGame->bEnabled; vgui::HScheme hScheme = vgui::scheme()->GetScheme( "SourceScheme" ); vgui::IScheme *pSourceScheme = vgui::scheme()->GetIScheme( hScheme ); KeyValues *pKV = pKVSettings->FindKey( "GamePanel" ); int panelWidth = pKV->GetInt( "wide" ); int panelHeight = pKV->GetInt( "tall" ); SetSize( panelWidth, panelHeight ); // thumbnail static image char szFilename[MAX_PATH]; V_snprintf( szFilename, sizeof( szFilename ), "vgui/appchooser/%s", pGame->pGameDir ); pKV = pKVSettings->FindKey( "GameImage" ); int y = pKV->GetInt( "ypos" ); int w = pKV->GetInt( "wide" ); int h = pKV->GetInt( "tall" ); int x = ( panelWidth - w ) / 2; m_pThumbImage = new CImage( this, "GameImage", szFilename ); SETUP_PANEL( m_pThumbImage ); m_pThumbImage->SetBounds( x, y, w, h ); m_pThumbImage->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, false ); m_pThumbImage->SetVisible( true ); // thumbnail movie m_pMovieImage = new CMovieImage( this, "Movie", pGame->pGameDir, 256, 256, 0.2f ); SETUP_PANEL( m_pMovieImage ); m_pMovieImage->SetBounds( x, y, w, h ); m_pMovieImage->SetVisible( true ); V_snprintf( szFilename, sizeof( szFilename ), "%s\\thumb_%s.wmv", MOVIE_PATH, pGame->pGameDir ); if ( !g_pFullFileSystem->ReadFile( szFilename, NULL, m_MovieBuffer ) ) { m_MovieBuffer.Purge(); } // game title shadow pKV = pKVSettings->FindKey( "GameTitle" ); y = pKV->GetInt( "ypos" ); m_pTitleShadow = new CShadowLabel( this, "GameTitle", pGame->pName ); SETUP_PANEL( m_pTitleShadow ); m_pTitleShadow->SetVisible( true ); m_pTitleShadow->SetFont( pSourceScheme->GetFont( "AppChooserGameTitleFontBlur" ) ); m_pTitleShadow->SizeToContents(); m_pTitleShadow->SetPos( 0, y ); m_pTitleShadow->SetWide( panelWidth ); m_pTitleShadow->SetContentAlignment( vgui::Label::a_center ); m_pTitleShadow->SetPaintBackgroundEnabled( false ); m_pTitleShadow->SetFgColor( Color( 0, 0, 0, 255 ) ); // game title m_pTitle = new vgui::Label( this, "GameTitle", pGame->pName ); SETUP_PANEL( m_pTitle ); m_pTitle->SetVisible( true ); m_pTitle->SetFont( pSourceScheme->GetFont( "AppChooserGameTitleFont" ) ); m_pTitle->SizeToContents(); m_pTitle->SetPos( 0, y ); m_pTitle->SetWide( panelWidth ); m_pTitle->SetContentAlignment( vgui::Label::a_center ); m_pTitle->SetPaintBackgroundEnabled( false ); m_pTitle->SetFgColor( Color( 255, 255, 255, 255 ) ); // button bounds vgui::HFont hFont = pSourceScheme->GetFont( "GameUIButtons" ); pKV = pKVSettings->FindKey( "GameButton" ); y = panelHeight - vgui::surface()->GetFontTall( hFont ) - pKV->GetInt( "ypos" ); m_pButtonText = new vgui::Label( this, "GameButton", g_pVGuiLocalize->Find( "#GameUI_Icons_A_BUTTON" ) ); SETUP_PANEL( m_pButtonText ); m_pButtonText->SetVisible( false ); m_pButtonText->SetFont( hFont ); m_pButtonText->SizeToContents(); m_pButtonText->SetPos( 0, y ); m_pButtonText->SetWide( panelWidth ); m_pButtonText->SetContentAlignment( vgui::Label::a_center ); m_pButtonText->SetPaintBackgroundEnabled( false ); m_pButtonText->SetFgColor( Color( 255, 255, 255, 255 ) ); } ~CGamePanel( void ) { } void SetSelected( bool bSelected ) { m_pButtonText->SetVisible( m_bEnabled ? bSelected : false ); if ( bSelected ) { SetBgColor( Color( 190, 115, 0, 128 ) ); m_pThumbImage->SetColor( 255, 255, 255 ); m_pMovieImage->SetColor( 255, 255, 255 ); m_pTitle->SetFgColor( Color( 255, 255, 255, 255 ) ); } else { SetBgColor( Color( 160, 160, 160, 50 ) ); m_pThumbImage->SetColor( 100, 100, 100 ); m_pMovieImage->SetColor( 120, 120, 120 ); m_pTitle->SetFgColor( Color( 140, 140, 140, 255 ) ); } } void StartMovie() { m_pMovieImage->StartMovieFromMemory( m_MovieBuffer.Base(), m_MovieBuffer.TellMaxPut(), true, true ); } void StopMovie() { m_pMovieImage->StopMovie(); } CImage *m_pThumbImage; CMovieImage *m_pMovieImage; CShadowLabel *m_pTitleShadow; vgui::Label *m_pTitle; vgui::Label *m_pButtonText; CUtlBuffer m_MovieBuffer; bool m_bEnabled; int m_imageID; }; //----------------------------------------------------------------------------- // Simple XAudio wrapper to instance a sound. Provides lightweight audio feedback for ui. // Instance this per sound. //----------------------------------------------------------------------------- class CXSound { public: CXSound() { for ( int i=0; iReadFile( pFilename, "GAME", buf ) ) { Msg( "SetupWave: File '%s' not found\n", pFilename ); return false; } // only supporting xwv format xwvHeader_t* pHeader = (xwvHeader_t *)buf.Base(); if ( pHeader->id != XWV_ID || pHeader->version != XWV_VERSION || pHeader->format != XWV_FORMAT_XMA ) { Msg( "SetupWave: File '%s' has bad format\n", pFilename ); return false; } XAUDIOSOURCEVOICEINIT SourceVoiceInit = { 0 }; SourceVoiceInit.Format.SampleType = XAUDIOSAMPLETYPE_XMA; SourceVoiceInit.Format.NumStreams = 1; SourceVoiceInit.MaxPacketCount = 1; SourceVoiceInit.Format.Stream[0].SampleRate = pHeader->GetSampleRate(); SourceVoiceInit.Format.Stream[0].ChannelCount = pHeader->channels; // create enough source voices to support simultaneous play of this sound HRESULT hr; numSimultaneous = min( numSimultaneous, ARRAYSIZE( m_pSourceVoices ) ); for ( int i=0; idataSize; m_pXMAData = (unsigned char *)XPhysicalAlloc( m_nXMADataSize, MAXULONG_PTR, 0, PAGE_READWRITE ); V_memcpy( m_pXMAData, (unsigned char *)buf.Base() + pHeader->dataOffset, m_nXMADataSize ); return true; } bool IsPlaying() { XAUDIOSOURCESTATE SourceState; for ( int i=0; iGetVoiceState( &SourceState ); if ( SourceState & XAUDIOSOURCESTATE_STARTED ) { return true; } } return false; } void Play() { int numPlaying = 0; XAUDIOSOURCESTATE SourceState; for ( int i=0; iGetVoiceState( &SourceState ); if ( SourceState & XAUDIOSOURCESTATE_STARTED ) { numPlaying++; } } if ( numPlaying >= m_numVoices ) { return; } // find a free voice IXAudioSourceVoice *pVoice = NULL; for ( int i=0; iGetVoiceState( &SourceState ); if ( !( SourceState & XAUDIOSOURCESTATE_STARTED ) ) { // use the free voice pVoice = m_pSourceVoices[i]; break; } } if ( !pVoice ) { // none free return; } // Set up packet XAUDIOPACKET Packet = { 0 }; Packet.pBuffer = m_pXMAData; Packet.BufferSize = m_nXMADataSize; // Submit packet HRESULT hr = pVoice->SubmitPacket( &Packet, XAUDIOSUBMITPACKET_DISCONTINUITY ); if ( !FAILED( hr ) ) { pVoice->Start( 0 ); } } void Stop() { for ( int i=0; iStop( 0 ); } } void Release() { for ( int i=0; iRelease(); m_pSourceVoices[i] = NULL; } } if ( m_pXMAData ) { XPhysicalFree( m_pXMAData ); m_pXMAData = NULL; } m_nXMADataSize = 0; m_numVoices = 0; } private: IXAudioSourceVoice *m_pSourceVoices[4]; unsigned char *m_pXMAData; int m_nXMADataSize; int m_numVoices; }; //----------------------------------------------------------------------------- // The application object //----------------------------------------------------------------------------- class CAppChooser : public CVguiSteamApp { typedef CVguiSteamApp BaseClass; public: virtual bool Create(); virtual bool PreInit(); virtual int Main(); virtual void Destroy(); void OnSelectionPrevious(); void OnSelectionNext(); void OnActivateGame(); void OnInactivityTimeout(); void OnStopDemoMovie(); void OnDemoMovieEnd(); void OnStopStartupMovie(); void Reset(); void ResetTimeout( float timeout ); bool IsInputEnabled() { return m_bInputEnabled; } bool IsDemoMoviePlaying() { return m_bPlayingDemoMovie; } bool IsStartupMoviePlaying() { return m_bPlayingStartupMovies; } void HandleInvite( DWORD nUserId ); private: const char *GetAppName() { return "AppChooser"; } bool CreateWindow( int width, int height, bool fullscreen ); bool InitMaterialSystem(); bool InitVGUI(); void ShutdownVGUI(); bool InitAudio(); void ShutdownAudio(); void FrameTick(); void RenderScene(); void ExitChooser(); void EnableInput( bool bEnable ); void StartExitingProcess(); void SetLoadingIconPosition( bool bIsMultiplayer ); int m_nScreenWidth; int m_nScreenHeight; HWND m_hWnd; float m_FadeInTime; float m_FadeOutTime; float m_StartTime; float m_Timeout; int m_Selection; int m_LastSelection; bool m_bInputEnabled; int m_ExitingFrameCount; float m_GameMovieStartTime; float m_BackgroundMovieStartTime; int m_LastBackgroundMovie; int m_StartupMovie; // various operating states bool m_bPlayingStartupMovies; bool m_bPlayingDemoMovie; bool m_bExiting; // Live invite handling bool m_bInviteAccepted; XNKID m_InviteSessionID; DWORD m_nInviteUserID; int m_iImageID; vgui::Panel *m_pRootPanel; CImage *m_pPersistedImage; CImage *m_pOverlay; CImage *m_pFullBlack; CMovieImage *m_pStartupMovieImage; CMovieImage *m_pBackgroundMovie; CMovieImage *m_pDemoMovieImage; CGamePanel *m_pGames[ARRAYSIZE( g_Games )]; CImage *m_pProductTitle; CShadowLabel *m_pInstructionsShadow; vgui::Label *m_pInstructions; vgui::Label *m_pLoading; CImage *m_pLoadingIcon; CImage *m_pProductBackgrounds[ARRAYSIZE( g_Games )]; DWORD m_iStorageDeviceID; DWORD m_iUserIdx; KeyValues *m_pKVSettings; int m_DemoMovie; CXSound m_Click; CXSound m_Clack; CXSound m_Deny; }; CAppChooser g_AppChooserSystem; //-------------------------------------------------------------------------------------- // InitMaterialSystem // //-------------------------------------------------------------------------------------- bool CAppChooser::InitMaterialSystem() { RECT rect; MaterialSystem_Config_t config; config.SetFlag( MATSYS_VIDCFG_FLAGS_WINDOWED, IsPC() ? true : false ); config.SetFlag( MATSYS_VIDCFG_FLAGS_NO_WAIT_FOR_VSYNC, 0 ); config.m_VideoMode.m_Width = 0; config.m_VideoMode.m_Height = 0; config.m_VideoMode.m_Format = IMAGE_FORMAT_BGRX8888; config.m_VideoMode.m_RefreshRate = 0; config.dxSupportLevel = IsX360() ? 98 : 0; g_pMaterialSystem->ModInit(); bool modeSet = g_pMaterialSystem->SetMode( m_hWnd, config ); if ( !modeSet ) { Error( "Failed to set mode\n" ); return false; } g_pMaterialSystem->OverrideConfig( config, false ); GetClientRect( m_hWnd, &rect ); m_nScreenWidth = rect.right; m_nScreenHeight = rect.bottom; return true; } void CAppChooser::SetLoadingIconPosition( bool bIsMultiplayer ) { // matches config from matsys_interface.cpp::InitStartupScreen() float flNormalizedX; float flNormalizedY; float flNormalizedSize; if ( !bIsMultiplayer ) { flNormalizedX = 0.5f; flNormalizedY = 0.86f; flNormalizedSize = 0.1f; } else { flNormalizedX = 0.5f; flNormalizedY = 0.9f; flNormalizedSize = 0.1f; } // matches calcs from CShaderDeviceDx8::RefreshFrontBufferNonInteractive() float flXPos = flNormalizedX; float flYPos = flNormalizedY; float flHeight = flNormalizedSize; int nSize = m_nScreenHeight * flHeight; int x = m_nScreenWidth * flXPos - nSize * 0.5f; int y = m_nScreenHeight * flYPos - nSize * 0.5f; int w = nSize; int h = nSize; m_pLoadingIcon->SetBounds( x, y, w, h ); } //----------------------------------------------------------------------------- // Setup all our VGUI info //----------------------------------------------------------------------------- bool CAppChooser::InitVGUI( void ) { int x, y, w, h, g; KeyValues *pKV; vgui::surface()->GetScreenSize( w, h ); float aspectRatio = (float)w/(float)h; bool bIsWidescreen = ( aspectRatio >= 1.7f ); bool bIsHiDef = h > 480; const char *pResolutionKey = ""; if ( bIsWidescreen ) { // 16:9 aspect if ( bIsHiDef ) { pResolutionKey = "_hidef"; } else { pResolutionKey = "_lodef_wide"; } } else { // 4:3 apsect if ( bIsHiDef ) { pResolutionKey = "_hidef_norm"; } else { pResolutionKey = "_lodef"; } } // Start vgui vgui::ivgui()->Start(); vgui::ivgui()->SetSleep( false ); // load the scheme vgui::scheme()->LoadSchemeFromFile( "resource/sourcescheme.res", NULL ); vgui::HScheme hScheme = vgui::scheme()->GetScheme( "SourceScheme" ); vgui::IScheme *pSourceScheme = vgui::scheme()->GetIScheme( hScheme ); m_pKVSettings = new KeyValues( "AppChooser.res" ); if ( m_pKVSettings->LoadFromFile( g_pFullFileSystem, "resource/UI/AppChooser.res", "GAME" ) ) { m_pKVSettings->ProcessResolutionKeys( pResolutionKey ); } else { return false; } // localization g_pVGuiLocalize->AddFile( "resource/gameui_%language%.txt" ,"GAME", true ); // Init the root panel m_pRootPanel = new vgui::Panel( NULL, "RootPanel" ); m_pRootPanel->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pRootPanel->SetPaintBackgroundEnabled( true ); m_pRootPanel->SetVisible( true ); vgui::surface()->SetEmbeddedPanel( m_pRootPanel->GetVPanel() ); // need a full pure opaque black before all panel drawing // this fixes the top of frame xmv video render work m_pFullBlack = new CImage( m_pRootPanel, "FullBlack", "vgui/black" ); SETUP_PANEL( m_pFullBlack ); m_pFullBlack->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pFullBlack->SetVisible( true ); // all the background movies were authored for widescreen m_pBackgroundMovie = new CMovieImage( m_pRootPanel, "Movie", "Background", m_nScreenWidth, m_nScreenHeight, 0.0f ); SETUP_PANEL( m_pBackgroundMovie ); m_pBackgroundMovie->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pBackgroundMovie->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, false ); m_pBackgroundMovie->SetColor( 255, 255, 255 ); m_pBackgroundMovie->SetVisible( true ); pKV = m_pKVSettings->FindKey( "Logo" ); y = pKV->GetInt( "ypos" ); w = pKV->GetInt( "wide" ); h = pKV->GetInt( "tall" ); m_pProductTitle = new CImage( m_pRootPanel, "Logo", "vgui/appchooser/orangeboxlogo" ); SETUP_PANEL( m_pProductTitle ); m_pProductTitle->SetBounds( ( m_nScreenWidth - w )/2, y, w, h ); m_pProductTitle->SetVisible( true ); wchar_t *pString = g_pVGuiLocalize->Find( "#GameUI_AppChooser_SelectGame" ); pKV = m_pKVSettings->FindKey( "SelectGame" ); y = pKV->GetInt( "ypos" ); m_pInstructionsShadow = new CShadowLabel( m_pRootPanel, "SelectGame", pString ); SETUP_PANEL( m_pInstructionsShadow ); m_pInstructionsShadow->SetFont( pSourceScheme->GetFont( "ChapterTitleBlur" ) ); m_pInstructionsShadow->SizeToContents(); m_pInstructionsShadow->SetWide( m_nScreenWidth ); m_pInstructionsShadow->SetPos( 0, y ); m_pInstructionsShadow->SetContentAlignment( vgui::Label::a_center ); m_pInstructionsShadow->SetPaintBackgroundEnabled( false ); m_pInstructionsShadow->SetFgColor( Color( 0, 0, 0, 255) ); m_pInstructions = new vgui::Label( m_pRootPanel, "SelectGame", pString ); SETUP_PANEL( m_pInstructions ); m_pInstructions->SetFont( pSourceScheme->GetFont( "ChapterTitle" ) ); m_pInstructions->SizeToContents(); m_pInstructions->SetWide( m_nScreenWidth ); m_pInstructions->SetPos( 0, y ); m_pInstructions->SetContentAlignment( vgui::Label::a_center ); m_pInstructions->SetPaintBackgroundEnabled( false ); m_pInstructions->SetFgColor( Color( 255, 255, 255, 255) ); pKV = m_pKVSettings->FindKey( "GamePanel" ); w = pKV->GetInt( "wide" ); g = pKV->GetInt( "gap" ); x = ( m_nScreenWidth - ( (int)( ARRAYSIZE( g_Games ) ) * ( w + g ) - g ) ) / 2; y = pKV->GetInt( "ypos" ); for ( int i=0; iSetPos( x, y ); m_pGames[i]->SetPaintBackgroundType( 2 ); m_pGames[i]->SetSelected( false ); x += w + g; } // the product backgrounds are topmost and used as fade out materials for ( int i=0; iSetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pProductBackgrounds[i]->SetVisible( true ); m_pProductBackgrounds[i]->SetAlpha( 0 ); } pString = g_pVGuiLocalize->Find( "#GameUI_Loading" ); y = m_nScreenHeight / 2; m_pLoading = new vgui::Label( m_pRootPanel, "Text", pString ); SETUP_PANEL( m_pLoading ); m_pLoading->SetFont( pSourceScheme->GetFont( "ChapterTitle" ) ); m_pLoading->SizeToContents(); m_pLoading->SetWide( m_nScreenWidth ); m_pLoading->SetPos( 0, y ); m_pLoading->SetContentAlignment( vgui::Label::a_center ); m_pLoading->SetPaintBackgroundEnabled( false ); m_pLoading->SetFgColor( Color( 255, 255, 255, 255) ); m_pLoading->SetVisible( false ); m_pLoadingIcon = new CImage( m_pRootPanel, "LoadingIcon", "vgui/appchooser/loading_icon" ); SETUP_PANEL( m_pLoadingIcon ); SetLoadingIconPosition( false ); m_pLoadingIcon->SetVisible( false ); m_pLoadingIcon->SetAlpha( 0 ); // create back buffer cloned texture ITexture *pTexture = NULL; if ( XboxLaunch()->GetLaunchFlags() & LF_INTERNALLAUNCH ) { pTexture = g_pMaterialSystem->CreateProceduralTexture( "PersistedTexture", TEXTURE_GROUP_OTHER, m_nScreenWidth, m_nScreenHeight, g_pMaterialSystem->GetBackBufferFormat(), TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); // the persisted texture is in the back buffer, get it CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); pRenderContext->CopyRenderTargetToTexture( pTexture ); } // create a material to bind the persisted texture // the fade-in material is topmost, fully opaque, and fades to transluscent on initial rendering KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); pVMTKeyValues->SetInt( "$vertexcolor", 1 ); pVMTKeyValues->SetInt( "$vertexalpha", 1 ); pVMTKeyValues->SetInt( "$linearwrite", 1 ); // These 2 lines are needed so that we don't lose bits of precision in pVMTKeyValues->SetInt( "$gammacolorread", 1 ); // the dark colors due to the 360's lossy sRGB read hardware pVMTKeyValues->SetInt( "$ignorez", 1 ); if ( pTexture ) { pVMTKeyValues->SetString( "$basetexture", "PersistedTexture" ); } else { // there is no persisted texture, fade in from black pVMTKeyValues->SetString( "$basetexture", "vgui/black" ); } IMaterial *pFadeInMaterial = g_pMaterialSystem->CreateMaterial( "__FadeInMaterial.vmt", pVMTKeyValues ); // the persisted image is either the image from the relaunch or black during first boot m_pPersistedImage = new CImage( m_pRootPanel, "FadeInOverlay", pFadeInMaterial ); SETUP_PANEL( m_pPersistedImage ); m_pPersistedImage->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pPersistedImage->SetVisible( true ); m_pPersistedImage->SetAlpha( 255 ); m_pOverlay = m_pPersistedImage; // full screen demo movie, use letterboxing m_pDemoMovieImage = new CMovieImage( m_pRootPanel, "Movie", "Demo", m_nScreenWidth, m_nScreenHeight, 2.0f ); SETUP_PANEL( m_pDemoMovieImage ); m_pDemoMovieImage->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pDemoMovieImage->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, true ); m_pDemoMovieImage->SetVisible( true ); // full screen startup movies, use letterboxing, instant start m_pStartupMovieImage = new CMovieImage( m_pRootPanel, "Movie", "Startup", m_nScreenWidth, m_nScreenHeight, 0.0f ); SETUP_PANEL( m_pStartupMovieImage ); m_pStartupMovieImage->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight ); m_pStartupMovieImage->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, true ); m_pStartupMovieImage->SetVisible( true ); return true; } //----------------------------------------------------------------------------- // Stop VGUI //----------------------------------------------------------------------------- void CAppChooser::ShutdownVGUI( void ) { delete m_pRootPanel; } //----------------------------------------------------------------------------- // Initialize Audio //----------------------------------------------------------------------------- bool CAppChooser::InitAudio() { // Set up initialize parameters of XAudio Engine // Both threads on core 2 XAUDIOENGINEINIT EngineInit = { 0 }; EngineInit.pEffectTable = &XAudioDefaultEffectTable; EngineInit.ThreadUsage = XAUDIOTHREADUSAGE_THREAD4 | XAUDIOTHREADUSAGE_THREAD5; // Initialize the XAudio Engine HRESULT hr = XAudioInitialize( &EngineInit ); if ( FAILED( hr ) ) { Error( "Error calling XAudioInitialize\n" ); } m_Click.SetupWave( "sound/ui/buttonclick.360.wav", 2 ); m_Clack.SetupWave( "sound/ui/buttonclickrelease.360.wav", 2 ); m_Deny.SetupWave( "sound/player/suit_denydevice.360.wav", 2 ); return true; } void CAppChooser::ShutdownAudio() { // Shut down and free XAudio resources XAudioShutDown(); } //-------------------------------------------------------------------------------------- // Reset timeout //-------------------------------------------------------------------------------------- void CAppChooser::ResetTimeout( float timeout ) { if ( timeout > 0 ) { m_Timeout = Plat_FloatTime() + timeout; } else { m_Timeout = 0; } } //-------------------------------------------------------------------------------------- // Intialization Reset. Expected to be called once after inits. //-------------------------------------------------------------------------------------- void CAppChooser::Reset() { // set selection to previous game or default m_Selection = 0; m_LastSelection = 0; const char *pGameName = CommandLine()->ParmValue( "-game", "ep2" ); for ( int i=0; iGetStorageID(); Msg( "Storage ID: %d", m_iStorageDeviceID ); // Set the user index m_iUserIdx = XboxLaunch()->GetUserID(); if ( g_pInputSystem ) { g_pInputSystem->SetPrimaryUserId( m_iUserIdx ); } XBX_SetPrimaryUserId( m_iUserIdx ); Msg( "User ID: %d", m_iUserIdx ); m_StartTime = Plat_FloatTime(); m_FadeInTime = 0; m_FadeOutTime = 0; m_bExiting = false; m_ExitingFrameCount = 0; m_bPlayingDemoMovie = false; m_DemoMovie = 0; // background movie and selection startup in sync m_LastBackgroundMovie = m_Selection; m_BackgroundMovieStartTime = 0; m_GameMovieStartTime = 0; EnableInput( true ); if ( !( XboxLaunch()->GetLaunchFlags() & LF_INTERNALLAUNCH ) ) { // first time boot // startup movies play first, and never again ResetTimeout( 0 ); m_StartupMovie = -1; m_bPlayingStartupMovies = true; } else { // normal background startup ResetTimeout( INACTIVITY_TIMEOUT ); // foreground movie starts a little staggered m_BackgroundMovieStartTime = Plat_FloatTime(); m_GameMovieStartTime = m_BackgroundMovieStartTime + 1.0f; } // Init our invite data m_bInviteAccepted = false; m_nInviteUserID = XBX_INVALID_USER_ID; memset( (void *)&m_InviteSessionID, 0, sizeof( m_InviteSessionID ) ); } //-------------------------------------------------------------------------------------- // Handle previous selection //-------------------------------------------------------------------------------------- void CAppChooser::OnSelectionPrevious() { // backward wraparound m_Selection = ( m_Selection + ARRAYSIZE( g_Games ) - 1 ) % ARRAYSIZE( g_Games ); m_Click.Play(); } //-------------------------------------------------------------------------------------- // Handle next selection //-------------------------------------------------------------------------------------- void CAppChooser::OnSelectionNext() { // forward wraparound m_Selection = ( m_Selection + ARRAYSIZE( g_Games ) + 1 ) % ARRAYSIZE( g_Games ); m_Click.Play(); } void CAppChooser::EnableInput( bool bEnable ) { m_bInputEnabled = bEnable; } //-------------------------------------------------------------------------------------- // Chooser exits //-------------------------------------------------------------------------------------- void CAppChooser::ExitChooser() { // reset stale arguments that encode prior game // launcher will establish correct arguments based on desired game CommandLine()->RemoveParm( "-game" ); CommandLine()->AppendParm( "-game", g_Games[m_Selection].pGameDir ); // Special command line parameter for tf. Command line args persist across // relaunches, so remove it first in case we came from tf and are going to another game. CommandLine()->RemoveParm( "-swapcores" ); if ( !Q_stricmp( g_Games[m_Selection].pGameDir, "tf" ) ) { CommandLine()->AppendParm( "-swapcores", NULL ); } int fFlags = LF_EXITFROMCHOOSER; // allocate the full payload int nPayloadSize = XboxLaunch()->MaxPayloadSize(); byte *pPayload = (byte *)stackalloc( nPayloadSize ); V_memset( pPayload, 0, nPayloadSize ); // payload is at least the command line // any user data needed must be placed AFTER the command line const char *pCmdLine = CommandLine()->GetCmdLine(); int nCmdLineLength = (int)strlen( pCmdLine ) + 1; V_memcpy( pPayload, pCmdLine, min( nPayloadSize, nCmdLineLength ) ); // add any other data here to payload, after the command line // ... XboxLaunch()->SetStorageID( m_iStorageDeviceID ); m_iUserIdx = XBX_GetPrimaryUserId(); if ( m_bInviteAccepted ) { // A potentially different user was invited, so we need to connect them m_iUserIdx = m_nInviteUserID; } XboxLaunch()->SetUserID( m_iUserIdx ); if ( m_bInviteAccepted ) { // In the case of an invitation acceptance, we need to pack extra data into the payload fFlags |= LF_INVITERESTART; XboxLaunch()->SetInviteSessionID( &m_InviteSessionID ); } // Save out the data bool bLaunch = XboxLaunch()->SetLaunchData( (void *)pPayload, nPayloadSize, fFlags ); if ( bLaunch ) { COM_TimestampedLog( "Launching: \"%s\" Flags: 0x%8.8x", pCmdLine, XboxLaunch()->GetLaunchFlags() ); g_pMaterialSystem->PersistDisplay(); ShutdownVGUI(); ShutdownAudio(); XBX_DisconnectConsoleMonitor(); XboxLaunch()->Launch(); } } //-------------------------------------------------------------------------------------- // Handle game selection //-------------------------------------------------------------------------------------- void CAppChooser::OnActivateGame() { if ( !m_FadeInTime || Plat_FloatTime() <= m_FadeInTime + 1.0f ) { // lockout user selection input until screen has stable rendering and completed its fade in // prevents button mashing doing an immediate game selection just as the startup movies end return; } if ( !g_Games[m_Selection].bEnabled ) { m_Deny.Play(); return; } m_Clack.Play(); while ( m_Clack.IsPlaying() ) { // let the audio complete Sleep( 1 ); } StartExitingProcess(); } void CAppChooser::OnDemoMovieEnd() { g_AppChooserSystem.ResetTimeout( INACTIVITY_TIMEOUT ); m_bPlayingDemoMovie = false; } void DemoMovieCallback() { g_AppChooserSystem.OnDemoMovieEnd(); } //-------------------------------------------------------------------------------------- // Handle inactivity event //-------------------------------------------------------------------------------------- void CAppChooser::OnInactivityTimeout() { // no further inactivity timeouts ResetTimeout( 0 ); const char *pDemoMovieName = g_DemoMovies[m_DemoMovie]; m_DemoMovie++; if ( m_DemoMovie >= ARRAYSIZE( g_DemoMovies ) ) { // reset m_DemoMovie = 0; } m_bPlayingDemoMovie = m_pDemoMovieImage->StartMovieFromFile( pDemoMovieName, true, false, DemoMovieCallback ); if ( !m_bPlayingDemoMovie ) { // try again, later ResetTimeout( INACTIVITY_TIMEOUT ); } } //-------------------------------------------------------------------------------------- // Handle demo stop request //-------------------------------------------------------------------------------------- void CAppChooser::OnStopDemoMovie() { m_pDemoMovieImage->StopMovie(); } //-------------------------------------------------------------------------------------- // Handle startup stop request //-------------------------------------------------------------------------------------- void CAppChooser::OnStopStartupMovie() { if ( m_StartupMovie >= 0 && g_StartupMovies[m_StartupMovie].bUserCanSkip ) { m_pStartupMovieImage->StopMovie(); } } //-------------------------------------------------------------------------------------- // Setup the exiting context. Cannot be aborted. //-------------------------------------------------------------------------------------- void CAppChooser::StartExitingProcess() { bool bIsMultiplayer = V_stricmp( g_Games[m_Selection].pGameDir, "tf" ) == 0; // stable rendering, start the fade out m_pOverlay = m_pProductBackgrounds[m_Selection]; m_pLoading->SetVisible( true ); m_pLoading->SetAlpha( 0 ); SetLoadingIconPosition( bIsMultiplayer ); m_pLoadingIcon->SetVisible( true ); m_pLoadingIcon->SetAlpha( 0 ); m_FadeOutTime = Plat_FloatTime(); m_bExiting = true; m_ExitingFrameCount = 0; // ensure that nothing can stop the exiting process // otherwise the player could rapidly select a game during the fade outs ResetTimeout( 0 ); EnableInput( false ); } //-------------------------------------------------------------------------------------- // Handle per frame update, prior to render //-------------------------------------------------------------------------------------- void CAppChooser::FrameTick() { if ( m_ExitingFrameCount ) { return; } g_TargetMovieVolume = 1.0f; BOOL bControl; if ( ERROR_SUCCESS == XMPTitleHasPlaybackControl(&bControl) ) { g_TargetMovieVolume = bControl ? 1.0f : 0.0f; } if ( m_FadeInTime ) { // fade in overlay goes from [1..0] and holds on 0 float t = ( Plat_FloatTime() - m_FadeInTime ) * 2.0f; t = 1.0f - clamp( t, 0.0f, 1.0f ); m_pOverlay->SetAlpha ( t * 255.0f ); } if ( m_FadeOutTime ) { // fade out overlay goes from [0..1] and holds on 1 float t = ( Plat_FloatTime() - m_FadeOutTime ) * 2.0f; t = clamp( t, 0.0f, 1.0f ); m_pOverlay->SetAlpha ( t * 255.0f ); } for ( int i=0; iSetSelected( false ); } m_pGames[m_Selection]->SetSelected( true ); if ( m_bExiting ) { m_pLoading->SetAlpha( m_pOverlay->GetAlpha() ); m_pLoadingIcon->SetAlpha( m_pOverlay->GetAlpha() ); if ( m_pOverlay->GetAlpha() == 255 ) { // exiting needs to have fade overlay fully opaque before stopping background movie m_pBackgroundMovie->StopMovie(); // exiting commences after all movies full release and enough frames have swapped to ensure stability // strict time (frame rate) cannot be trusted, must allow a non trivial amount of presents m_ExitingFrameCount = 30; } } if ( m_bPlayingStartupMovies ) { if ( m_pStartupMovieImage->IsFullyReleased() ) { m_StartupMovie++; if ( m_StartupMovie >= ARRAYSIZE( g_StartupMovies ) ) { // end of cycle m_StartupMovie = -1; m_bPlayingStartupMovies = false; } else { m_bPlayingStartupMovies = m_pStartupMovieImage->StartMovieFromFile( g_StartupMovies[m_StartupMovie].pMovieName, true, false ); } } if ( !m_bPlayingStartupMovies ) { ResetTimeout( INACTIVITY_TIMEOUT ); m_BackgroundMovieStartTime = Plat_FloatTime(); m_GameMovieStartTime = m_BackgroundMovieStartTime + 1.0f; } else { return; } } if ( m_bInviteAccepted ) { // stop attract mode if ( m_bPlayingDemoMovie ) { // hint the demo movie to stop OnStopDemoMovie(); } // attract movies must finish before allowing invite // background movies must finish before allowing invite // starts the exiting process ONCE if ( !m_bPlayingStartupMovies && !m_bPlayingDemoMovie && !m_bExiting ) { for ( int i = 0; i < ARRAYSIZE( g_Games ); i++ ) { if ( V_stristr( "tf", g_Games[i].pGameDir ) ) { // EVIL! spoof a user slection to invite only TF m_Selection = i; StartExitingProcess(); // must fixup state, invite could have happened very early, at any time, before the initial fade-in // ensure no movies start m_BackgroundMovieStartTime = 0; m_GameMovieStartTime = 0; // hack the fade times to match the expected state if ( !m_FadeInTime || m_FadeInTime >= Plat_FloatTime() ) { // can't fade out, invite occured before fade-in // can only snap it because there is no background to fade from m_pPersistedImage->SetAlpha( 0 ); m_FadeInTime = 0; m_FadeOutTime = 0; m_pOverlay->SetAlpha( 255 ); } break; } } } } if ( m_bExiting || m_bPlayingDemoMovie || m_LastSelection != m_Selection ) { m_pGames[m_LastSelection]->StopMovie(); m_LastSelection = m_Selection; // user can change selection very quickly // lag the movie starting until selection settles m_GameMovieStartTime = Plat_FloatTime() + 2.0f; } if ( !m_bExiting && !m_bPlayingDemoMovie && ( m_GameMovieStartTime != 0 ) && ( Plat_FloatTime() >= m_GameMovieStartTime ) ) { // keep trying the current selection, it will eventually start m_pGames[m_Selection]->StartMovie(); if ( m_LastBackgroundMovie != m_Selection ) { m_pBackgroundMovie->StopMovie(); m_LastBackgroundMovie = m_Selection; m_BackgroundMovieStartTime = Plat_FloatTime() + 2.0f; } } // update the background movie, lags far behind any user selection if ( !m_bExiting && !m_bPlayingDemoMovie && ( m_BackgroundMovieStartTime != 0 ) && ( Plat_FloatTime() >= m_BackgroundMovieStartTime ) ) { char szFilename[MAX_PATH]; V_snprintf( szFilename, sizeof( szFilename ), "background_%s.wmv", g_Games[m_Selection].pGameDir ); // keep trying the current selection, it will eventually start bool bStarted = m_pBackgroundMovie->StartMovieFromFile( szFilename, false, true ); if ( bStarted ) { m_LastBackgroundMovie = m_Selection; } } } //-------------------------------------------------------------------------------------- // Render the scene //-------------------------------------------------------------------------------------- void CAppChooser::RenderScene() { FrameTick(); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); g_pMaterialSystem->BeginFrame( Plat_FloatTime() ); pRenderContext->ClearColor3ub( 0, 0, 0 ); pRenderContext->ClearBuffers( true, true ); pRenderContext->Flush( true ); // render all movies, XMV needs to hijack D3D bool bPreviousState = g_pMaterialSystem->OwnGPUResources( false ); { m_pStartupMovieImage->RenderVideoFrame(); m_pBackgroundMovie->RenderVideoFrame(); m_pDemoMovieImage->RenderVideoFrame(); for ( int i=0; im_pMovieImage->RenderVideoFrame(); } } g_pMaterialSystem->OwnGPUResources( bPreviousState ); pRenderContext->Viewport( 0, 0, m_nScreenWidth, m_nScreenHeight ); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->LoadIdentity(); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->LoadIdentity(); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->LoadIdentity(); pRenderContext->Flush( true ); vgui::ivgui()->RunFrame(); vgui::surface()->PaintTraverseEx( vgui::surface()->GetEmbeddedPanel() ); g_pMaterialSystem->EndFrame(); g_pMaterialSystem->SwapBuffers(); if ( !m_FadeInTime && !m_bPlayingStartupMovies && !m_bExiting ) { // stable rendering, start the fade in m_FadeInTime = Plat_FloatTime() + 1.0f; } // check for timeout if ( m_Timeout && ( Plat_FloatTime() >= m_Timeout ) ) { m_Timeout = 0; OnInactivityTimeout(); } if ( m_ExitingFrameCount > 1 && m_pBackgroundMovie->IsFullyReleased() && m_pGames[m_Selection]->m_pMovieImage->IsFullyReleased() ) { // must wait for a few frames to settle to ensure frame buffer is stable m_ExitingFrameCount--; if ( m_ExitingFrameCount == 1 ) { ExitChooser(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAppChooser::HandleInvite( DWORD nUserId ) { // invites are asynchronous and could happen at any time if ( m_bInviteAccepted ) { // already accepted return; } // Collect our session data XINVITE_INFO inviteInfo; DWORD dwError = XInviteGetAcceptedInfo( nUserId, &inviteInfo ); if ( dwError != ERROR_SUCCESS ) { return; } // We only care if we're asked to join an Orange Box session if ( inviteInfo.dwTitleID != TITLEID_THE_ORANGE_BOX ) { return; } // Store off the session ID and mark the invite as accepted internally m_bInviteAccepted = true; m_InviteSessionID = inviteInfo.hostInfo.sessionID; m_nInviteUserID = nUserId; // must wait for the startup movies or attract mode to end // FrameTick() will detect the invite and transition // the user has accpeted and cannot abort the invite EnableInput( false ); ResetTimeout( 0 ); } //-------------------------------------------------------------------------------------- // Window Proc //-------------------------------------------------------------------------------------- LRESULT CALLBACK WndProc( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam ) { switch ( iMsg ) { case WM_CLOSE: g_bActive = false; break; case WM_DESTROY: PostQuitMessage( 0 ); return 0L; case WM_LIVE_INVITE_ACCEPTED: g_AppChooserSystem.HandleInvite( LOWORD( lParam ) ); break; case WM_XCONTROLLER_KEY: if ( !g_AppChooserSystem.IsInputEnabled() ) { break; } if ( g_AppChooserSystem.IsStartupMoviePlaying() ) { // some startup movies can be aborted switch ( wParam ) { case KEY_XBUTTON_A: case KEY_XBUTTON_START: if ( LOWORD( lParam ) ) { g_AppChooserSystem.OnStopStartupMovie(); } } // no other input is allowed during this state break; } if ( g_AppChooserSystem.IsDemoMoviePlaying() ) { // demo movies can be aborted switch ( wParam ) { case KEY_XBUTTON_A: case KEY_XBUTTON_START: if ( LOWORD( lParam ) ) { g_AppChooserSystem.OnStopDemoMovie(); } } } else { if ( LOWORD( lParam ) ) { g_AppChooserSystem.ResetTimeout( INACTIVITY_TIMEOUT ); } switch ( wParam ) { case KEY_XBUTTON_LEFT: case KEY_XSTICK1_LEFT: if ( LOWORD( lParam ) ) { g_AppChooserSystem.OnSelectionPrevious(); } break; case KEY_XBUTTON_RIGHT: case KEY_XSTICK1_RIGHT: if ( LOWORD( lParam ) ) { g_AppChooserSystem.OnSelectionNext(); } break; case KEY_XBUTTON_A: case KEY_XBUTTON_START: if ( LOWORD( lParam ) ) { g_AppChooserSystem.OnActivateGame(); } break; } } break; } return DefWindowProc( hWnd, iMsg, wParam, lParam ); } //-------------------------------------------------------------------------------------- // CreateWindow // //-------------------------------------------------------------------------------------- bool CAppChooser::CreateWindow( int width, int height, bool bFullScreen ) { HWND hWnd; WNDCLASSEX wndClass; DWORD dwStyle, dwExStyle; int x, y, sx, sy; if ( ( hWnd = FindWindow( GetAppName(), GetAppName() ) ) != NULL ) { SetForegroundWindow( hWnd ); return true; } wndClass.cbSize = sizeof( wndClass ); wndClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wndClass.lpfnWndProc = ::WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = (HINSTANCE)GetAppInstance(); wndClass.hIcon = 0; wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT; wndClass.lpszMenuName = NULL; wndClass.lpszClassName = GetAppName(); wndClass.hIconSm = 0; if ( !RegisterClassEx( &wndClass ) ) { Error( "Window class registration failed\n" ); return false; } if ( bFullScreen ) { dwExStyle = WS_EX_TOPMOST; dwStyle = WS_POPUP | WS_VISIBLE; } else { dwExStyle = 0; dwStyle = WS_CAPTION | WS_SYSMENU; } x = y = 0; sx = width; sy = height; hWnd = CreateWindowEx( dwExStyle, GetAppName(), // window class name GetAppName(), // window caption dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // window style x, // initial x position y, // initial y position sx, // initial x size sy, // initial y size NULL, // parent window handle NULL, // window menu handle (HINSTANCE)GetAppInstance(),// program instance handle NULL); // creation parameter if ( hWnd == NULL ) { return false; } m_hWnd = hWnd; return true; } //----------------------------------------------------------------------------- // Create //----------------------------------------------------------------------------- bool CAppChooser::Create() { AppSystemInfo_t appSystems[] = { { "filesystem_stdio.dll", QUEUEDLOADER_INTERFACE_VERSION }, { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION }, { "inputsystem.dll", INPUTSYSTEM_INTERFACE_VERSION }, { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, { "vguimatsurface.dll", VGUI_SURFACE_INTERFACE_VERSION }, { "", "" } }; MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); SpewOutputFunc( g_DefaultSpewFunc ); // Add in the cvar factory AppModule_t cvarModule = LoadModule( VStdLib_GetICVarFactory() ); AddSystem( cvarModule, CVAR_INTERFACE_VERSION ); // vxconsole - true will block (legacy behavior) XBX_InitConsoleMonitor( false ); if ( !AddSystems( appSystems ) ) return false; IMaterialSystem* pMaterialSystem = (IMaterialSystem*)FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION ); if ( !pMaterialSystem ) { Error( "Failed to find %s\n", MATERIAL_SYSTEM_INTERFACE_VERSION ); return false; } if ( IsX360() ) { IFileSystem* pFileSystem = (IFileSystem*)FindSystem( FILESYSTEM_INTERFACE_VERSION ); if ( !pFileSystem ) { Error( "Failed to find %s\n", FILESYSTEM_INTERFACE_VERSION ); return false; } pFileSystem->LoadModule( "shaderapidx9.dll" ); } pMaterialSystem->SetShaderAPI( "shaderapidx9.dll" ); return true; } //----------------------------------------------------------------------------- // PreInit //----------------------------------------------------------------------------- bool CAppChooser::PreInit() { if ( !BaseClass::PreInit() ) return false; if ( !g_pFullFileSystem || !g_pMaterialSystem ) { Warning( "Unable to find required interfaces!\n" ); return false; } // Add paths... if ( !SetupSearchPaths( NULL, false, true ) ) { Error( "Failed to setup search paths\n" ); return false; } // Create the main program window and our viewport int w = 640; int h = 480; if ( IsX360() ) { w = GetSystemMetrics( SM_CXSCREEN ); h = GetSystemMetrics( SM_CYSCREEN ); } if ( !CreateWindow( w, h, false ) ) { ChangeDisplaySettings( 0, 0 ); Error( "Unable to create main window\n" ); return false; } ShowWindow( m_hWnd, SW_SHOWNORMAL ); UpdateWindow( m_hWnd ); SetForegroundWindow( m_hWnd ); SetFocus( m_hWnd ); XOnlineStartup(); return true; } //----------------------------------------------------------------------------- // Destroy //----------------------------------------------------------------------------- void CAppChooser::Destroy() { XOnlineCleanup(); } //----------------------------------------------------------------------------- // Main //----------------------------------------------------------------------------- int CAppChooser::Main() { if ( !InitMaterialSystem() ) { return 0; } if ( !InitVGUI() ) { return 0; } if ( !InitAudio() ) { return 0; } // post initialization reset Reset(); // Setup a listener for invites XBX_NotifyCreateListener( XNOTIFY_LIVE ); MSG msg; while ( g_bActive == TRUE ) { // Pump the XBox notifications XBX_ProcessEvents(); while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } g_pInputSystem->PollInputState(); RenderScene(); } ShutdownVGUI(); ShutdownAudio(); Destroy(); return 0; } //----------------------------------------------------------------------------- // The entry point for the application //----------------------------------------------------------------------------- extern "C" __declspec(dllexport) int AppChooserMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { CommandLine()->CreateCmdLine( lpCmdLine ); SetAppInstance( hInstance ); CSteamApplication steamApplication( &g_AppChooserSystem ); steamApplication.Run(); return 0; }