/**************************************************************************** * * File: testnet.cpp * Project: DxDiag (DirectX Diagnostic Tool) * Author: Jason Sandlin (jasonsa@microsoft.com) * Purpose: Test DPlay8 functionality on this machine * * (C) Copyright 2000-2001 Microsoft Corp. All rights reserved. * ****************************************************************************/ #define INITGUID #include #include #include #include #include #include #include #include "reginfo.h" #include "sysinfo.h" #include "dispinfo.h" #include "netinfo.h" #include "testnet.h" #include "resource.h" #ifndef ReleasePpo #define ReleasePpo(ppo) \ if (*(ppo) != NULL) \ { \ (*(ppo))->Release(); \ *(ppo) = NULL; \ } \ else (VOID)0 #endif #define TIMER_WAIT_CONNECT_COMPLETE 0 #define TIMER_UPDATE_SESSION_LIST 1 enum TESTID { TESTID_COINITIALIZE = 1, TESTID_CREATEDPLAY, TESTID_ADDRESSING, TESTID_ENUMSESSIONS, TESTID_ENUMPLAYERS, TESTID_SENDCHATMESSAGE, TESTID_RECEIVE, TESTID_SETPEERINFO, TESTID_CREATESESSION, TESTID_JOINSESSION, }; struct DPHostEnumInfo { DPN_APPLICATION_DESC* pAppDesc; IDirectPlay8Address* pHostAddr; IDirectPlay8Address* pDeviceAddr; TCHAR szSession[MAX_PATH]; DWORD dwLastPollTime; BOOL bValid; DPHostEnumInfo* pNext; }; #define MAX_CHAT_STRING_LENGTH 200 #define MAX_PLAYER_NAME MAX_PATH #define MAX_CHAT_STRING (MAX_PLAYER_NAME + MAX_CHAT_STRING_LENGTH + 32) struct APP_PLAYER_INFO { LONG lRefCount; // Ref count so we can cleanup when all threads // are done w/ this object DPNID dpnidPlayer; // DPNID of player WCHAR strPlayerName[MAX_PLAYER_NAME]; // Player name }; #define GAME_MSGID_CHAT 1 // Change compiler pack alignment to be BYTE aligned, and pop the current value #pragma pack( push, 1 ) UNALIGNED struct GAMEMSG_GENERIC { WORD nType; }; UNALIGNED struct GAMEMSG_CHAT : public GAMEMSG_GENERIC { WCHAR strChatString[MAX_CHAT_STRING_LENGTH]; }; // Pop the old pack alignment #pragma pack( pop ) struct APP_QUEUE_CHAT_MSG { WCHAR strChatBuffer[MAX_CHAT_STRING]; }; struct APP_PLAYER_MSG { WCHAR strPlayerName[MAX_PATH]; // Player name }; #define WM_APP_CHAT (WM_APP + 1) #define WM_APP_LEAVE (WM_APP + 2) #define WM_APP_JOIN (WM_APP + 3) #define WM_APP_CONNECTING (WM_APP + 4) #define WM_APP_CONNECTED (WM_APP + 5) BOOL BTranslateError(HRESULT hr, TCHAR* psz, BOOL bEnglish = FALSE); // from main.cpp (yuck) static INT_PTR CALLBACK SetupDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static BOOL FAR PASCAL EnumConnectionsCallback(LPCGUID lpguidSP, VOID* pvConnection, DWORD dwConnectionSize, LPCDPNAME pName, DWORD dwFlags, VOID* pvContext); static INT_PTR CALLBACK SessionsDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static VOID SessionsDlgInitListbox( HWND hDlg ); static VOID SessionsDlgNoteEnumResponse( PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg ); static VOID SessionsDlgUpdateSessionList(HWND hDlg); static VOID SessionsDlgEnumListCleanup(); static HRESULT InitDirectPlay( BOOL* pbCoInitializeDone ); static HRESULT InitDirectPlayAddresses(); static HRESULT InitSession(); static VOID LoadStringWide( int nID, WCHAR* szWide ); static BOOL FAR PASCAL EnumSessionsCallback(LPCDPSESSIONDESC2 pdpsd, DWORD* pdwTimeout, DWORD dwFlags, VOID* pvContext); static INT_PTR CALLBACK ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static VOID ShowTextString(HWND hDlg, WCHAR* sz ); static HRESULT SendChatMessage( TCHAR* szMessage ); static HRESULT WINAPI DirectPlayMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer ); static BOOL ConvertStringToGUID(const TCHAR* strBuffer, GUID* lpguid); static VOID ConvertGenericStringToWide( WCHAR* wstrDestination, const TCHAR* tstrSource, int cchDestChar = -1 ); static VOID ConvertWideStringToGeneric( TCHAR* tstrDestination, const WCHAR* wstrSource, int cchDestChar ); static VOID ConvertWideStringToAnsi( CHAR* strDestination, const WCHAR* wstrSource, int cchDestChar ); static const GUID s_guidDPTest = // {61EF80DA-691B-4247-9ADD-1C7BED2BC13E} { 0x61ef80da, 0x691b, 0x4247, { 0x9a, 0xdd, 0x1c, 0x7b, 0xed, 0x2b, 0xc1, 0x3e } }; static NetInfo* s_pNetInfo = NULL; static IDirectPlay8Peer* s_pDP = NULL; static TCHAR s_szPlayerName[100]; static TCHAR s_szSessionName[100]; static DWORD s_dwPort = 0; static NetSP* s_pNetSP = NULL; static BOOL s_bCreateSession = FALSE; static DPHostEnumInfo* s_pSelectedSession = NULL; static DPHostEnumInfo s_DPHostEnumHead; static IDirectPlay8Address* s_pDeviceAddress = NULL; static IDirectPlay8Address* s_pHostAddress = NULL; static DPNHANDLE s_hEnumAsyncOp = NULL; static DWORD s_dwEnumHostExpireInterval = 0; static BOOL s_bEnumListChanged = FALSE; static BOOL s_bConnecting = FALSE; static DPHostEnumInfo* s_pDPHostEnumSelected = NULL; static CRITICAL_SECTION s_csHostEnum; static DPNID s_dpnidLocalPlayer = 0; static LONG s_lNumberOfActivePlayers = 0; static HWND s_hDlg = NULL; static HWND s_hwndSessionDlg = NULL; static DPNHANDLE s_hConnectAsyncOp = NULL; static HRESULT s_hrConnectComplete = S_OK; static HANDLE s_hConnectCompleteEvent = NULL; static CRITICAL_SECTION s_csPlayerContext; #define PLAYER_LOCK() EnterCriticalSection( &s_csPlayerContext ); #define PLAYER_ADDREF( pPlayerInfo ) if( pPlayerInfo ) pPlayerInfo->lRefCount++; #define PLAYER_RELEASE( pPlayerInfo ) if( pPlayerInfo ) { pPlayerInfo->lRefCount--; if( pPlayerInfo->lRefCount <= 0 ) delete pPlayerInfo; } pPlayerInfo = NULL; #define PLAYER_UNLOCK() LeaveCriticalSection( &s_csPlayerContext ); /**************************************************************************** * * TestNetwork * ****************************************************************************/ VOID TestNetwork(HWND hwndMain, NetInfo* pNetInfo) { BOOL bCoInitializeDone = FALSE; TCHAR sz[300]; HINSTANCE hinst = (HINSTANCE)GetWindowLongPtr(hwndMain, GWLP_HINSTANCE); s_pNetInfo = pNetInfo; // Remove info from any previous test: ZeroMemory(&s_pNetInfo->m_testResult, sizeof(TestResult)); s_pNetInfo->m_testResult.m_bStarted = TRUE; // Setup the s_DPHostEnumHead circular linked list ZeroMemory( &s_DPHostEnumHead, sizeof( DPHostEnumInfo ) ); s_DPHostEnumHead.pNext = &s_DPHostEnumHead; InitializeCriticalSection( &s_csHostEnum ); InitializeCriticalSection( &s_csPlayerContext ); s_hConnectCompleteEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); // Setup s_pDP, and mark installed SP's if( FAILED( InitDirectPlay( &bCoInitializeDone ) ) ) goto LEnd; // Show setup dialog. This will tell us: // - service provider // - player name // - either create or join // - game name (if creating) // - port (if SP=TCP/IP) DialogBox(hinst, MAKEINTRESOURCE(IDD_TESTNETSETUP), hwndMain, SetupDialogProc); if (s_pNetSP == NULL) { // Something weird happened...no service provider chosen goto LEnd; } // At this point s_szPlayerName, s_szSessionName, s_pNetSP, s_dwPort, // and s_bCreateSession have been initialized // Setup s_dwEnumHostExpireInterval, s_pDeviceAddress, and s_pHostAddress if( FAILED( InitDirectPlayAddresses() ) ) goto LEnd; // Now s_dwEnumHostExpireInterval, s_pDeviceAddress, and s_pHostAddress // have been initialized // Session list window (if joining session) if( !s_bCreateSession ) { // Open a dialog to choose which host to connect to DialogBox(hinst, MAKEINTRESOURCE(IDD_TESTNETSESSIONS), hwndMain, SessionsDialogProc); // Now s_pDPHostEnumSelected will be NULL or valid if( FAILED(s_pNetInfo->m_testResult.m_hr) || s_pDPHostEnumSelected == NULL ) goto LEnd; // Now s_pDPHostEnumSelected is valid } // Launch chat window and host or join session DialogBox(hinst, MAKEINTRESOURCE(IDD_TESTNETCHAT), hwndMain, ChatDialogProc); LEnd: s_pNetSP = NULL; ReleasePpo( &s_pDeviceAddress ); ReleasePpo( &s_pHostAddress ); if( s_hEnumAsyncOp ) s_pDP->CancelAsyncOperation( s_hEnumAsyncOp, 0 ); ReleasePpo(&s_pDP); if (bCoInitializeDone) CoUninitialize(); // Release COM DeleteCriticalSection( &s_csHostEnum ); DeleteCriticalSection( &s_csPlayerContext ); CloseHandle( s_hConnectCompleteEvent ); if (s_pNetInfo->m_testResult.m_bCancelled) { LoadString(NULL, IDS_TESTSCANCELLED, sz, 300); lstrcpy(s_pNetInfo->m_testResult.m_szDescription, sz); LoadString(NULL, IDS_TESTSCANCELLED_ENGLISH, sz, 300); lstrcpy(s_pNetInfo->m_testResult.m_szDescriptionEnglish, sz); } else if (s_pNetInfo->m_testResult.m_iStepThatFailed == 0) { LoadString(NULL, IDS_TESTSSUCCESSFUL, sz, 300); lstrcpy(s_pNetInfo->m_testResult.m_szDescription, sz); LoadString(NULL, IDS_TESTSSUCCESSFUL_ENGLISH, sz, 300); lstrcpy(s_pNetInfo->m_testResult.m_szDescriptionEnglish, sz); } else { TCHAR szDesc[300]; TCHAR szError[300]; if (0 == LoadString(NULL, IDS_FIRSTDPLAYTESTERROR + s_pNetInfo->m_testResult.m_iStepThatFailed - 1, szDesc, 200)) { LoadString(NULL, IDS_UNKNOWNERROR, sz, 300); lstrcpy(szDesc, sz); } LoadString(NULL, IDS_FAILUREFMT, sz, 300); BTranslateError(s_pNetInfo->m_testResult.m_hr, szError); wsprintf(s_pNetInfo->m_testResult.m_szDescription, sz, s_pNetInfo->m_testResult.m_iStepThatFailed, szDesc, s_pNetInfo->m_testResult.m_hr, szError); // Nonlocalized version: if (0 == LoadString(NULL, IDS_FIRSTDPLAYTESTERROR_ENGLISH + s_pNetInfo->m_testResult.m_iStepThatFailed - 1, szDesc, 200)) { LoadString(NULL, IDS_UNKNOWNERROR_ENGLISH, sz, 300); lstrcpy(szDesc, sz); } LoadString(NULL, IDS_FAILUREFMT_ENGLISH, sz, 300); BTranslateError(s_pNetInfo->m_testResult.m_hr, szError, TRUE); wsprintf(s_pNetInfo->m_testResult.m_szDescriptionEnglish, sz, s_pNetInfo->m_testResult.m_iStepThatFailed, szDesc, s_pNetInfo->m_testResult.m_hr, szError); } } /**************************************************************************** * * InitDirectPlay * ****************************************************************************/ HRESULT InitDirectPlay( BOOL* pbCoInitializeDone ) { HRESULT hr; DWORD dwItems = 0; DWORD dwSize = 0; DPN_SERVICE_PROVIDER_INFO* pdnSPInfoEnum = NULL; DPN_SERVICE_PROVIDER_INFO* pdnSPInfo = NULL; DWORD i; // Initialize COM if (FAILED(hr = CoInitialize(NULL))) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_COINITIALIZE; s_pNetInfo->m_testResult.m_hr = hr; return hr; } *pbCoInitializeDone = TRUE; // Create DirectPlay object if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Peer, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Peer, (LPVOID*) &s_pDP ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_CREATEDPLAY; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // Init IDirectPlay8Peer if( FAILED( hr = s_pDP->Initialize( NULL, DirectPlayMessageHandler, 0 ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_CREATEDPLAY; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // Enumerate all DirectPlay service providers // to figure out which are installed hr = s_pDP->EnumServiceProviders( NULL, NULL, pdnSPInfo, &dwSize, &dwItems, 0 ); if( hr != DPNERR_BUFFERTOOSMALL && FAILED(hr) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } pdnSPInfo = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[dwSize]; if( FAILED( hr = s_pDP->EnumServiceProviders( NULL, NULL, pdnSPInfo, &dwSize, &dwItems, 0 ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; if( pdnSPInfo ) delete[] pdnSPInfo; return hr; } // Mark installed SP's as such pdnSPInfoEnum = pdnSPInfo; for ( i = 0; i < dwItems; i++ ) { NetSP* pNetSP; for (pNetSP = s_pNetInfo->m_pNetSPFirst; pNetSP != NULL; pNetSP = pNetSP->m_pNetSPNext) { if( pNetSP->m_guid == pdnSPInfoEnum->guid ) { pNetSP->m_bInstalled = TRUE; break; } } pdnSPInfoEnum++; } if( pdnSPInfo ) delete[] pdnSPInfo; return S_OK; } /**************************************************************************** * * SetupDialogProc * ****************************************************************************/ INT_PTR CALLBACK SetupDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { NetSP* pNetSP; TCHAR sz[MAX_PATH]; HWND hwndList = GetDlgItem(hDlg, IDC_SPLIST); LONG iItem; LONG iSelect = LB_ERR; for (pNetSP = s_pNetInfo->m_pNetSPFirst; pNetSP != NULL; pNetSP = pNetSP->m_pNetSPNext) { if( pNetSP->m_dwDXVer == 8 && pNetSP->m_bInstalled ) { iItem = (LONG)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)pNetSP->m_szName); if ((LRESULT)iItem != LB_ERR) { SendMessage(hwndList, LB_SETITEMDATA, iItem, (LPARAM)pNetSP); // Try to select TCP/IP by default if( lstrcmpi(pNetSP->m_szGuid, TEXT("{EBFE7BA0-628D-11D2-AE0F-006097B01411}")) == 0) iSelect = iItem; } } } // Try to select the default preferred provider if( iSelect != LB_ERR ) SendMessage( hwndList, LB_SETCURSEL, iSelect, 0 ); else SendMessage( hwndList, LB_SETCURSEL, 0, 0 ); SendMessage(hDlg, WM_COMMAND, IDC_SPLIST, 0); LoadString(NULL, IDS_DEFAULTUSERNAME, sz, MAX_PATH); SetWindowText(GetDlgItem(hDlg, IDC_PLAYERNAME), sz); LoadString(NULL, IDS_DEFAULTSESSIONNAME, sz, MAX_PATH); SetWindowText(GetDlgItem(hDlg, IDC_SESSIONNAME), sz); CheckRadioButton(hDlg, IDC_CREATESESSION, IDC_JOINSESSION, IDC_CREATESESSION); return TRUE; } case WM_COMMAND: { switch(LOWORD(wParam)) { case IDC_CREATESESSION: { EnableWindow(GetDlgItem(hDlg, IDC_SESSIONNAME), TRUE); break; } case IDC_JOINSESSION: { EnableWindow(GetDlgItem(hDlg, IDC_SESSIONNAME), FALSE); break; } case IDC_SPLIST: { HWND hwndList; hwndList = GetDlgItem(hDlg, IDC_SPLIST); LONG iItem; iItem = (LONG)SendMessage(hwndList, LB_GETCURSEL, 0, 0); NetSP* pNetSP = (NetSP*)SendMessage(hwndList, LB_GETITEMDATA, iItem, 0); // Only enable the port if the selected SP == TCP/IP if( pNetSP && lstrcmp( pNetSP->m_szGuid, TEXT("{EBFE7BA0-628D-11D2-AE0F-006097B01411}") ) == 0 ) { EnableWindow( GetDlgItem(hDlg, IDC_PORT), TRUE ); EnableWindow( GetDlgItem(hDlg, IDC_PORT_TEXT), TRUE ); } else { EnableWindow( GetDlgItem(hDlg, IDC_PORT), FALSE ); EnableWindow( GetDlgItem(hDlg, IDC_PORT_TEXT), FALSE ); } break; } case IDOK: { // Set create/join option if (IsDlgButtonChecked(hDlg, IDC_CREATESESSION)) s_bCreateSession = TRUE; else s_bCreateSession = FALSE; // Get player name GetWindowText(GetDlgItem(hDlg, IDC_PLAYERNAME), s_szPlayerName, 100); if (lstrlen(s_szPlayerName) == 0) { TCHAR szMsg[MAX_PATH]; TCHAR szTitle[MAX_PATH]; LoadString(NULL, IDS_NEEDUSERNAME, szMsg, MAX_PATH); LoadString(NULL, IDS_APPFULLNAME, szTitle, MAX_PATH); MessageBox(hDlg, szMsg, szTitle, MB_OK); break; } // Get port TCHAR szPort[MAX_PATH]; GetDlgItemText( hDlg, IDC_PORT, szPort, MAX_PATH); s_dwPort = _ttoi( szPort ); // Get session name GetWindowText(GetDlgItem(hDlg, IDC_SESSIONNAME), s_szSessionName, 100); if (s_bCreateSession && lstrlen(s_szSessionName) == 0) { TCHAR szMsg[MAX_PATH]; TCHAR szTitle[MAX_PATH]; LoadString(NULL, IDS_NEEDSESSIONNAME, szMsg, MAX_PATH); LoadString(NULL, IDS_APPFULLNAME, szTitle, MAX_PATH); MessageBox(hDlg, szMsg, szTitle, MB_OK); break; } // Get sp HWND hwndList; hwndList = GetDlgItem(hDlg, IDC_SPLIST); LONG iItem; iItem = (LONG)SendMessage(hwndList, LB_GETCURSEL, 0, 0); if ((LPARAM)iItem == LB_ERR) { s_pNetInfo->m_testResult.m_bCancelled = TRUE; EndDialog(hDlg, 0); return FALSE; } else { s_pNetSP = (NetSP*)SendMessage(hwndList, LB_GETITEMDATA, iItem, 0); } EndDialog(hDlg, 1); break; } case IDCANCEL: { EndDialog(hDlg, 0); s_pNetInfo->m_testResult.m_bCancelled = TRUE; break; } } } } return FALSE; } /**************************************************************************** * * InitDirectPlayAddresses * ****************************************************************************/ HRESULT InitDirectPlayAddresses() { HRESULT hr; // Query for the enum host timeout for this SP DPN_SP_CAPS dpspCaps; ZeroMemory( &dpspCaps, sizeof(DPN_SP_CAPS) ); dpspCaps.dwSize = sizeof(DPN_SP_CAPS); if( FAILED( hr = s_pDP->GetSPCaps( &s_pNetSP->m_guid, &dpspCaps, 0 ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // Set the host expire time to around 3 times // length of the dwDefaultEnumRetryInterval s_dwEnumHostExpireInterval = dpspCaps.dwDefaultEnumRetryInterval * 3; // Create a device address ReleasePpo( &s_pDeviceAddress ); hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL,CLSCTX_INPROC_SERVER, IID_IDirectPlay8Address, (LPVOID*) &s_pDeviceAddress ); if( FAILED(hr) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } if( FAILED( hr = s_pDeviceAddress->SetSP( &s_pNetSP->m_guid ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // Create a host address ReleasePpo( &s_pHostAddress ); hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL,CLSCTX_INPROC_SERVER, IID_IDirectPlay8Address, (LPVOID*) &s_pHostAddress ); if( FAILED(hr) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // Set the SP if( FAILED( hr = s_pHostAddress->SetSP( &s_pNetSP->m_guid ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // If TCP/IP then set the port if its non-zero if( s_pNetSP->m_guid == CLSID_DP8SP_TCPIP ) { if( s_bCreateSession ) { if( s_dwPort > 0 ) { // Add the port to pDeviceAddress if( FAILED( hr = s_pDeviceAddress->AddComponent( DPNA_KEY_PORT, &s_dwPort, sizeof(s_dwPort), DPNA_DATATYPE_DWORD ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } } } else { if( s_dwPort > 0 ) { // Add the port to pHostAddress if( FAILED( hr = s_pHostAddress->AddComponent( DPNA_KEY_PORT, &s_dwPort, sizeof(s_dwPort), DPNA_DATATYPE_DWORD ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ADDRESSING; s_pNetInfo->m_testResult.m_hr = hr; return hr; } } } } return S_OK; } /**************************************************************************** * * SessionsDialogProc * ****************************************************************************/ INT_PTR CALLBACK SessionsDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { HRESULT hr; s_hwndSessionDlg = hDlg; s_bEnumListChanged = TRUE; // Enumerate hosts DPN_APPLICATION_DESC dnAppDesc; ZeroMemory( &dnAppDesc, sizeof(DPN_APPLICATION_DESC) ); dnAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC); dnAppDesc.guidApplication = s_guidDPTest; // Enumerate all the active DirectPlay games on the selected connection hr = s_pDP->EnumHosts( &dnAppDesc, // application description s_pHostAddress, // host address s_pDeviceAddress, // device address NULL, // pointer to user data 0, // user data size INFINITE, // retry count (forever) 0, // retry interval (0=default) INFINITE, // time out (forever) NULL, // user context &s_hEnumAsyncOp, // async handle DPNENUMHOSTS_OKTOQUERYFORADDRESSING // flags ); if( FAILED(hr) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ENUMSESSIONS; s_pNetInfo->m_testResult.m_hr = hr; EndDialog(hDlg, 0); return TRUE; } SessionsDlgInitListbox(hDlg); SetTimer(hDlg, TIMER_UPDATE_SESSION_LIST, 250, NULL); return TRUE; } case WM_TIMER: { if( wParam == TIMER_UPDATE_SESSION_LIST ) SessionsDlgUpdateSessionList(hDlg); return TRUE; } case WM_COMMAND: { switch(LOWORD(wParam)) { case IDOK: { HWND hwndList = GetDlgItem(hDlg, IDC_SESSIONLIST); LONG iSelCur = (LONG)SendMessage(hwndList, LB_GETCURSEL, 0, 0); if( iSelCur != LB_ERR ) { // This will prevent s_pDPHostEnumSelected from being // deleting due to SessionsDlgUpdateSessionList() EnterCriticalSection( &s_csHostEnum ); s_pDPHostEnumSelected = (DPHostEnumInfo*)SendMessage( hwndList, LB_GETITEMDATA, iSelCur, 0 ); if ( (LRESULT)s_pDPHostEnumSelected != LB_ERR && s_pDPHostEnumSelected != NULL ) { // We keep the CS until we are done with s_pDPHostEnumSelected, // otherwise it might change out from under us. EndDialog(hDlg, 1); break; } s_pDPHostEnumSelected = NULL; LeaveCriticalSection( &s_csHostEnum ); } s_pNetInfo->m_testResult.m_bCancelled = TRUE; EndDialog(hDlg, 0); break; } case IDCANCEL: { s_pDPHostEnumSelected = NULL; s_pNetInfo->m_testResult.m_bCancelled = TRUE; EndDialog(hDlg, 0); break; } } } case WM_DESTROY: { KillTimer( hDlg, TIMER_UPDATE_SESSION_LIST ); s_hwndSessionDlg = NULL; break; } } return FALSE; } /**************************************************************************** * * SessionsDlgInitListbox * ****************************************************************************/ VOID SessionsDlgInitListbox( HWND hDlg ) { HWND hWndListBox = GetDlgItem( hDlg, IDC_SESSIONLIST ); LONG numChars; TCHAR szFmt[200]; LoadString(NULL, IDS_LOOKINGFORSESSIONS, szFmt, 200); numChars = (LONG)SendMessage(GetDlgItem(hDlg, IDC_CHATOUTPUT), WM_GETTEXTLENGTH, 0, 0); SendMessage(GetDlgItem(hDlg, IDC_CHATOUTPUT), EM_SETSEL, numChars, numChars); SendMessage(GetDlgItem(hDlg, IDC_CHATOUTPUT), EM_REPLACESEL, FALSE, (LPARAM)szFmt); // Clear the contents from the list box, and // display "Looking for sessions" text in listbox SendMessage( hWndListBox, LB_RESETCONTENT, 0, 0 ); SendMessage( hWndListBox, LB_SETITEMDATA, 0, NULL ); SendMessage( hWndListBox, LB_SETCURSEL, 0, 0 ); // Disable the join button until sessions are found EnableWindow( GetDlgItem( hDlg, IDOK ), FALSE ); } /**************************************************************************** * * SessionsDlgNoteEnumResponse * ****************************************************************************/ VOID SessionsDlgNoteEnumResponse( PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg ) { HRESULT hr = S_OK; BOOL bFound; // This function is called from the DirectPlay message handler so it could be // called simultaneously from multiple threads, so enter a critical section // to assure that it we don't get race conditions. EnterCriticalSection( &s_csHostEnum ); DPHostEnumInfo* pDPHostEnum = s_DPHostEnumHead.pNext; DPHostEnumInfo* pDPHostEnumNext = NULL; const DPN_APPLICATION_DESC* pResponseMsgAppDesc = pEnumHostsResponseMsg->pApplicationDescription; // Look for a matching session instance GUID. bFound = FALSE; while ( pDPHostEnum != &s_DPHostEnumHead ) { if( pResponseMsgAppDesc->guidInstance == pDPHostEnum->pAppDesc->guidInstance ) { bFound = TRUE; break; } pDPHostEnumNext = pDPHostEnum; pDPHostEnum = pDPHostEnum->pNext; } if( !bFound ) { s_bEnumListChanged = TRUE; // If there's no match, then look for invalid session and use it pDPHostEnum = s_DPHostEnumHead.pNext; while ( pDPHostEnum != &s_DPHostEnumHead ) { if( !pDPHostEnum->bValid ) break; pDPHostEnum = pDPHostEnum->pNext; } // If no invalid sessions are found then make a new one if( pDPHostEnum == &s_DPHostEnumHead ) { // Found a new session, so create a new node pDPHostEnum = new DPHostEnumInfo; if( NULL == pDPHostEnum ) { hr = E_OUTOFMEMORY; goto LCleanup; } ZeroMemory( pDPHostEnum, sizeof(DPHostEnumInfo) ); // Add pDPHostEnum to the circular linked list, m_DPHostEnumHead pDPHostEnum->pNext = s_DPHostEnumHead.pNext; s_DPHostEnumHead.pNext = pDPHostEnum; } } // Update the pDPHostEnum with new information TCHAR strName[MAX_PATH]; if( pResponseMsgAppDesc->pwszSessionName ) ConvertWideStringToGeneric( strName, pResponseMsgAppDesc->pwszSessionName, MAX_PATH ); else lstrcpy( strName, TEXT("???") ); // Cleanup any old enum if( pDPHostEnum->pAppDesc ) { delete[] pDPHostEnum->pAppDesc->pwszSessionName; delete[] pDPHostEnum->pAppDesc; } ReleasePpo( &pDPHostEnum->pHostAddr ); ReleasePpo( &pDPHostEnum->pDeviceAddr ); pDPHostEnum->bValid = FALSE; // // Duplicate pEnumHostsResponseMsg->pAddressSender in pDPHostEnum->pHostAddr. // Duplicate pEnumHostsResponseMsg->pAddressDevice in pDPHostEnum->pDeviceAddr. // if( FAILED( hr = pEnumHostsResponseMsg->pAddressSender->Duplicate( &pDPHostEnum->pHostAddr ) ) ) { goto LCleanup; } if( FAILED( hr = pEnumHostsResponseMsg->pAddressDevice->Duplicate( &pDPHostEnum->pDeviceAddr ) ) ) { goto LCleanup; } // Deep copy the DPN_APPLICATION_DESC from pDPHostEnum->pAppDesc = new DPN_APPLICATION_DESC; ZeroMemory( pDPHostEnum->pAppDesc, sizeof(DPN_APPLICATION_DESC) ); memcpy( pDPHostEnum->pAppDesc, pResponseMsgAppDesc, sizeof(DPN_APPLICATION_DESC) ); if( pResponseMsgAppDesc->pwszSessionName ) { pDPHostEnum->pAppDesc->pwszSessionName = new WCHAR[ wcslen(pResponseMsgAppDesc->pwszSessionName)+1 ]; wcscpy( pDPHostEnum->pAppDesc->pwszSessionName, pResponseMsgAppDesc->pwszSessionName ); } // Update the time this was done, so that we can expire this host // if it doesn't refresh w/in a certain amount of time pDPHostEnum->dwLastPollTime = timeGetTime(); // if this node was previously invalidated, or the session name is now // different the session list in the dialog needs to be updated if( ( pDPHostEnum->bValid == FALSE ) || ( _tcscmp( pDPHostEnum->szSession, strName ) != 0 ) ) { s_bEnumListChanged = TRUE; } _tcscpy( pDPHostEnum->szSession, strName ); // This host is now valid pDPHostEnum->bValid = TRUE; LCleanup: LeaveCriticalSection( &s_csHostEnum ); } /**************************************************************************** * * SessionsDlgUpdateSessionList * ****************************************************************************/ VOID SessionsDlgUpdateSessionList( HWND hDlg ) { HWND hWndListBox = GetDlgItem(hDlg, IDC_SESSIONLIST); DPHostEnumInfo* pDPHostEnum = NULL; DPHostEnumInfo* pDPHostEnumSelected = NULL; GUID guidSelectedInstance; BOOL bFindSelectedGUID; BOOL bFoundSelectedGUID; int nItemSelected; DWORD dwCurrentTime = timeGetTime(); // This is called from the dialog UI thread, NoteEnumResponse() // is called from the DirectPlay message handler threads so // they may also be inside it at this time, so we need to go into the // critical section first EnterCriticalSection( &s_csHostEnum ); // Expire old host enums pDPHostEnum = s_DPHostEnumHead.pNext; while ( pDPHostEnum != &s_DPHostEnumHead ) { // Check the poll time to expire stale entries. Also check to see if // the entry is already invalid. If so, don't note that the enum list // changed because that causes the list in the dialog to constantly redraw. if( ( pDPHostEnum->bValid != FALSE ) && ( pDPHostEnum->dwLastPollTime < dwCurrentTime - s_dwEnumHostExpireInterval ) ) { // This node has expired, so invalidate it. pDPHostEnum->bValid = FALSE; s_bEnumListChanged = TRUE; } pDPHostEnum = pDPHostEnum->pNext; } // Only update the display list if it has changed since last time if( !s_bEnumListChanged ) { LeaveCriticalSection( &s_csHostEnum ); return; } s_bEnumListChanged = FALSE; bFindSelectedGUID = FALSE; bFoundSelectedGUID = FALSE; // Try to keep the same session selected unless it goes away or // there is no real session currently selected nItemSelected = (int)SendMessage( hWndListBox, LB_GETCURSEL, 0, 0 ); if( nItemSelected != LB_ERR ) { pDPHostEnumSelected = (DPHostEnumInfo*) SendMessage( hWndListBox, LB_GETITEMDATA, nItemSelected, 0 ); if( pDPHostEnumSelected != NULL && pDPHostEnumSelected->bValid ) { guidSelectedInstance = pDPHostEnumSelected->pAppDesc->guidInstance; bFindSelectedGUID = TRUE; } } // Tell listbox not to redraw itself since the contents are going to change SendMessage( hWndListBox, WM_SETREDRAW, FALSE, 0 ); // Test to see if any sessions exist in the linked list pDPHostEnum = s_DPHostEnumHead.pNext; while ( pDPHostEnum != &s_DPHostEnumHead ) { if( pDPHostEnum->bValid ) break; pDPHostEnum = pDPHostEnum->pNext; } // If there are any sessions in list, // then add them to the listbox if( pDPHostEnum != &s_DPHostEnumHead ) { // Clear the contents from the list box and enable the join button SendMessage( hWndListBox, LB_RESETCONTENT, 0, 0 ); EnableWindow( GetDlgItem( hDlg, IDOK ), TRUE ); pDPHostEnum = s_DPHostEnumHead.pNext; while ( pDPHostEnum != &s_DPHostEnumHead ) { // Add host to list box if it is valid if( pDPHostEnum->bValid ) { int nIndex = (int)SendMessage( hWndListBox, LB_ADDSTRING, 0, (LPARAM)pDPHostEnum->szSession ); SendMessage( hWndListBox, LB_SETITEMDATA, nIndex, (LPARAM)pDPHostEnum ); if( bFindSelectedGUID ) { // Look for the session the was selected before if( pDPHostEnum->pAppDesc->guidInstance == guidSelectedInstance ) { SendMessage( hWndListBox, LB_SETCURSEL, nIndex, 0 ); bFoundSelectedGUID = TRUE; } } } pDPHostEnum = pDPHostEnum->pNext; } if( !bFindSelectedGUID || !bFoundSelectedGUID ) SendMessage( hWndListBox, LB_SETCURSEL, 0, 0 ); } else { // There are no active session, so just reset the listbox SessionsDlgInitListbox( hDlg ); } // Tell listbox to redraw itself now since the contents have changed SendMessage( hWndListBox, WM_SETREDRAW, TRUE, 0 ); InvalidateRect( hWndListBox, NULL, FALSE ); LeaveCriticalSection( &s_csHostEnum ); return; } /**************************************************************************** * * SessionsDlgEnumListCleanup * ****************************************************************************/ VOID SessionsDlgEnumListCleanup() { DPHostEnumInfo* pDPHostEnum = s_DPHostEnumHead.pNext; DPHostEnumInfo* pDPHostEnumDelete; while ( pDPHostEnum != &s_DPHostEnumHead ) { pDPHostEnumDelete = pDPHostEnum; pDPHostEnum = pDPHostEnum->pNext; if( pDPHostEnumDelete->pAppDesc ) { delete[] pDPHostEnumDelete->pAppDesc->pwszSessionName; delete[] pDPHostEnumDelete->pAppDesc; } // Changed from array delete to Release ReleasePpo( &pDPHostEnumDelete->pHostAddr ); ReleasePpo( &pDPHostEnumDelete->pDeviceAddr ); delete pDPHostEnumDelete; } // Re-link the s_DPHostEnumHead circular linked list s_DPHostEnumHead.pNext = &s_DPHostEnumHead; } /**************************************************************************** * * ChatDialogProc * ****************************************************************************/ INT_PTR CALLBACK ChatDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { s_hDlg = hDlg; // Join or host the session if( FAILED( InitSession() ) ) { EndDialog(hDlg, 0); } return TRUE; } case WM_TIMER: { if( wParam == TIMER_WAIT_CONNECT_COMPLETE ) { // Check for connect complete if( WAIT_OBJECT_0 == WaitForSingleObject( s_hConnectCompleteEvent, 0 ) ) { s_bConnecting = FALSE; if( FAILED( s_hrConnectComplete ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_JOINSESSION; s_pNetInfo->m_testResult.m_hr = s_hrConnectComplete; EndDialog(hDlg, 0); } else { // DirectPlay connect successful PostMessage( s_hDlg, WM_APP_CONNECTED, 0, 0 ); EnableWindow( GetDlgItem( s_hDlg, IDC_SEND), TRUE ); } KillTimer( s_hDlg, TIMER_WAIT_CONNECT_COMPLETE ); } } break; } case WM_APP_CONNECTING: { WCHAR sz[MAX_PATH]; LoadStringWide(IDS_CONNECTING, sz); ShowTextString( hDlg, sz ); break; } case WM_APP_CONNECTED: { WCHAR sz[MAX_PATH]; LoadStringWide(IDS_CONNECTED, sz); ShowTextString( hDlg, sz ); break; } case WM_APP_JOIN: { APP_PLAYER_MSG* pPlayerMsg = (APP_PLAYER_MSG*) lParam; WCHAR szFmt[MAX_PATH]; WCHAR szSuperMessage[MAX_PATH]; LoadStringWide(IDS_JOINMSGFMT, szFmt); swprintf(szSuperMessage, szFmt, pPlayerMsg->strPlayerName); ShowTextString( hDlg, szSuperMessage ); delete pPlayerMsg; break; } case WM_APP_CHAT: { APP_QUEUE_CHAT_MSG* pQueuedChat = (APP_QUEUE_CHAT_MSG*) lParam; ShowTextString( hDlg, pQueuedChat->strChatBuffer ); delete pQueuedChat; break; } case WM_APP_LEAVE: { APP_PLAYER_MSG* pPlayerMsg = (APP_PLAYER_MSG*) lParam; WCHAR szSuperMessage[MAX_PATH]; WCHAR szFmt[MAX_PATH]; LoadStringWide(IDS_LEAVEMSGFMT, szFmt); swprintf(szSuperMessage, szFmt, pPlayerMsg->strPlayerName ); ShowTextString( hDlg, szSuperMessage ); delete pPlayerMsg; break; } case WM_COMMAND: { switch(LOWORD(wParam)) { case IDC_SEND: { HRESULT hr; TCHAR szMessage[MAX_PATH]; GetWindowText(GetDlgItem(hDlg, IDC_CHATINPUT), szMessage, MAX_PATH); SendMessage(GetDlgItem(hDlg, IDC_CHATINPUT), EM_SETSEL, 0, -1); SendMessage(GetDlgItem(hDlg, IDC_CHATINPUT), EM_REPLACESEL, FALSE, (LPARAM)""); hr = SendChatMessage( szMessage ); if (FAILED(hr)) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_SENDCHATMESSAGE; s_pNetInfo->m_testResult.m_hr = hr; EndDialog(hDlg, 1); } } break; case IDOK: EndDialog(hDlg, 1); break; case IDCANCEL: EndDialog(hDlg, 0); break; } return TRUE; } case WM_DESTROY: { s_hDlg = NULL; break; } } return FALSE; } /**************************************************************************** * * InitSession * ****************************************************************************/ HRESULT InitSession() { HRESULT hr; if( s_bCreateSession ) { // Set peer info name WCHAR wszPeerName[MAX_PLAYER_NAME]; ConvertGenericStringToWide( wszPeerName, s_szPlayerName, MAX_PLAYER_NAME ); DPN_PLAYER_INFO dpPlayerInfo; ZeroMemory( &dpPlayerInfo, sizeof(DPN_PLAYER_INFO) ); dpPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO); dpPlayerInfo.dwInfoFlags = DPNINFO_NAME; dpPlayerInfo.pwszName = wszPeerName; // Set the peer info, and use the DPNOP_SYNC since by default this // is an async call. If it is not DPNOP_SYNC, then the peer info may not // be set by the time we call Host() below. if( FAILED( hr = s_pDP->SetPeerInfo( &dpPlayerInfo, NULL, NULL, DPNOP_SYNC ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_SETPEERINFO; s_pNetInfo->m_testResult.m_hr = hr; return hr; } WCHAR wszSessionName[MAX_PATH]; ConvertGenericStringToWide( wszSessionName, s_szSessionName ); // Setup the application desc DPN_APPLICATION_DESC dnAppDesc; ZeroMemory( &dnAppDesc, sizeof(DPN_APPLICATION_DESC) ); dnAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC); dnAppDesc.guidApplication = s_guidDPTest; dnAppDesc.pwszSessionName = wszSessionName; dnAppDesc.dwFlags = DPNSESSION_MIGRATE_HOST; // Host a game on m_pDeviceAddress as described by dnAppDesc // DPNHOST_OKTOQUERYFORADDRESSING allows DirectPlay to prompt the user // using a dialog box for any device address information that is missing if( FAILED( hr = s_pDP->Host( &dnAppDesc, // the application desc &s_pDeviceAddress, // array of addresses of the local devices used to connect to the host 1, // number in array NULL, NULL, // DPN_SECURITY_DESC, DPN_SECURITY_CREDENTIALS NULL, // player context DPNHOST_OKTOQUERYFORADDRESSING ) ) ) // flags { if (hr == DPNERR_USERCANCEL || hr == DPNERR_INVALIDDEVICEADDRESS) { s_pNetInfo->m_testResult.m_bCancelled = TRUE; return hr; } s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_CREATESESSION; s_pNetInfo->m_testResult.m_hr = hr; return hr; } } else { // Set the peer info WCHAR wszPeerName[MAX_PLAYER_NAME]; ConvertGenericStringToWide( wszPeerName, s_szPlayerName, MAX_PLAYER_NAME ); DPN_PLAYER_INFO dpPlayerInfo; ZeroMemory( &dpPlayerInfo, sizeof(DPN_PLAYER_INFO) ); dpPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO); dpPlayerInfo.dwInfoFlags = DPNINFO_NAME; dpPlayerInfo.pwszName = wszPeerName; // Set the peer info, and use the DPNOP_SYNC since by default this // is an async call. If it is not DPNOP_SYNC, then the peer info may not // be set by the time we call Connect() below. if( FAILED( hr = s_pDP->SetPeerInfo( &dpPlayerInfo, NULL, NULL, DPNOP_SYNC ) ) ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_SETPEERINFO; s_pNetInfo->m_testResult.m_hr = hr; LeaveCriticalSection( &s_csHostEnum ); return hr; } ResetEvent( s_hConnectCompleteEvent ); s_bConnecting = TRUE; // Connect to an existing session. DPNCONNECT_OKTOQUERYFORADDRESSING allows // DirectPlay to prompt the user using a dialog box for any device address // or host address information that is missing // We also pass in copies of the app desc and host addr, since pDPHostEnumSelected // might be deleted from another thread that calls SessionsDlgExpireOldHostEnums(). // This process could also be done using reference counting instead. hr = s_pDP->Connect( s_pDPHostEnumSelected->pAppDesc, // the application desc s_pDPHostEnumSelected->pHostAddr, // address of the host of the session s_pDPHostEnumSelected->pDeviceAddr, // address of the local device the enum responses were received on NULL, NULL, // DPN_SECURITY_DESC, DPN_SECURITY_CREDENTIALS NULL, 0, // user data, user data size NULL, // player context, NULL, &s_hConnectAsyncOp, // async context, async handle, DPNCONNECT_OKTOQUERYFORADDRESSING ); // flags LeaveCriticalSection( &s_csHostEnum ); if( hr != E_PENDING && FAILED(hr) ) { if (hr == DPNERR_USERCANCEL) { s_pNetInfo->m_testResult.m_bCancelled = TRUE; return hr; } s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_JOINSESSION; s_pNetInfo->m_testResult.m_hr = hr; return hr; } // Set a timer to wait for m_hConnectCompleteEvent to be signaled. // This will tell us when DPN_MSGID_CONNECT_COMPLETE has been processed // which lets us know if the connect was successful or not. PostMessage( s_hDlg, WM_APP_CONNECTING, 0, 0 ); SetTimer( s_hDlg, TIMER_WAIT_CONNECT_COMPLETE, 100, NULL ); EnableWindow( GetDlgItem( s_hDlg, IDC_SEND), FALSE ); } return S_OK; } /**************************************************************************** * * LoadStringWide * ****************************************************************************/ VOID LoadStringWide( int nID, WCHAR* szWide ) { TCHAR sz[MAX_PATH]; LoadString(NULL, nID, sz, MAX_PATH); ConvertGenericStringToWide( szWide, sz, MAX_PATH ); } /**************************************************************************** * * ShowTextString * ****************************************************************************/ VOID ShowTextString( HWND hDlg, WCHAR* sz ) { TCHAR szT[MAX_CHAT_STRING]; ConvertWideStringToGeneric( szT, sz, MAX_CHAT_STRING ); LONG numChars = (LONG)SendMessage(GetDlgItem(hDlg, IDC_CHATOUTPUT), WM_GETTEXTLENGTH, 0, 0); SendMessage(GetDlgItem(hDlg, IDC_CHATOUTPUT), EM_SETSEL, numChars, numChars); SendMessage(GetDlgItem(hDlg, IDC_CHATOUTPUT), EM_REPLACESEL, FALSE, (LPARAM)szT); } /**************************************************************************** * * SendChatMessage * ****************************************************************************/ HRESULT SendChatMessage( TCHAR* szMessage ) { // Send a message to all of the players GAMEMSG_CHAT msgChat; msgChat.nType = GAME_MSGID_CHAT; ConvertGenericStringToWide( msgChat.strChatString, szMessage, MAX_CHAT_STRING_LENGTH-1 ); msgChat.strChatString[MAX_CHAT_STRING_LENGTH-1] = 0; DPN_BUFFER_DESC bufferDesc; bufferDesc.dwBufferSize = sizeof(GAMEMSG_CHAT); bufferDesc.pBufferData = (BYTE*) &msgChat; DPNHANDLE hAsync; s_pDP->SendTo( DPNID_ALL_PLAYERS_GROUP, &bufferDesc, 1, 0, NULL, &hAsync, 0 ); return S_OK; } /**************************************************************************** * * DirectPlayMessageHandler * ****************************************************************************/ HRESULT WINAPI DirectPlayMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer ) { switch( dwMessageId ) { case DPN_MSGID_CONNECT_COMPLETE: { PDPNMSG_CONNECT_COMPLETE pConnectCompleteMsg; pConnectCompleteMsg = (PDPNMSG_CONNECT_COMPLETE)pMsgBuffer; // Set m_hrConnectComplete, then set an event letting // everyone know that the DPN_MSGID_CONNECT_COMPLETE msg // has been handled s_hrConnectComplete = pConnectCompleteMsg->hResultCode; SetEvent( s_hConnectCompleteEvent ); break; } case DPN_MSGID_ENUM_HOSTS_RESPONSE: { PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg; pEnumHostsResponseMsg = (PDPNMSG_ENUM_HOSTS_RESPONSE)pMsgBuffer; // Take note of the host response SessionsDlgNoteEnumResponse( pEnumHostsResponseMsg ); break; } case DPN_MSGID_ASYNC_OP_COMPLETE: { PDPNMSG_ASYNC_OP_COMPLETE pAsyncOpCompleteMsg; pAsyncOpCompleteMsg = (PDPNMSG_ASYNC_OP_COMPLETE)pMsgBuffer; if( pAsyncOpCompleteMsg->hAsyncOp == s_hEnumAsyncOp ) { SessionsDlgEnumListCleanup(); s_hEnumAsyncOp = NULL; // Ignore errors if we are connecting already or something else failed if( !s_bConnecting && s_pNetInfo->m_testResult.m_iStepThatFailed == 0 ) { if( FAILED(pAsyncOpCompleteMsg->hResultCode) ) { if( pAsyncOpCompleteMsg->hResultCode == DPNERR_USERCANCEL ) { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ENUMSESSIONS; s_pNetInfo->m_testResult.m_hr = pAsyncOpCompleteMsg->hResultCode; s_pNetInfo->m_testResult.m_bCancelled = TRUE; } else if( pAsyncOpCompleteMsg->hResultCode == DPNERR_ADDRESSING ) { TCHAR szTitle[MAX_PATH]; TCHAR szMessage[MAX_PATH]; LoadString(NULL, IDS_APPFULLNAME, szTitle, MAX_PATH); LoadString(NULL, IDS_SESSIONLISTERROR, szMessage, MAX_PATH); MessageBox(s_hwndSessionDlg, szMessage, szTitle, MB_OK); s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ENUMSESSIONS; s_pNetInfo->m_testResult.m_hr = pAsyncOpCompleteMsg->hResultCode; s_pNetInfo->m_testResult.m_bCancelled = TRUE; } else { s_pNetInfo->m_testResult.m_iStepThatFailed = TESTID_ENUMSESSIONS; s_pNetInfo->m_testResult.m_hr = pAsyncOpCompleteMsg->hResultCode; } EndDialog(s_hwndSessionDlg, 1); } } } break; } case DPN_MSGID_TERMINATE_SESSION: { PDPNMSG_TERMINATE_SESSION pTerminateSessionMsg; pTerminateSessionMsg = (PDPNMSG_TERMINATE_SESSION)pMsgBuffer; EndDialog(s_hDlg,0); break; } case DPN_MSGID_CREATE_PLAYER: { HRESULT hr; PDPNMSG_CREATE_PLAYER pCreatePlayerMsg; pCreatePlayerMsg = (PDPNMSG_CREATE_PLAYER)pMsgBuffer; // Get the peer info and extract its name DWORD dwSize = 0; DPN_PLAYER_INFO* pdpPlayerInfo = NULL; // Create a new and fill in a APP_PLAYER_INFO APP_PLAYER_INFO* pPlayerInfo = new APP_PLAYER_INFO; ZeroMemory( pPlayerInfo, sizeof(APP_PLAYER_INFO) ); pPlayerInfo->dpnidPlayer = pCreatePlayerMsg->dpnidPlayer; wcscpy( pPlayerInfo->strPlayerName, L"???" ); pPlayerInfo->lRefCount = 1; hr = s_pDP->GetPeerInfo( pCreatePlayerMsg->dpnidPlayer, pdpPlayerInfo, &dwSize, 0 ); if( SUCCEEDED(hr) || hr == DPNERR_BUFFERTOOSMALL ) { pdpPlayerInfo = (DPN_PLAYER_INFO*) new BYTE[ dwSize ]; ZeroMemory( pdpPlayerInfo, dwSize ); pdpPlayerInfo->dwSize = sizeof(DPN_PLAYER_INFO); hr = s_pDP->GetPeerInfo( pCreatePlayerMsg->dpnidPlayer, pdpPlayerInfo, &dwSize, 0 ); if( SUCCEEDED(hr) ) { // This stores a extra TCHAR copy of the player name for // easier access. This will be redundent copy since DPlay // also keeps a copy of the player name in GetPeerInfo() wcsncpy( pPlayerInfo->strPlayerName, pdpPlayerInfo->pwszName, MAX_PLAYER_NAME ); pPlayerInfo->strPlayerName[MAX_PLAYER_NAME-1] = 0; } } if( pdpPlayerInfo->dwPlayerFlags & DPNPLAYER_LOCAL ) s_dpnidLocalPlayer = pCreatePlayerMsg->dpnidPlayer; delete[] pdpPlayerInfo; pdpPlayerInfo = NULL; if( s_hDlg ) { // Record the buffer handle so the buffer can be returned later APP_PLAYER_MSG* pPlayerMsg = new APP_PLAYER_MSG; wcscpy( pPlayerMsg->strPlayerName, pPlayerInfo->strPlayerName ); // Pass the APP_PLAYER_MSG to the main dialog thread, so it can // process it. It will also cleanup the struct PostMessage( s_hDlg, WM_APP_JOIN, pPlayerInfo->dpnidPlayer, (LPARAM) pPlayerMsg ); } // Tell DirectPlay to store this pPlayerInfo // pointer in the pvPlayerContext. pCreatePlayerMsg->pvPlayerContext = pPlayerInfo; // Update the number of active players, and // post a message to the dialog thread to update the // UI. This keeps the DirectPlay message handler // from blocking InterlockedIncrement( &s_lNumberOfActivePlayers ); break; } case DPN_MSGID_DESTROY_PLAYER: { PDPNMSG_DESTROY_PLAYER pDestroyPlayerMsg; pDestroyPlayerMsg = (PDPNMSG_DESTROY_PLAYER)pMsgBuffer; APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pDestroyPlayerMsg->pvPlayerContext; if( s_hDlg ) { // Record the buffer handle so the buffer can be returned later APP_PLAYER_MSG* pPlayerMsg = new APP_PLAYER_MSG; wcscpy( pPlayerMsg->strPlayerName, pPlayerInfo->strPlayerName ); // Pass the APP_PLAYER_MSG to the main dialog thread, so it can // process it. It will also cleanup the struct PostMessage( s_hDlg, WM_APP_LEAVE, pPlayerInfo->dpnidPlayer, (LPARAM) pPlayerMsg ); } PLAYER_LOCK(); // enter player context CS PLAYER_RELEASE( pPlayerInfo ); // Release player and cleanup if needed PLAYER_UNLOCK(); // leave player context CS // Update the number of active players, and // post a message to the dialog thread to update the // UI. This keeps the DirectPlay message handler // from blocking InterlockedDecrement( &s_lNumberOfActivePlayers ); break; } case DPN_MSGID_RECEIVE: { PDPNMSG_RECEIVE pReceiveMsg; pReceiveMsg = (PDPNMSG_RECEIVE)pMsgBuffer; APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pReceiveMsg->pvPlayerContext; GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*) pReceiveMsg->pReceiveData; if( pReceiveMsg->dwReceiveDataSize == sizeof(GAMEMSG_CHAT) && pMsg->nType == GAME_MSGID_CHAT ) { // This message is sent when a player has send a chat message to us, so // post a message to the dialog thread to update the UI. // This keeps the DirectPlay threads from blocking, and also // serializes the recieves since DirectPlayMessageHandler can // be called simultaneously from a pool of DirectPlay threads. GAMEMSG_CHAT* pChatMessage = (GAMEMSG_CHAT*) pMsg; // Record the buffer handle so the buffer can be returned later APP_QUEUE_CHAT_MSG* pQueuedChat = new APP_QUEUE_CHAT_MSG; _snwprintf( pQueuedChat->strChatBuffer, MAX_CHAT_STRING, L"<%s> %s\r\n", pPlayerInfo->strPlayerName, pChatMessage->strChatString ); pQueuedChat->strChatBuffer[MAX_CHAT_STRING-1]=0; // Pass the APP_QUEUE_CHAT_MSG to the main dialog thread, so it can // process it. It will also cleanup the struct PostMessage( s_hDlg, WM_APP_CHAT, pPlayerInfo->dpnidPlayer, (LPARAM) pQueuedChat ); } break; } } return S_OK; } /**************************************************************************** * * ConvertAnsiStringToWide * ****************************************************************************/ VOID ConvertAnsiStringToWide( WCHAR* wstrDestination, const CHAR* strSource, int cchDestChar ) { if( wstrDestination==NULL || strSource==NULL ) return; if( cchDestChar == -1 ) cchDestChar = strlen(strSource)+1; MultiByteToWideChar( CP_ACP, 0, strSource, -1, wstrDestination, cchDestChar-1 ); wstrDestination[cchDestChar-1] = 0; } /**************************************************************************** * * ConvertGenericStringToWide * ****************************************************************************/ VOID ConvertGenericStringToWide( WCHAR* wstrDestination, const TCHAR* tstrSource, int cchDestChar ) { if( wstrDestination==NULL || tstrSource==NULL ) return; #ifdef _UNICODE if( cchDestChar == -1 ) wcscpy( wstrDestination, tstrSource ); else { wcsncpy( wstrDestination, tstrSource, cchDestChar ); wstrDestination[cchDestChar-1] = 0; } #else ConvertAnsiStringToWide( wstrDestination, tstrSource, cchDestChar ); #endif } /**************************************************************************** * * ConvertWideStringToGeneric * ****************************************************************************/ VOID ConvertWideStringToGeneric( TCHAR* tstrDestination, const WCHAR* wstrSource, int cchDestChar ) { if( tstrDestination==NULL || wstrSource==NULL ) return; #ifdef _UNICODE if( cchDestChar == -1 ) wcscpy( tstrDestination, wstrSource ); else { wcsncpy( tstrDestination, wstrSource, cchDestChar ); tstrDestination[cchDestChar-1] = 0; } #else ConvertWideStringToAnsi( tstrDestination, wstrSource, cchDestChar ); #endif } /**************************************************************************** * * ConvertWideStringToAnsi * ****************************************************************************/ VOID ConvertWideStringToAnsi( CHAR* strDestination, const WCHAR* wstrSource, int cchDestChar ) { if( strDestination==NULL || wstrSource==NULL ) return; if( cchDestChar == -1 ) cchDestChar = wcslen(wstrSource)+1; WideCharToMultiByte( CP_ACP, 0, wstrSource, -1, strDestination, cchDestChar-1, NULL, NULL ); strDestination[cchDestChar-1] = 0; }