//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //===========================================================================// #if defined( USE_SDL ) #undef PROTECTED_THINGS_ENABLE #include "SDL.h" #include "SDL_syswm.h" #if defined( OSX ) #define DONT_DEFINE_BOOL #include #endif #endif #if defined( WIN32 ) && !defined( _X360 ) && !defined( DX_TO_GL_ABSTRACTION ) #include "winlite.h" #include "xbox/xboxstubs.h" #endif #if defined( IS_WINDOWS_PC ) && !defined( USE_SDL ) #include #elif defined(_X360) // nothing to include for 360 #elif defined(OSX) #elif defined(LINUX) #include "tier0/dynfunction.h" #elif defined(_WIN32) #include "tier0/dynfunction.h" #else #error #endif #include "appframework/ilaunchermgr.h" #include "igame.h" #include "cl_main.h" #include "host.h" #include "quakedef.h" #include "tier0/vcrmode.h" #include "tier0/icommandline.h" #include "ivideomode.h" #include "gl_matsysiface.h" #include "cdll_engine_int.h" #include "vgui_baseui_interface.h" #include "iengine.h" #include "keys.h" #include "VGuiMatSurface/IMatSystemSurface.h" #include "tier3/tier3.h" #include "sound.h" #include "vgui_controls/Controls.h" #include "vgui_controls/MessageDialog.h" #include "sys_dll.h" #include "inputsystem/iinputsystem.h" #include "inputsystem/ButtonCode.h" #ifdef WIN32 #undef WIN32_LEAN_AND_MEAN #include "unicode/unicode.h" #endif #include "GameUI/IGameUI.h" #include "matchmaking.h" #include "sv_main.h" #include "video/ivideoservices.h" #include "sys.h" #include "materialsystem/imaterial.h" #if defined( _X360 ) #include "xbox/xbox_win32stubs.h" #include "hl2orange.spa.h" #endif #if defined( LINUX ) #include "snd_dev_sdl.h" #endif #ifdef DBGFLAG_ASSERT #define AssertExit( _exp ) Assert( _exp ) #define AssertExitF( _exp ) Assert( _exp ) #else #define AssertExit( _exp ) if ( !( _exp ) ) return; #define AssertExitF( _exp ) if ( !( _exp ) ) return false; #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" void S_BlockSound (void); void S_UnblockSound (void); void ClearIOStates( void ); //----------------------------------------------------------------------------- // Game input events //----------------------------------------------------------------------------- enum GameInputEventType_t { IE_Close = IE_FirstAppEvent, IE_WindowMove, IE_AppActivated, }; #ifdef WIN32 static IUnicodeWindows *unicode = NULL; #endif //----------------------------------------------------------------------------- // Purpose: Main game interface, including message pump and window creation //----------------------------------------------------------------------------- class CGame : public IGame { public: CGame( void ); virtual ~CGame( void ); bool Init( void *pvInstance ); bool Shutdown( void ); bool CreateGameWindow( void ); void DestroyGameWindow(); void SetGameWindow( void* hWnd ); // This is used in edit mode to override the default wnd proc associated w/ bool InputAttachToGameWindow(); void InputDetachFromGameWindow(); void PlayStartupVideos( void ); // This is the SDL_Window* under SDL, HWND otherwise. void* GetMainWindow( void ); // This will be the HWND under D3D + Windows (both with and without SDL), SDL_Window* everywhere else. void* GetMainDeviceWindow( void ); // This will be the HWND under Windows, the WindowRef under Mac, and (for now) NULL on Linux void* GetMainWindowPlatformSpecificHandle( void ); void** GetMainWindowAddress( void ); void GetDesktopInfo( int &width, int &height, int &refreshrate ); void SetWindowXY( int x, int y ); void SetWindowSize( int w, int h ); void GetWindowRect( int *x, int *y, int *w, int *h ); bool IsActiveApp( void ); void SetCanPostActivateEvents( bool bEnable ); bool CanPostActivateEvents(); public: #ifdef USE_SDL void SetMainWindow( SDL_Window* window ); #else #ifdef WIN32 int WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); #endif void SetMainWindow( HWND window ); #endif void SetActiveApp( bool active ); bool LoadUnicode(); void UnloadUnicode(); // Message handlers. public: void HandleMsg_WindowMove( const InputEvent_t &event ); void HandleMsg_ActivateApp( const InputEvent_t &event ); void HandleMsg_Close( const InputEvent_t &event ); // Call the appropriate HandleMsg_ function. void DispatchInputEvent( const InputEvent_t &event ); // Dispatch all the queued up messages. virtual void DispatchAllStoredGameMessages(); private: void AppActivate( bool fActive ); void PlayVideoAndWait( const char *filename, bool bNeedHealthWarning = false); // plays a video file and waits till it's done to return. Can be interrupted by user. private: void AttachToWindow(); void DetachFromWindow(); #ifndef _X360 static const wchar_t CLASSNAME[]; #else static const char CLASSNAME[]; #endif bool m_bExternallySuppliedWindow; #if defined( WIN32 ) HWND m_hWindow; #if !defined( USE_SDL ) HINSTANCE m_hInstance; // Stores a wndproc to chain message calls to WNDPROC m_ChainedWindowProc; RECT m_rcLastRestoredClientRect; #endif #endif #if defined( USE_SDL ) SDL_Window *m_pSDLWindow; #endif int m_x; int m_y; int m_width; int m_height; bool m_bActiveApp; CSysModule *m_hUnicodeModule; bool m_bCanPostActivateEvents; int m_iDesktopWidth, m_iDesktopHeight, m_iDesktopRefreshRate; void UpdateDesktopInformation(); #ifdef WIN32 void UpdateDesktopInformation( WPARAM wParam, LPARAM lParam ); #endif }; static CGame g_Game; IGame *game = ( IGame * )&g_Game; #if !defined( _X360 ) const wchar_t CGame::CLASSNAME[] = L"Valve001"; #else const char CGame::CLASSNAME[] = "Valve001"; #endif // In VCR playback mode, it sleeps this amount each frame. int g_iVCRPlaybackSleepInterval = 0; // During VCR playback, if this is true, then it'll pause at the end of each frame. bool g_bVCRSingleStep = false; bool g_bWaitingForStepKeyUp = false; // Used to prevent it from running frames while you hold the S key down. bool g_bShowVCRPlaybackDisplay = true; // These are all the windows messages that can change game state. // See CGame::WindowProc for a description of how they work. struct GameMessageHandler_t { int m_nEventType; void (CGame::*pFn)( const InputEvent_t &event ); }; GameMessageHandler_t g_GameMessageHandlers[] = { { IE_AppActivated, &CGame::HandleMsg_ActivateApp }, { IE_WindowMove, &CGame::HandleMsg_WindowMove }, { IE_Close, &CGame::HandleMsg_Close }, { IE_Quit, &CGame::HandleMsg_Close }, }; void CGame::AppActivate( bool fActive ) { // If text mode, force it to be active. if ( g_bTextMode ) { fActive = true; } // Don't bother if we're already in the correct state if ( IsActiveApp() == fActive ) return; // Don't let video modes changes queue up another activate event SetCanPostActivateEvents( false ); #ifndef SWDS if ( videomode ) { if ( fActive ) { videomode->RestoreVideo(); } else { videomode->ReleaseVideo(); } } if ( host_initialized ) { if ( fActive ) { // Clear keyboard states (should be cleared already but...) // VGui_ActivateMouse will reactivate the mouse soon. ClearIOStates(); UpdateMaterialSystemConfig(); } else { // Clear keyboard input and deactivate the mouse while we're away. ClearIOStates(); if ( g_ClientDLL ) { g_ClientDLL->IN_DeactivateMouse(); } } } #endif // SWDS SetActiveApp( fActive ); #ifdef _XBOX if ( host_initialized ) { ClearIOStates(); if ( fActive ) { UpdateMaterialSystemConfig(); } } SetActiveApp( fActive ); #endif // Allow queueing of activation events SetCanPostActivateEvents( true ); } void CGame::HandleMsg_WindowMove( const InputEvent_t &event ) { game->SetWindowXY( event.m_nData, event.m_nData2 ); #ifndef SWDS videomode->UpdateWindowPosition(); #endif } void CGame::HandleMsg_ActivateApp( const InputEvent_t &event ) { AppActivate( event.m_nData ? true : false ); } void CGame::HandleMsg_Close( const InputEvent_t &event ) { if ( eng->GetState() == IEngine::DLL_ACTIVE ) { eng->SetQuitting( IEngine::QUIT_TODESKTOP ); } } void CGame::DispatchInputEvent( const InputEvent_t &event ) { switch( event.m_nType ) { // Handle button events specially, // since we have all manner of crazy filtering going on when dealing with them case IE_ButtonPressed: case IE_ButtonDoubleClicked: case IE_ButtonReleased: Key_Event( event ); break; default: // Let vgui have the first whack at events if ( g_pMatSystemSurface && g_pMatSystemSurface->HandleInputEvent( event ) ) break; for ( int i=0; i < ARRAYSIZE( g_GameMessageHandlers ); i++ ) { if ( g_GameMessageHandlers[i].m_nEventType == event.m_nType ) { (this->*g_GameMessageHandlers[i].pFn)( event ); break; } } break; } } void CGame::DispatchAllStoredGameMessages() { #if !defined( NO_VCR ) if ( VCRGetMode() == VCR_Playback ) { InputEvent_t event; while ( VCRHook_PlaybackGameMsg( &event ) ) { event.m_nTick = g_pInputSystem->GetPollTick(); DispatchInputEvent( event ); } } else { int nEventCount = g_pInputSystem->GetEventCount(); const InputEvent_t* pEvents = g_pInputSystem->GetEventData( ); for ( int i = 0; i < nEventCount; ++i ) { VCRHook_RecordGameMsg( pEvents[i] ); DispatchInputEvent( pEvents[i] ); } VCRHook_RecordEndGameMsg(); } #else int nEventCount = g_pInputSystem->GetEventCount(); const InputEvent_t* pEvents = g_pInputSystem->GetEventData( ); for ( int i = 0; i < nEventCount; ++i ) { DispatchInputEvent( pEvents[i] ); } #endif } void VCR_EnterPausedState() { // Turn this off in case they're in single-step mode. g_bVCRSingleStep = false; #ifdef WIN32 // This is cheesy, but GetAsyncKeyState is blocked (in protected_things.h) // from being accidentally used, so we get it through it by getting its pointer directly. static HINSTANCE hInst = LoadLibrary( "user32.dll" ); if ( !hInst ) return; typedef SHORT (WINAPI *GetAsyncKeyStateFn)( int vKey ); static GetAsyncKeyStateFn pfn = (GetAsyncKeyStateFn)GetProcAddress( hInst, "GetAsyncKeyState" ); if ( !pfn ) return; // In this mode, we enter a wait state where we only pay attention to R and Q. while ( 1 ) { if ( pfn( 'R' ) & 0x8000 ) break; if ( pfn( 'Q' ) & 0x8000 ) TerminateProcess( GetCurrentProcess(), 1 ); if ( pfn( 'S' ) & 0x8000 ) { if ( !g_bWaitingForStepKeyUp ) { // Do a single step. g_bVCRSingleStep = true; g_bWaitingForStepKeyUp = true; // Don't do another single step until they release the S key. break; } } else { // Ok, they released the S key, so we'll process it next time the key goes down. g_bWaitingForStepKeyUp = false; } Sleep( 2 ); } #else Assert( !"Impl me" ); #endif } #ifdef WIN32 void VCR_HandlePlaybackMessages( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if ( uMsg == WM_KEYDOWN ) { if ( wParam == VK_SUBTRACT || wParam == 0xbd ) { g_iVCRPlaybackSleepInterval += 5; } else if ( wParam == VK_ADD || wParam == 0xbb ) { g_iVCRPlaybackSleepInterval -= 5; } else if ( toupper( wParam ) == 'Q' ) { TerminateProcess( GetCurrentProcess(), 1 ); } else if ( toupper( wParam ) == 'P' ) { VCR_EnterPausedState(); } else if ( toupper( wParam ) == 'S' && !g_bVCRSingleStep ) { g_bWaitingForStepKeyUp = true; VCR_EnterPausedState(); } else if ( toupper( wParam ) == 'D' ) { g_bShowVCRPlaybackDisplay = !g_bShowVCRPlaybackDisplay; } g_iVCRPlaybackSleepInterval = clamp( g_iVCRPlaybackSleepInterval, 0, 500 ); } } //----------------------------------------------------------------------------- // Calls the default window procedure // FIXME: It would be nice to remove the need for this, which we can do // if we can make unicode work when running inside hammer. //----------------------------------------------------------------------------- static LONG WINAPI CallDefaultWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if ( unicode ) return unicode->DefWindowProcW( hWnd, uMsg, wParam, lParam ); return DefWindowProc( hWnd, uMsg, wParam, lParam ); } #endif //----------------------------------------------------------------------------- // Purpose: The user has accepted an invitation to a game, we need to detect if // it's TF2 and restart properly if it is //----------------------------------------------------------------------------- void XBX_HandleInvite( DWORD nUserId ) { #ifdef _X360 // Grab our invite info 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 ) { // Do the normal "we've been invited to a game" behavior return; } // Otherwise, launch depending on our current MOD if ( !Q_stricmp( GetCurrentMod(), "tf" ) ) { // We're already running TF2, so just join the session if ( nUserId != XBX_GetPrimaryUserId() ) { // Switch users, the other had the invite XBX_SetPrimaryUserId( nUserId ); } // Kick off our join g_pMatchmaking->JoinInviteSession( &(inviteInfo.hostInfo) ); } else { // Save off our session ID for later retrieval // NOTE: We may need to actually save off the inviter's XID and search for them later on if we took too long or the // session they were a part of went away XBX_SetInviteSessionId( inviteInfo.hostInfo.sessionID ); XBX_SetInvitedUserId( nUserId ); // Quit via the menu path "QuitNoConfirm" EngineVGui()->SystemNotification( SYSTEMNOTIFY_INVITE_SHUTDOWN ); } #endif //_X360 } #if defined( WIN32 ) && !defined( USE_SDL ) //----------------------------------------------------------------------------- // Main windows procedure //----------------------------------------------------------------------------- int CGame::WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LONG lRet = 0; HDC hdc; PAINTSTRUCT ps; // // NOTE: the way this function works is to handle all messages that just call through to // Windows or provide data to it. // // Any messages that change the engine's internal state (like key events) are stored in a list // and processed at the end of the frame. This is necessary for VCR mode to work correctly because // Windows likes to pump messages during some of its API calls like SetWindowPos, and unless we add // custom code around every Windows API call so VCR mode can trap the wndproc calls, VCR mode can't // reproduce the calls to the wndproc. // if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING ) return CallWindowProc( m_ChainedWindowProc, hWnd, uMsg, wParam, lParam ); // If we're playing back, listen to a couple input things used to drive VCR mode if ( VCRGetMode() == VCR_Playback ) { VCR_HandlePlaybackMessages( hWnd, uMsg, wParam, lParam ); } // // Note: NO engine state should be changed in here while in VCR record or playback. // We can send whatever we want to Windows, but if we change its state in here instead of // in DispatchAllStoredGameMessages, the playback may not work because Windows messages // are not deterministic, so you might get different messages during playback than you did during record. // InputEvent_t event; memset( &event, 0, sizeof(event) ); event.m_nTick = g_pInputSystem->GetPollTick(); switch ( uMsg ) { case WM_CREATE: ::SetForegroundWindow( hWnd ); break; case WM_ACTIVATEAPP: { if ( CanPostActivateEvents() ) { bool bActivated = ( wParam == 1 ); event.m_nType = IE_AppActivated; event.m_nData = bActivated; g_pInputSystem->PostUserEvent( event ); } } break; case WM_POWERBROADCAST: // Don't go into Sleep mode when running engine, we crash on resume for some reason (as // do half of the apps I have running usually anyway...) if ( wParam == PBT_APMQUERYSUSPEND ) { Msg( "OS requested hibernation, ignoring request.\n" ); return BROADCAST_QUERY_DENY; } lRet = CallWindowProc( m_ChainedWindowProc, hWnd, uMsg, wParam, lParam ); break; case WM_SYSCOMMAND: if ( ( wParam == SC_MONITORPOWER ) || ( wParam == SC_KEYMENU ) || ( wParam == SC_SCREENSAVE ) ) return lRet; if ( wParam == SC_CLOSE ) { #if !defined( NO_VCR ) // handle the close message, but make sure // it's not because we accidently hit ALT-F4 if ( HIBYTE(VCRHook_GetKeyState(VK_LMENU)) || HIBYTE(VCRHook_GetKeyState(VK_RMENU) ) ) return lRet; #endif Cbuf_Clear(); Cbuf_AddText( "quit\n" ); } #ifndef SWDS if ( VCRGetMode() == VCR_Disabled ) { S_BlockSound(); S_ClearBuffer(); } #endif lRet = CallWindowProc( m_ChainedWindowProc, hWnd, uMsg, wParam, lParam ); #ifndef SWDS if ( VCRGetMode() == VCR_Disabled ) { S_UnblockSound(); } #endif break; case WM_CLOSE: // Handle close messages event.m_nType = IE_Close; g_pInputSystem->PostUserEvent( event ); return 0; case WM_MOVE: event.m_nType = IE_WindowMove; event.m_nData = (short)LOWORD(lParam); event.m_nData2 = (short)HIWORD(lParam); g_pInputSystem->PostUserEvent( event ); break; case WM_SIZE: if ( wParam != SIZE_MINIMIZED ) { // Update restored client rect ::GetClientRect( hWnd, &m_rcLastRestoredClientRect ); } else { #ifndef _X360 // Fix the window rect to have same client area as it used to have // before it got minimized RECT rcWindow; ::GetWindowRect( hWnd, &rcWindow ); rcWindow.right = rcWindow.left + m_rcLastRestoredClientRect.right; rcWindow.bottom = rcWindow.top + m_rcLastRestoredClientRect.bottom; ::AdjustWindowRect( &rcWindow, ::GetWindowLong( hWnd, GWL_STYLE ), FALSE ); ::MoveWindow( hWnd, rcWindow.left, rcWindow.top, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, FALSE ); #endif } break; case WM_SYSCHAR: // keep Alt-Space from happening break; case WM_COPYDATA: // Hammer -> engine remote console command. // Return true to indicate that the message was handled. Cbuf_AddText( (const char *)(((COPYDATASTRUCT *)lParam)->lpData) ); Cbuf_AddText( "\n" ); lRet = 1; break; #if defined( _X360 ) case WM_XREMOTECOMMAND: Cbuf_AddText( (const char*)lParam ); Cbuf_AddText( "\n" ); break; case WM_SYS_STORAGEDEVICESCHANGED: if ( !EngineVGui()->IsGameUIVisible() ) { EngineVGui()->ActivateGameUI(); } EngineVGui()->SystemNotification( SYSTEMNOTIFY_STORAGEDEVICES_CHANGED ); break; case WM_XMP_PLAYBACKCONTROLLERCHANGED: S_EnableMusic( lParam != 0 ); break; case WM_LIVE_INVITE_ACCEPTED: XBX_HandleInvite( LOWORD( lParam ) ); break; case WM_SYS_SIGNINCHANGED: if ( XUserGetSigninState( XBX_GetPrimaryUserId() ) == eXUserSigninState_NotSignedIn ) { // X360TBD: User signed out - pause the game? } EngineVGui()->SystemNotification( lParam ? SYSTEMNOTIFY_USER_SIGNEDIN : SYSTEMNOTIFY_USER_SIGNEDOUT ); break; case WM_SYS_UI: if ( lParam ) { // Don't activate it if it's already active (a sub window may be active) // Multiplayer doesn't want the UI to appear, since it can't pause anyway if ( !EngineVGui()->IsGameUIVisible() && g_ServerGlobalVariables.maxClients == 1 ) { Cbuf_AddText( "gameui_activate" ); } } EngineVGui()->SystemNotification( lParam ? SYSTEMNOTIFY_XUIOPENING : SYSTEMNOTIFY_XUICLOSED ); break; case WM_SYS_MUTELISTCHANGED: g_pMatchmaking->UpdateMuteList(); break; case WM_SYS_INPUTDEVICESCHANGED: { XINPUT_CAPABILITIES caps; if ( XInputGetCapabilities( XBX_GetPrimaryUserId(), XINPUT_FLAG_GAMEPAD, &caps ) == ERROR_DEVICE_NOT_CONNECTED ) { if ( EngineVGui()->IsGameUIVisible() == false ) { EngineVGui()->ActivateGameUI(); } } } break; #endif case WM_PAINT: hdc = BeginPaint(hWnd, &ps); RECT rcClient; GetClientRect( hWnd, &rcClient ); #ifndef SWDS // Only renders stuff if running -noshaderapi if ( videomode ) { videomode->DrawNullBackground( hdc, rcClient.right, rcClient.bottom ); } #endif EndPaint(hWnd, &ps); break; #ifndef _X360 case WM_DISPLAYCHANGE: if ( !m_iDesktopHeight || !m_iDesktopWidth ) { UpdateDesktopInformation( wParam, lParam ); } break; #endif case WM_IME_NOTIFY: switch ( wParam ) { default: break; #ifndef SWDS case 14: if ( !videomode->IsWindowedMode() ) return 0; break; #endif } return CallWindowProc( m_ChainedWindowProc, hWnd, uMsg, wParam, lParam); default: lRet = CallWindowProc( m_ChainedWindowProc, hWnd, uMsg, wParam, lParam ); break; } // return 0 if handled message, 1 if not return lRet; } #elif defined(OSX) #elif defined(LINUX) #elif defined(_WIN32) #else #error #endif #if defined( WIN32 ) && !defined( USE_SDL ) //----------------------------------------------------------------------------- // Creates the game window //----------------------------------------------------------------------------- static LONG WINAPI HLEngineWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { return g_Game.WindowProc( hWnd, uMsg, wParam, lParam ); } #define DEFAULT_EXE_ICON 101 static void DoSomeSocketStuffInOrderToGetZoneAlarmToNoticeUs( void ) { #ifdef IS_WINDOWS_PC WSAData wsaData; if ( ! WSAStartup( 0x0101, &wsaData ) ) { SOCKET tmpSocket = socket( AF_INET, SOCK_DGRAM, 0 ); if ( tmpSocket != INVALID_SOCKET ) { char Options[]={ 1 }; setsockopt( tmpSocket, SOL_SOCKET, SO_BROADCAST, Options, sizeof(Options)); char pszHostName[256]; gethostname( pszHostName, sizeof( pszHostName ) ); hostent *hInfo = gethostbyname( pszHostName ); if ( hInfo ) { sockaddr_in myIpAddress; memset( &myIpAddress, 0, sizeof( myIpAddress ) ); myIpAddress.sin_family = AF_INET; myIpAddress.sin_port = htons( 27015 ); // our normal server port myIpAddress.sin_addr.S_un.S_un_b.s_b1 = hInfo->h_addr_list[0][0]; myIpAddress.sin_addr.S_un.S_un_b.s_b2 = hInfo->h_addr_list[0][1]; myIpAddress.sin_addr.S_un.S_un_b.s_b3 = hInfo->h_addr_list[0][2]; myIpAddress.sin_addr.S_un.S_un_b.s_b4 = hInfo->h_addr_list[0][3]; if ( bind( tmpSocket, ( sockaddr * ) &myIpAddress, sizeof( myIpAddress ) ) != -1 ) { if ( sendto( tmpSocket, pszHostName, 1, 0, ( sockaddr *) &myIpAddress, sizeof( myIpAddress ) ) == -1 ) { // error? } } } closesocket( tmpSocket ); } WSACleanup(); } #endif } #endif bool CGame::CreateGameWindow( void ) { // get the window name char windowName[256]; windowName[0] = 0; KeyValues *modinfo = new KeyValues("ModInfo"); if (modinfo->LoadFromFile(g_pFileSystem, "gameinfo.txt")) { Q_strncpy( windowName, modinfo->GetString("game"), sizeof(windowName) ); } if (!windowName[0]) { Q_strncpy( windowName, "HALF-LIFE 2", sizeof(windowName) ); } if ( IsOpenGL() ) { V_strcat( windowName, " - OpenGL", sizeof( windowName ) ); } #if PIX_ENABLE || defined( PIX_INSTRUMENTATION ) // PIX_ENABLE/PIX_INSTRUMENTATION is a big slowdown (that should never be checked in, but sometimes is by accident), so add this to the Window title too. V_strcat( windowName, " - PIX_ENABLE", sizeof( windowName ) ); #endif const char *p = CommandLine()->ParmValue( "-window_name_suffix", "" ); if ( p && V_strlen( p ) ) { V_strcat( windowName, " - ", sizeof( windowName ) ); V_strcat( windowName, p, sizeof( windowName ) ); } #if defined( WIN32 ) && !defined( USE_SDL ) #ifndef SWDS if ( IsPC() ) { if ( !LoadUnicode() ) { return false; } } #if !defined( _X360 ) WNDCLASSW wc; #else WNDCLASS wc; #endif memset( &wc, 0, sizeof( wc ) ); wc.style = CS_OWNDC | CS_DBLCLKS; wc.lpfnWndProc = CallDefaultWindowProc; wc.hInstance = m_hInstance; wc.lpszClassName = CLASSNAME; // find the icon file in the filesystem if ( IsPC() ) { char localPath[ MAX_PATH ]; if ( g_pFileSystem->GetLocalPath( "resource/game.ico", localPath, sizeof(localPath) ) ) { g_pFileSystem->GetLocalCopy( localPath ); wc.hIcon = (HICON)::LoadImage(NULL, localPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE); } else { wc.hIcon = (HICON)LoadIcon( GetModuleHandle( 0 ), MAKEINTRESOURCE( DEFAULT_EXE_ICON ) ); } } #ifndef SWDS char const *pszGameType = modinfo->GetString( "type" ); if ( pszGameType && Q_stristr( pszGameType, "multiplayer" ) ) DoSomeSocketStuffInOrderToGetZoneAlarmToNoticeUs(); #endif wchar_t uc[512]; if ( IsPC() ) { ::MultiByteToWideChar(CP_UTF8, 0, windowName, -1, uc, sizeof( uc ) / sizeof(wchar_t)); } modinfo->deleteThis(); modinfo = NULL; // Oops, we didn't clean up the class registration from last cycle which // might mean that the wndproc pointer is bogus #ifndef _X360 unicode->UnregisterClassW( CLASSNAME, m_hInstance ); // Register it again unicode->RegisterClassW( &wc ); #else RegisterClass( &wc ); #endif // Note, it's hidden DWORD style = WS_POPUP | WS_CLIPSIBLINGS; // Give it a frame if we want a border if ( videomode->IsWindowedMode() ) { if( !CommandLine()->FindParm( "-noborder" ) ) { style |= WS_OVERLAPPEDWINDOW; style &= ~WS_THICKFRAME; } } // Never a max box style &= ~WS_MAXIMIZEBOX; int w, h; // Create a full screen size window by default, it'll get resized later anyway w = GetSystemMetrics( SM_CXSCREEN ); h = GetSystemMetrics( SM_CYSCREEN ); // Create the window DWORD exFlags = 0; if ( g_bTextMode ) { style &= ~WS_VISIBLE; exFlags |= WS_EX_TOOLWINDOW; // So it doesn't show up in the taskbar. } #if !defined( _X360 ) HWND hwnd = unicode->CreateWindowExW( exFlags, CLASSNAME, uc, style, 0, 0, w, h, NULL, NULL, m_hInstance, NULL ); // NOTE: On some cards, CreateWindowExW slams the FPU control word SetupFPUControlWord(); #else HWND hwnd = CreateWindowEx( exFlags, CLASSNAME, windowName, style, 0, 0, w, h, NULL, NULL, m_hInstance, NULL ); #endif if ( !hwnd ) { Error( "Fatal Error: Unable to create game window!" ); return false; } SetMainWindow( hwnd ); AttachToWindow( ); return true; #else return true; #endif #elif defined( USE_SDL ) bool windowed = videomode->IsWindowedMode(); modinfo->deleteThis(); modinfo = NULL; if ( !g_pLauncherMgr->CreateGameWindow( windowName, windowed, 0, 0 ) ) { Error( "Fatal Error: Unable to create game window!" ); return false; } char localPath[ MAX_PATH ]; if ( g_pFileSystem->GetLocalPath( "resource/game-icon.bmp", localPath, sizeof(localPath) ) ) { g_pFileSystem->GetLocalCopy( localPath ); g_pLauncherMgr->SetApplicationIcon( localPath ); } SetMainWindow( (SDL_Window*)g_pLauncherMgr->GetWindowRef() ); AttachToWindow( ); return true; #else #error #endif } //----------------------------------------------------------------------------- // Destroys the game window //----------------------------------------------------------------------------- void CGame::DestroyGameWindow() { #if defined( USE_SDL ) g_pLauncherMgr->DestroyGameWindow(); #else #ifndef DEDICATED // Destroy all things created when the window was created if ( !m_bExternallySuppliedWindow ) { DetachFromWindow( ); if ( m_hWindow ) { DestroyWindow( m_hWindow ); m_hWindow = (HWND)0; } #if !defined( _X360 ) unicode->UnregisterClassW( CLASSNAME, m_hInstance ); UnloadUnicode(); #else UnregisterClass( CLASSNAME, m_hInstance ); #endif } else { m_hWindow = (HWND)0; m_bExternallySuppliedWindow = false; } #endif // !defined( SWDS ) #endif } //----------------------------------------------------------------------------- // This is used in edit mode to specify a particular game window (created by hammer) //----------------------------------------------------------------------------- void CGame::SetGameWindow( void *hWnd ) { m_bExternallySuppliedWindow = true; #if defined( USE_SDL ) SDL_RaiseWindow( (SDL_Window *)hWnd ); #else SetMainWindow( (HWND)hWnd ); #endif } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CGame::AttachToWindow() { #if defined( WIN32 ) if ( !m_hWindow ) return; #if !defined( USE_SDL ) m_ChainedWindowProc = (WNDPROC)GetWindowLongPtrW( m_hWindow, GWLP_WNDPROC ); SetWindowLongPtrW( m_hWindow, GWLP_WNDPROC, (LONG_PTR)HLEngineWindowProc ); #endif #endif // WIN32 if ( g_pInputSystem ) { // Attach the input system window proc #if defined( WIN32 ) g_pInputSystem->AttachToWindow( (void *)m_hWindow ); #else g_pInputSystem->AttachToWindow( (void *)m_pSDLWindow ); #endif g_pInputSystem->EnableInput( true ); g_pInputSystem->EnableMessagePump( false ); } if ( g_pMatSystemSurface ) { // Attach the vgui matsurface window proc #if defined( WIN32 ) g_pMatSystemSurface->AttachToWindow( (void *)m_hWindow, true ); #else g_pMatSystemSurface->AttachToWindow( (void *)m_pSDLWindow, true ); #endif g_pMatSystemSurface->EnableWindowsMessages( true ); } } void CGame::DetachFromWindow() { #if defined( WIN32 ) && !defined( USE_SDL ) if ( !m_hWindow || !m_ChainedWindowProc ) { m_ChainedWindowProc = NULL; return; } #endif if ( g_pMatSystemSurface ) { // Detach the vgui matsurface window proc g_pMatSystemSurface->AttachToWindow( NULL ); } if ( g_pInputSystem ) { // Detach the input system window proc g_pInputSystem->EnableInput( false ); g_pInputSystem->DetachFromWindow( ); } #if defined( WIN32 ) && !defined( USE_SDL ) Assert( (WNDPROC)GetWindowLongPtrW( m_hWindow, GWLP_WNDPROC ) == HLEngineWindowProc ); SetWindowLongPtrW( m_hWindow, GWLP_WNDPROC, (LONG_PTR)m_ChainedWindowProc ); #endif } //----------------------------------------------------------------------------- // This is used in edit mode to override the default wnd proc associated w/ // the game window specified in SetGameWindow. //----------------------------------------------------------------------------- bool CGame::InputAttachToGameWindow() { // We can't use this feature unless we didn't control the creation of the window if ( !m_bExternallySuppliedWindow ) return true; AttachToWindow(); #ifndef DEDICATED vgui::surface()->OnScreenSizeChanged( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() ); #endif // We don't get WM_ACTIVATEAPP messages in this case; simulate one. AppActivate( true ); #if defined( WIN32 ) && !defined( USE_SDL ) // Capture + hide the mouse SetCapture( m_hWindow ); #elif defined( USE_SDL ) Assert( !"Impl me" ); return false; #else #error #endif return true; } void CGame::InputDetachFromGameWindow() { // We can't use this feature unless we didn't control the creation of the window if ( !m_bExternallySuppliedWindow ) return; #if defined( WIN32 ) && !defined( USE_SDL ) if ( !m_ChainedWindowProc ) return; // Release + show the mouse ReleaseCapture(); #elif defined( USE_SDL ) Assert( !"Impl me" ); #else #error "have no idea what OS we are building for" #endif // We don't get WM_ACTIVATEAPP messages in this case; simulate one. AppActivate( false ); DetachFromWindow(); } void CGame::PlayStartupVideos( void ) { if ( IsX360() || Plat_IsInBenchmarkMode() ) return; #ifndef SWDS // Wait for the mode to change and stabilized // FIXME: There's really no way to know when this is completed, so we have to guess a time that will mostly be correct if ( videomode->IsWindowedMode() == false ) { Sys_Sleep( 1000 ); } bool bEndGame = CommandLine()->CheckParm( "-endgamevid" ); bool bRecap = CommandLine()->CheckParm( "-recapvid" ); // FIXME: This is a temp addition until the movie playback is centralized -- jdw bool bNeedHealthWarning = false; const char *HealthFile = "media/HealthWarning.txt"; FileHandle_t hFile; COM_OpenFile( HealthFile, &hFile ); //There is no access to steam at this point so we are checking for the presence of an empty file that will only exist in the chinese depot if ( hFile != FILESYSTEM_INVALID_HANDLE ) { bNeedHealthWarning = true; COM_CloseFile( hFile ); } if (!bNeedHealthWarning && !bEndGame && !bRecap && (CommandLine()->CheckParm("-dev") || CommandLine()->CheckParm("-novid") || CommandLine()->CheckParm("-allowdebug"))) return; const char *pszFile = "media/StartupVids.txt"; if ( bEndGame ) { // Don't go back into the map that triggered this. CommandLine()->RemoveParm( "+map" ); CommandLine()->RemoveParm( "+load" ); pszFile = "media/EndGameVids.txt"; } else if ( bRecap ) { pszFile = "media/RecapVids.txt"; } int vidFileLength; // have to use the malloc memory allocation option in COM_LoadFile since the memory system isn't set up at this point. const char *buffer = (char *) COM_LoadFile( pszFile, 5, &vidFileLength ); if ((buffer == NULL) || (vidFileLength == 0)) { return; } // hide cursor while playing videos #if defined( USE_SDL ) g_pLauncherMgr->SetMouseVisible( false ); #elif defined( WIN32 ) ::ShowCursor( FALSE ); #endif #if defined( LINUX ) extern void VAudioInit(); VAudioInit(); Audio_CreateSDLAudioDevice(); #endif const char *start = buffer; while( true ) { start = COM_Parse(start); if ( Q_strlen( com_token ) <= 0 ) { break; } // get the path to the media file and play it. char localPath[MAX_PATH]; g_pFileSystem->GetLocalPath( com_token, localPath, sizeof(localPath) ); PlayVideoAndWait( localPath, bNeedHealthWarning ); localPath[0] = 0; // just to make sure we don't play the same avi file twice in the case that one movie is there but another isn't. } // show cursor again #if defined( USE_SDL ) g_pLauncherMgr->SetMouseVisible( true ); #elif defined( WIN32 ) ::ShowCursor( TRUE ); #endif // call free on the buffer since the buffer was malloc'd in COM_LoadFile free( (void *)buffer ); #endif // SWDS } //----------------------------------------------------------------------------- // Purpose: Plays a video until the video completes or ESC is pressed // Input : *filename - Name of the file (relative to the filesystem) //----------------------------------------------------------------------------- void CGame::PlayVideoAndWait( const char *filename, bool bNeedHealthWarning ) { // do we have a filename and a video system, and not on a console? if ( !filename || !filename[0] || g_pVideo == NULL ) return; // is it the valve logo file? bool bIsValveLogo = ( Q_strstr( filename, "valve.") != NULL ); //Chinese health messages appears for 11 seconds, so we force a minimum delay time for those float forcedMinTime = ( bIsValveLogo && bNeedHealthWarning ) ? 11.0f : -1.0f; #if defined( WIN32 ) && !defined ( _X360 ) && !defined( USE_SDL ) // Black out the back of the screen once at the beginning of each video (since we're not scaling to fit) HDC dc = ::GetDC( m_hWindow ); RECT rect; rect.top = 0; rect.bottom = m_height; rect.left = 0; rect.right = m_width; HBRUSH hBlackBrush = (HBRUSH) ::GetStockObject( BLACK_BRUSH ); ::SetViewportOrgEx( dc, 0, 0, NULL ); ::FillRect( dc, &rect, hBlackBrush ); ::ReleaseDC( (HWND) GetMainWindow(), dc ); #else // need OS specific way to clear screen #endif VideoResult_t status = g_pVideo->PlayVideoFileFullScreen( filename, "GAME", GetMainWindowPlatformSpecificHandle (), m_width, m_height, m_iDesktopWidth, m_iDesktopHeight, videomode->IsWindowedMode(), forcedMinTime, VideoPlaybackFlags::DEFAULT_FULLSCREEN_OPTIONS | VideoPlaybackFlags::FILL_WINDOW ); // Everything ok? if ( status == VideoResult::SUCCESS ) { return; } // We don't worry if it could not find something to could play if ( status == VideoResult::VIDEO_FILE_NOT_FOUND ) { return; } // Debug Builds, we want an error looked at by a developer, Release builds just send a message to the spew #ifdef _DEBUG Error( "Error %d occurred attempting to play video file %s\n", (int) status, filename ); #else Msg( "Error %d occurred attempting to play video file %s\n", (int) status, filename ); #endif } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CGame::CGame() { #if defined( USE_SDL ) m_pSDLWindow = 0; #endif #if defined( WIN32 ) #if !defined( USE_SDL ) unicode = NULL; m_hUnicodeModule = NULL; m_hInstance = 0; m_ChainedWindowProc = NULL; #endif m_hWindow = 0; #endif m_x = m_y = 0; m_width = m_height = 0; m_bActiveApp = false; m_bCanPostActivateEvents = true; m_iDesktopWidth = 0; m_iDesktopHeight = 0; m_iDesktopRefreshRate = 0; } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CGame::~CGame() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CGame::Init( void *pvInstance ) { m_bExternallySuppliedWindow = false; #if defined( WIN32 ) && !defined( USE_SDL ) OSVERSIONINFO vinfo; vinfo.dwOSVersionInfoSize = sizeof(vinfo); if ( !GetVersionEx( &vinfo ) ) { return false; } if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32s ) { return false; } m_hInstance = (HINSTANCE)pvInstance; #endif return true; } bool CGame::Shutdown( void ) { #if defined( WIN32 ) && !defined( USE_SDL ) m_hInstance = 0; #endif return true; } bool CGame::LoadUnicode( void ) { #ifdef WIN32 m_hUnicodeModule = Sys_LoadModule( "unicode" ); if ( !m_hUnicodeModule ) { Error( "Unable to load unicode.dll" ); return false; } CreateInterfaceFn factory = Sys_GetFactory( m_hUnicodeModule ); if ( !factory ) { Error( "Unable to get factory from unicode.dll" ); return false; } unicode = ( IUnicodeWindows * )factory( VENGINE_UNICODEINTERFACE_VERSION, NULL ); if ( !unicode ) { Error( "Unable to load interface '%s' from unicode.dll", VENGINE_UNICODEINTERFACE_VERSION ); return false; } #endif return true; } void CGame::UnloadUnicode() { #ifdef WIN32 unicode = NULL; if ( m_hUnicodeModule ) { Sys_UnloadModule( m_hUnicodeModule ); m_hUnicodeModule = NULL; } #endif } void *CGame::GetMainWindow( void ) { #ifdef USE_SDL return (void*)m_pSDLWindow; #else return GetMainWindowPlatformSpecificHandle(); #endif } void *CGame::GetMainDeviceWindow( void ) { #if defined( DX_TO_GL_ABSTRACTION ) && defined( USE_SDL ) return (void*)m_pSDLWindow; #else return (void*)m_hWindow; #endif } void *CGame::GetMainWindowPlatformSpecificHandle( void ) { #ifdef WIN32 return (void*)m_hWindow; #else SDL_SysWMinfo pInfo; SDL_VERSION( &pInfo.version ); if ( !SDL_GetWindowWMInfo( (SDL_Window*)m_pSDLWindow, &pInfo ) ) { Error( "Fatal Error: Unable to get window info from SDL." ); return NULL; } #ifdef OSX id nsWindow = (id)pInfo.info.cocoa.window; SEL selector = sel_registerName("windowRef"); id windowRef = objc_msgSend( nsWindow, selector ); return windowRef; #else // Not used on Linux. return NULL; #endif #endif // !WIN32 } void** CGame::GetMainWindowAddress( void ) { #ifdef WIN32 return (void**)&m_hWindow; #else return NULL; #endif } void CGame::GetDesktopInfo( int &width, int &height, int &refreshrate ) { #if defined( USE_SDL ) width = 640; height = 480; refreshrate = 0; // Go through all the displays and return the size of the largest. for( int i = 0; i < SDL_GetNumVideoDisplays(); i++ ) { SDL_Rect rect; if ( !SDL_GetDisplayBounds( i, &rect ) ) { if ( ( rect.w > width ) || ( ( rect.w == width ) && ( rect.h > height ) ) ) { width = rect.w; height = rect.h; } } } #else // order of initialization means that this might get called early. In that case go ahead and grab the current // screen window and setup based on that. // we need to do this when initializing the base list of video modes, for example if ( m_iDesktopWidth == 0 ) { HDC dc = ::GetDC( NULL ); width = ::GetDeviceCaps(dc, HORZRES); height = ::GetDeviceCaps(dc, VERTRES); refreshrate = ::GetDeviceCaps(dc, VREFRESH); ::ReleaseDC( NULL, dc ); return; } width = m_iDesktopWidth; height = m_iDesktopHeight; refreshrate = m_iDesktopRefreshRate; #endif } void CGame::UpdateDesktopInformation( ) { #if defined( USE_SDL ) // Get the size of the display we will be displayed fullscreen on. static ConVarRef sdl_displayindex( "sdl_displayindex" ); int displayIndex = sdl_displayindex.IsValid() ? sdl_displayindex.GetInt() : 0; SDL_DisplayMode mode; SDL_GetDesktopDisplayMode( displayIndex, &mode ); m_iDesktopWidth = mode.w; m_iDesktopHeight = mode.h; m_iDesktopRefreshRate = mode.refresh_rate; #else HDC dc = ::GetDC( m_hWindow ); m_iDesktopWidth = ::GetDeviceCaps(dc, HORZRES); m_iDesktopHeight = ::GetDeviceCaps(dc, VERTRES); m_iDesktopRefreshRate = ::GetDeviceCaps(dc, VREFRESH); ::ReleaseDC( m_hWindow, dc ); #endif } #ifdef WIN32 void CGame::UpdateDesktopInformation( WPARAM wParam, LPARAM lParam ) { m_iDesktopWidth = LOWORD( lParam ); m_iDesktopHeight = HIWORD( lParam ); } #endif #ifndef USE_SDL void CGame::SetMainWindow( HWND window ) { m_hWindow = window; // update our desktop info (since the results will change if we are going to fullscreen mode) if ( !m_iDesktopWidth || !m_iDesktopHeight ) { UpdateDesktopInformation(); } } #else void CGame::SetMainWindow( SDL_Window* window ) { #if defined( WIN32 ) // For D3D, we need to access the underlying HWND of the SDL_Window. // We also can't do this in GetMainDeviceWindow and just use that, because for some reason // people use GetMainWindowAddress and store that pointer to our member. SDL_SysWMinfo pInfo; SDL_VERSION( &pInfo.version ); if ( !SDL_GetWindowWMInfo( (SDL_Window*)g_pLauncherMgr->GetWindowRef(), &pInfo ) ) { Error( "Fatal Error: Unable to get window info from SDL." ); return; } m_hWindow = pInfo.info.win.window; #endif m_pSDLWindow = window; // update our desktop info (since the results will change if we are going to fullscreen mode) if ( !m_iDesktopWidth || !m_iDesktopHeight ) { UpdateDesktopInformation(); } } #endif void CGame::SetWindowXY( int x, int y ) { m_x = x; m_y = y; } void CGame::SetWindowSize( int w, int h ) { m_width = w; m_height = h; } void CGame::GetWindowRect( int *x, int *y, int *w, int *h ) { if ( x ) { *x = m_x; } if ( y ) { *y = m_y; } if ( w ) { *w = m_width; } if ( h ) { *h = m_height; } } bool CGame::IsActiveApp( void ) { return m_bActiveApp; } void CGame::SetCanPostActivateEvents( bool bEnabled ) { m_bCanPostActivateEvents = bEnabled; } bool CGame::CanPostActivateEvents() { return m_bCanPostActivateEvents; } void CGame::SetActiveApp( bool active ) { m_bActiveApp = active; }