hl2_src-leak-2017/src/engine/sys_mainwind.cpp

1743 lines
42 KiB
C++

//========= 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 <objc/message.h>
#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 <winsock.h>
#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;
}