//----------------------------------------------------------------------------
// File: DataRelay.cpp
//
// Desc: The main game file for the DataRelay sample.  It connects 
//       players together with two dialog boxes to prompt users on the 
//       connection settings to join or create a session. After the user 
//       connects to a sesssion, the sample displays a multiplayer stage. 
// 
//       This sample uses DirectPlay to process high volumes of incoming 
//       data on a seperate worker while avoiding copying the incoming packets
//       by using the ReturnBuffer() call.  It also shows some basics of 
//       thread synconization between the worker thread and the DirectPlay
//       message handler thread pool.
//
// Copyright (c) 1999-2001 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#ifndef STRICT
#define STRICT
#endif // !STRICT

#include <windows.h>
#include <commctrl.h>
#include <basetsd.h>
#include <mmsystem.h>
#include <dxerr9.h>
#include <dplay8.h>
#include <dplobby8.h>
#include <stdio.h>
#include "NetConnect.h"
#include "DXUtil.h"
#include "resource.h"

//-----------------------------------------------------------------------------
// Platform-dependent includes
//-----------------------------------------------------------------------------
#ifdef UNDER_CE
    #define assert ASSERT // The CE headers define an ASSERT macro
    #include <aygshell.h>
#else
    #include <assert.h>
    #include <process.h>
#endif // !UNDER_CE


//-----------------------------------------------------------------------------
// Player context locking defines
//-----------------------------------------------------------------------------
CRITICAL_SECTION g_csPlayerContext;
#define PLAYER_LOCK()                   EnterCriticalSection( &g_csPlayerContext ); 
#define PLAYER_ADDREF( pPlayerInfo )    if( pPlayerInfo ) pPlayerInfo->lRefCount++;
#define PLAYER_RELEASE( pPlayerInfo )   if( pPlayerInfo ) { pPlayerInfo->lRefCount--; if( pPlayerInfo->lRefCount <= 0 ) SAFE_DELETE( pPlayerInfo ); } pPlayerInfo = NULL;
#define PLAYER_UNLOCK()                 LeaveCriticalSection( &g_csPlayerContext );


//-----------------------------------------------------------------------------
// Defines, and constants
//-----------------------------------------------------------------------------
#define DPLAY_SAMPLE_KEY        TEXT("Software\\Microsoft\\DirectX DirectPlay Samples")
#define MAX_PLAYER_NAME         14
#define TIMERID_NETWORK         1
#define TIMERID_STATS           2
#define WM_APP_UPDATE_TARGETS   (WM_APP + 0)
#define WM_APP_APPEND_TEXT      (WM_APP + 1)
#define DATARELAY_PORT          2503

// This GUID allows DirectPlay to find other instances of the same game on
// the network.  So it must be unique for every game, and the same for 
// every instance of that game.  // {BA214178-AAE6-4ea6-84E0-65CE36F84479}
GUID g_guidApp = { 0xba214178, 0xaae6, 0x4ea6, { 0x84, 0xe0, 0x65, 0xce, 0x36, 0xf8, 0x44, 0x79 } };

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
    TCHAR strPlayerName[MAX_PLAYER_NAME];  // Player name
    DWORD dwFlags;                          // Player flags
    APP_PLAYER_INFO* pNext;
    APP_PLAYER_INFO* pPrev;
};




//-----------------------------------------------------------------------------
// App specific DirectPlay messages and structures 
//-----------------------------------------------------------------------------
#define GAME_MSGID_GAMEPACKET       1

// Change compiler pack alignment to be BYTE aligned, and pop the current value
#pragma pack( push, 1 )

struct GAMEMSG_GENERIC
{
    DWORD dwType;
    DWORD dwPacketId;
};

#define GAMEMSG_GENERIC_SIZE 8

struct GAMEMSG_DATA_512 : GAMEMSG_GENERIC
{
    BYTE pBuffer[512-GAMEMSG_GENERIC_SIZE];
};

struct GAMEMSG_DATA_256 : GAMEMSG_GENERIC
{
    BYTE pBuffer[256-GAMEMSG_GENERIC_SIZE];
};

struct GAMEMSG_DATA_128 : GAMEMSG_GENERIC
{
    BYTE pBuffer[128-GAMEMSG_GENERIC_SIZE];
};

struct GAMEMSG_DATA_64 : GAMEMSG_GENERIC
{
    BYTE pBuffer[64-GAMEMSG_GENERIC_SIZE];
};

struct GAMEMSG_DATA_32 : GAMEMSG_GENERIC
{
    BYTE pBuffer[32-GAMEMSG_GENERIC_SIZE];
};

struct GAMEMSG_DATA_16 : GAMEMSG_GENERIC
{
    BYTE pBuffer[16-GAMEMSG_GENERIC_SIZE];
};

#define DATA_TYPE_NETPACKET_RECIEVE       1
#define DATA_TYPE_NETPACKET_SENT          2
#define DATA_TYPE_NETPACKET_TIMEOUT       3

struct GAMEMSG_DATA_NODE
{
    GAMEMSG_GENERIC*    pDataMsg;
    DWORD               dwPacketId;
    APP_PLAYER_INFO*    pPlayerFrom; 
    DWORD               dwReceiveDataSize;
    DPNHANDLE           hBufferHandle;
    DWORD               dwType;
    GAMEMSG_DATA_NODE*  pNext;
    GAMEMSG_DATA_NODE*  pPrev;
};

// Pop the old pack alignment
#pragma pack( pop )




//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------
TCHAR              g_strAppName[256]          = TEXT("DataRelay");
IDirectPlay8Peer*  g_pDP                      = NULL;   // DirectPlay peer object
CNetConnectWizard* g_pNetConnectWizard        = NULL;   // Connection wizard
IDirectPlay8LobbiedApplication* g_pLobbiedApp = NULL;   // DirectPlay lobbied app 
BOOL               g_bWasLobbyLaunched        = FALSE;   // TRUE if lobby launched
HINSTANCE          g_hInst                    = NULL;   // HINST of app
HWND               g_hDlg                     = NULL;   // HWND of main dialog
HRESULT            g_hrDialog;                          // Exit code for app 
TCHAR              g_strLocalPlayerName[MAX_PATH];      // Local player name
TCHAR              g_strSessionName[MAX_PATH];          // Session name
TCHAR              g_strPreferredProvider[MAX_PATH];    // Provider string
HANDLE             g_hDPDataAvailEvent        = NULL;   // Signaled if there is data to process
HANDLE             g_hShutdownEvent           = NULL;   // Signaled if shutting down
DWORD              g_dwPacketId               = 0;      // Packet id counter
APP_PLAYER_INFO*   g_pTargetPlayer            = NULL;   // The current target player
BOOL               g_bSendingData             = FALSE;  // TRUE if sending data
DPNID              g_dpnidLocalPlayer         = 0;      // DPNID of local player
DPNID              g_dpnidHostPlayer          = 0;      // DPNID of host player
DWORD              g_dwDataRecieved           = 0;      // Amount of data recieved 
DWORD              g_dwDataSent               = 0;      // Amount of data sent
DWORD              g_dwSendTimeout            = 0;      // Length of send timeout
DWORD              g_dwTimeBetweenSends       = 0;      // Time between packet sends
DWORD              g_dwSendSize               = 0;      // Size in bytes of packet
#ifdef UNDER_CE
ULONG              g_dwProcessNetDataThreadID = 0;      // Worker thread ID
#else // !UNDER_CE
UINT               g_dwProcessNetDataThreadID = 0;      // Worker thread ID
#endif // UNDER_CE
HANDLE             g_hProcessNetDataThread    = NULL;   // Worker thread handle
APP_PLAYER_INFO    g_PlayerHead;                        // Linked list of players connected
GAMEMSG_DATA_NODE  g_DataHead;                          // Linked list of data to process
CRITICAL_SECTION   g_csPlayerList;                      // CS for g_PlayerHead
CRITICAL_SECTION   g_csDataList;                        // CS for g_DataHead
APP_PLAYER_INFO*   g_pConnInfoTargetPlayer    = NULL;   // The current target for Connection Info




//-----------------------------------------------------------------------------
// Function-prototypes
//-----------------------------------------------------------------------------
HRESULT WINAPI   DirectPlayMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer );
HRESULT WINAPI   DirectPlayLobbyMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer );
INT_PTR CALLBACK SampleDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
#ifdef UNDER_CE
ULONG   WINAPI   ProcessNetDataProc( LPVOID lpParameter );
#else // !UNDER_CE
UINT    WINAPI   ProcessNetDataProc( LPVOID lpParameter );
#endif // UNDER_CE
HRESULT InitDirectPlay();
HRESULT OnInitDialog( HWND hDlg );
VOID    FillTargetCombo( HWND hDlg );
VOID    FillOneTimeCombos( HWND hDlg );
HRESULT LinkPlayer( DPNID dpnid, APP_PLAYER_INFO** ppPlayerInfo );
VOID    FillMemoryRandom( PVOID pDest, SIZE_T length );
HRESULT SendNetworkData();
HRESULT ProcessData();
VOID    AppendTextToEditControl( HWND hDlg, TCHAR* strNewLogLine );
VOID    ReadCombos( HWND hDlg );
VOID    UpdateSendQueueInfo( HWND hDlg );
VOID    UpdateStats( HWND hDlg );
HRESULT RefreshStatusBarText( DPNID idLocal, DPNID idHost );
HRESULT GetHostnamePortString( TCHAR* str, LPDIRECTPLAY8ADDRESS pAddress, DWORD dwBufferLen );





//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Entry point for the application.  Since we use a simple dialog for 
//       user interaction we don't need to pump messages.
//-----------------------------------------------------------------------------
INT APIENTRY _tWinMain( HINSTANCE hInst, HINSTANCE hPrevInst, 
                        LPTSTR pCmdLine, INT nCmdShow )
{
    HRESULT hr;
    BOOL    bConnectSuccess     = FALSE;
    HKEY    hDPlaySampleRegKey;

    InitCommonControls();

#ifdef UNDER_CE
    // This is needed along with a hidden control in the RC file to make the
    // soft keyboard pop up when the user selects an edit control.
    SHInitExtraControls();
#endif // UNDER_CE

    // Create events and critical sections
    g_hDPDataAvailEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); 
    g_hShutdownEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); 
    InitializeCriticalSection( &g_csPlayerList );
    InitializeCriticalSection( &g_csDataList );
    InitializeCriticalSection( &g_csPlayerContext );

    g_hInst = hInst;

    // Init circular linked list
    ZeroMemory( &g_PlayerHead, sizeof(APP_PLAYER_INFO) );
    g_PlayerHead.pNext = &g_PlayerHead;
    g_PlayerHead.pPrev = &g_PlayerHead;
    g_PlayerHead.dpnidPlayer = DPNID_ALL_PLAYERS_GROUP;
    _tcscpy( g_PlayerHead.strPlayerName, TEXT("Everyone") );

    // Init circular linked list
    ZeroMemory( &g_DataHead, sizeof(GAMEMSG_DATA_NODE) );
    g_DataHead.pNext = &g_DataHead;
    g_DataHead.pPrev = &g_DataHead;  

    // Read persistent state information from registry
    RegCreateKeyEx( HKEY_CURRENT_USER, DPLAY_SAMPLE_KEY, 0, NULL,
                    REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, 
                    &hDPlaySampleRegKey, NULL );
    DXUtil_ReadStringRegKeyCch( hDPlaySampleRegKey, TEXT("Player Name"), 
                             g_strLocalPlayerName, MAX_PATH, TEXT("TestPlayer") );
    DXUtil_ReadStringRegKeyCch( hDPlaySampleRegKey, TEXT("Session Name"), 
                             g_strSessionName, MAX_PATH, TEXT("TestGame") );
    DXUtil_ReadStringRegKeyCch( hDPlaySampleRegKey, TEXT("Preferred Provider"), 
                             g_strPreferredProvider, MAX_PATH, TEXT("DirectPlay8 TCP/IP Service Provider") );

    // Init COM so we can use CoCreateInstance
    CoInitializeEx( NULL, COINIT_MULTITHREADED );
    
    // Create helper class
    g_pNetConnectWizard = new CNetConnectWizard( hInst, NULL, g_strAppName, &g_guidApp );

    if( FAILED( hr = InitDirectPlay() ) )
    {
        DXTRACE_ERR_MSGBOX( TEXT("InitDirectPlay"), hr );
        MessageBox( NULL, TEXT("Failed initializing IDirectPlay8Peer. ")
                    TEXT("The sample will now quit."),
                    TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
        return FALSE;
    }

    // Check if we were launched from a lobby client
    if( g_bWasLobbyLaunched && g_pNetConnectWizard->HaveConnectionSettingsFromLobby() )
    {
        // If were lobby launched then DPL_MSGID_CONNECT has already been
        // handled, so we can just tell the wizard to connect to the lobby
        // that has sent us a DPL_MSGID_CONNECT msg.
        if( FAILED( hr = g_pNetConnectWizard->ConnectUsingLobbySettings() ) )
        {
            DXTRACE_ERR_MSGBOX( TEXT("ConnectUsingLobbySettings"), hr );
            MessageBox( NULL, TEXT("Failed to connect using lobby settings. ")
                        TEXT("The sample will now quit."),
                        TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );

            bConnectSuccess = FALSE;
        }
        else
        {
            // Read information from g_pNetConnectWizard
            _tcsncpy( g_strLocalPlayerName, g_pNetConnectWizard->GetPlayerName(), MAX_PLAYER_NAME );
            g_strLocalPlayerName[MAX_PLAYER_NAME-1]=0;

            bConnectSuccess = TRUE; 
        }
    }
    else
    {
        // If not lobby launched, prompt the user about the network 
        // connection and which session they would like to join or 
        // if they want to create a new one.

        // Setup connection wizard
        g_pNetConnectWizard->SetPlayerName( g_strLocalPlayerName );
        g_pNetConnectWizard->SetSessionName( g_strSessionName );
        g_pNetConnectWizard->SetPreferredProvider( g_strPreferredProvider );
        g_pNetConnectWizard->SetDefaultPort( DATARELAY_PORT );

        // Do the connection wizard
        hr = g_pNetConnectWizard->DoConnectWizard( FALSE );        
        if( FAILED( hr ) ) 
        {
            DXTRACE_ERR_MSGBOX( TEXT("DoConnectWizard"), hr );
            MessageBox( NULL, TEXT("Multiplayer connect failed. ")
                        TEXT("The sample will now quit."),
                        TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
            bConnectSuccess = FALSE;
        } 
        else if( hr == NCW_S_QUIT ) 
        {
            // The user canceled the Multiplayer connect, so quit 
            bConnectSuccess = FALSE;
        }
        else
        {
            bConnectSuccess = TRUE; 

            // Read information from g_pNetConnectWizard
            _tcsncpy( g_strLocalPlayerName, g_pNetConnectWizard->GetPlayerName(), MAX_PLAYER_NAME-1 );
            g_strLocalPlayerName[ MAX_PLAYER_NAME-1 ]=0;

            _tcsncpy( g_strSessionName, g_pNetConnectWizard->GetSessionName(), MAX_PATH-1 );
            g_strSessionName[ MAX_PATH-1 ] = 0;

            _tcsncpy( g_strPreferredProvider, g_pNetConnectWizard->GetPreferredProvider(), MAX_PATH-1 );
            g_strPreferredProvider[ MAX_PATH-1 ] = 0;

            // Write information to the registry
            DXUtil_WriteStringRegKey( hDPlaySampleRegKey, TEXT("Player Name"), g_strLocalPlayerName );
            DXUtil_WriteStringRegKey( hDPlaySampleRegKey, TEXT("Session Name"), g_strSessionName );
            DXUtil_WriteStringRegKey( hDPlaySampleRegKey, TEXT("Preferred Provider"), g_strPreferredProvider );
        }
    }

    if( bConnectSuccess )
    {
        // For this sample, we just start a simple dialog box game.
        g_hrDialog = S_OK;
        DialogBox( hInst, MAKEINTRESOURCE(IDD_MAIN_GAME), NULL, 
                   (DLGPROC) SampleDlgProc );
    }

    // Close down process data thread
    SetEvent( g_hShutdownEvent );
    WaitForSingleObject( g_hProcessNetDataThread, INFINITE );
    CloseHandle( g_hProcessNetDataThread );

    if( FAILED( g_hrDialog ) )
    {
        if( g_hrDialog == DPNERR_CONNECTIONLOST )
        {
            MessageBox( NULL, TEXT("The DirectPlay session was lost. ")
                        TEXT("The sample will now quit."),
                        TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
        }
        else
        {
            DXTRACE_ERR_MSGBOX( TEXT("DialogBox"), g_hrDialog );
            MessageBox( NULL, TEXT("An error occured during the game. ")
                        TEXT("The sample will now quit."),
                        TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR );
        }
    }

    // Cleanup DirectPlay and helper classes
    g_pNetConnectWizard->Shutdown();

    if( g_pDP )
    {
        g_pDP->Close(0);
        SAFE_RELEASE( g_pDP );
    }

    if( g_pLobbiedApp )
    {
        g_pLobbiedApp->Close( 0 );
        SAFE_RELEASE( g_pLobbiedApp );
    }    

    // Don't delete the wizard until we know that 
    // DirectPlay is out of its message handlers.
    // This will be true after Close() has been called. 
    SAFE_DELETE( g_pNetConnectWizard );

    // Cleanup circular linked list, g_PlayerHead
    {
        EnterCriticalSection( &g_csPlayerList );
        APP_PLAYER_INFO* pNode = g_PlayerHead.pNext;
        APP_PLAYER_INFO* pDeleteNode;
        while( pNode != &g_PlayerHead ) 
        {
            pDeleteNode = pNode;
            pNode = pNode->pNext;
            SAFE_DELETE( pDeleteNode );
        }
        LeaveCriticalSection( &g_csPlayerList );
    }

    // Cleanup circular linked list, g_DataHead
    {
        EnterCriticalSection( &g_csDataList );
        GAMEMSG_DATA_NODE* pNode = g_DataHead.pNext;
        GAMEMSG_DATA_NODE* pDeleteNode;
        while( pNode != &g_DataHead ) 
        {
            pDeleteNode = pNode;
            pNode = pNode->pNext;
            SAFE_DELETE( pDeleteNode );
        }
        LeaveCriticalSection( &g_csDataList );
    }

    // Cleanup Win32 resources
    RegCloseKey( hDPlaySampleRegKey );
    CloseHandle( g_hShutdownEvent );
    CloseHandle( g_hDPDataAvailEvent );
    DeleteCriticalSection( &g_csPlayerList );
    DeleteCriticalSection( &g_csDataList );
    DeleteCriticalSection( &g_csPlayerContext );
    CoUninitialize();

    return TRUE;
}




//-----------------------------------------------------------------------------
// Name: InitDirectPlay()
// Desc: Create and initialize the DirectPlay peer object
//-----------------------------------------------------------------------------
HRESULT InitDirectPlay()
{
    DPNHANDLE hLobbyLaunchedConnection = NULL;
    HRESULT hr;

    // Create IDirectPlay8Peer
    if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Peer, NULL, 
                                       CLSCTX_INPROC_SERVER,
                                       IID_IDirectPlay8Peer, 
                                       (LPVOID*) &g_pDP ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr );

    // Create IDirectPlay8LobbiedApplication
    if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8LobbiedApplication, NULL, 
                                       CLSCTX_INPROC_SERVER,
                                       IID_IDirectPlay8LobbiedApplication, 
                                       (LPVOID*) &g_pLobbiedApp ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr );

    // Init the helper class, now that g_pDP and g_pLobbiedApp are valid
    g_pNetConnectWizard->Init( g_pDP, g_pLobbiedApp );

    // Turn off parameter validation in release builds
#ifdef _DEBUG
    const DWORD dwInitFlags = 0;
#else
    const DWORD dwInitFlags = DPNINITIALIZE_DISABLEPARAMVAL;
#endif // _DEBUG

    // Init IDirectPlay8Peer
    if( FAILED( hr = g_pDP->Initialize( NULL, DirectPlayMessageHandler, dwInitFlags ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("Initialize"), hr );

    // Init IDirectPlay8LobbiedApplication.  Before this Initialize() returns 
    // a DPL_MSGID_CONNECT msg may come in to the DirectPlayLobbyMessageHandler 
    // so be prepared ahead of time.
    if( FAILED( hr = g_pLobbiedApp->Initialize( NULL, DirectPlayLobbyMessageHandler, 
                                                &hLobbyLaunchedConnection, dwInitFlags ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("Initialize"), hr );

    // IDirectPlay8LobbiedApplication::Initialize returns a handle to a connnection
    // if we have been lobby launced.  Initialize is guanteeded to return after 
    // the DPL_MSGID_CONNECT msg has been processed.  So unless a we are expected 
    // multiple lobby connections, we do not need to remember the lobby connection
    // handle since it will be recorded upon the DPL_MSGID_CONNECT msg.
    g_bWasLobbyLaunched = ( hLobbyLaunchedConnection != NULL );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: SampleDlgProc()
// Desc: Handles dialog messages
//-----------------------------------------------------------------------------
INT_PTR CALLBACK SampleDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg ) 
    {
        case WM_INITDIALOG:
        {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
            SHINITDLGINFO   shidi;
            memset( &shidi, 0, sizeof(SHINITDLGINFO) );
            shidi.dwMask = SHIDIM_FLAGS;
            shidi.dwFlags = SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN;
            shidi.hDlg = hDlg;

            SetForegroundWindow( hDlg );
            SHInitDialog( &shidi );
#endif // WIN32_PLATFORM_PSPC
            g_hDlg = hDlg;
            if( FAILED( g_hrDialog = OnInitDialog( hDlg ) ) )
            {
                DXTRACE_ERR_MSGBOX( TEXT("OnInitDialog"), g_hrDialog );
                EndDialog( hDlg, 0 );
            }

            break;
        }

        case WM_APP_UPDATE_TARGETS:
        {
            // Display the player names in the UI
            CheckDlgButton( hDlg, IDC_SEND_READY, (g_bSendingData) ? BST_CHECKED : BST_UNCHECKED );
            PostMessage( hDlg, WM_COMMAND, IDC_SEND_READY, 0 );
            FillTargetCombo( g_hDlg );
            ReadCombos( g_hDlg );
            
            // Update the status bar text
            RefreshStatusBarText( g_dpnidLocalPlayer, g_dpnidHostPlayer );
            break;
        }

        case WM_APP_APPEND_TEXT:
        {
            // Append a string to the edit control
            TCHAR* strNewLogLine = (TCHAR*) wParam;
            AppendTextToEditControl( g_hDlg, strNewLogLine );
            SAFE_DELETE_ARRAY( strNewLogLine );
            break;
        }

        case WM_COMMAND:
        {
            switch( LOWORD(wParam) )
            {
                case IDC_SEND_READY:
                    g_bSendingData = (IsDlgButtonChecked( hDlg, IDC_SEND_READY ) == BST_CHECKED );
                    if( g_bSendingData )
                    {
                        SetTimer( g_hDlg, TIMERID_NETWORK, g_dwTimeBetweenSends, NULL );
                        SetDlgItemText( hDlg, IDC_SEND_READY, TEXT("Sending...") );
                    }
                    else
                    {
                        KillTimer( g_hDlg, TIMERID_NETWORK );
                        SetDlgItemText( hDlg, IDC_SEND_READY, TEXT("Send") );
                    }
                    break;

                case IDCANCEL:
                    g_hrDialog = S_OK;
                    EndDialog( hDlg, 0 );
                    return TRUE;

                case IDC_SEND_SIZE_COMBO:
                case IDC_SEND_RATE_COMBO:
                case IDC_SEND_TARGET_COMBO:
                case IDC_CONNINFO_COMBO:
                    if( HIWORD(wParam) == CBN_SELENDOK )
                        ReadCombos( hDlg );
                    break;
            }
            break;
        }

        case WM_TIMER:
        {
            switch( wParam )
            {
                case TIMERID_NETWORK:
                {
                    // Send network data
                    if( FAILED( g_hrDialog = SendNetworkData() ) )
                    {
                        DXTRACE_ERR_MSGBOX( TEXT("SendNetworkData"), g_hrDialog );
                        EndDialog( hDlg, 0 );
                    }
                    break;
                }
                
                case TIMERID_STATS:
                {
                    UpdateStats( hDlg );
                    break;
                }
            }
            break;
        }
    }

    return FALSE; // Didn't handle message
}




//-----------------------------------------------------------------------------
// Name: OnInitDialog()
// Desc: Initializes the dialog.
//-----------------------------------------------------------------------------
HRESULT OnInitDialog( HWND hDlg )
{
    // Load and set the icon
    HICON hIcon = LoadIcon( g_hInst, MAKEINTRESOURCE( IDI_MAIN ) );
    SendMessage( hDlg, WM_SETICON, ICON_BIG,   (LPARAM) hIcon );  // Set big icon
    SendMessage( hDlg, WM_SETICON, ICON_SMALL, (LPARAM) hIcon );  // Set small icon

    // Display local player's name
    SetDlgItemText( hDlg, IDC_PLAYER_NAME, g_strLocalPlayerName );

    // Start a thread to process the network data
#ifdef UNDER_CE
    g_hProcessNetDataThread = (HANDLE) CreateThread( NULL, 0, ProcessNetDataProc, 
                                                     hDlg, 0, &g_dwProcessNetDataThreadID );
#else
    g_hProcessNetDataThread = (HANDLE) _beginthreadex( NULL, 0, ProcessNetDataProc, 
                                                       hDlg, 0, &g_dwProcessNetDataThreadID );
#endif // UNDER_CE

    // Update the dialog box
    SendMessage( hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
    FillOneTimeCombos( hDlg );
    ReadCombos( hDlg );

    // Update the stats every second
    SetTimer( hDlg, TIMERID_STATS, 1000, NULL );

    if( g_pNetConnectWizard->IsHostPlayer() )
        SetWindowText( hDlg, TEXT("DataRelay (Host)") );
    else
        SetWindowText( hDlg, TEXT("DataRelay") );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: FillTargetCombo()
// Desc: Fills the target combo with all of the players
//-----------------------------------------------------------------------------
VOID FillTargetCombo( HWND hDlg )
{
    DWORD    dwNumberOfActivePlayers = 0;
    int      nIndex;
    int      nCurSelect;
    int      nNewSelect = 0;
    LONG_PTR lCurItemData; 
    HWND     hTargetCombo;
    HWND     hInfoTargetCombo;

    if( hDlg == NULL )
        return;

    hTargetCombo = GetDlgItem( hDlg, IDC_SEND_TARGET_COMBO );
    if( hTargetCombo == NULL )
        return;

    nCurSelect   = (int)SendMessage( hTargetCombo, CB_GETCURSEL, 0, 0 );
    lCurItemData = (LONG_PTR)SendMessage( hTargetCombo, CB_GETITEMDATA, nCurSelect, 0 );

    // Clear combo box
    SendMessage( hTargetCombo, CB_RESETCONTENT, 0, 0 );

    // Add "everyone"
    nIndex = (int)SendMessage( hTargetCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("Everyone") );
    SendMessage( hTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) &g_PlayerHead );

    // Enter the player list CS, because we are about to read shared memory, and 
    // the data may be changed concurrently from the DirectPlay
    // message handler threads
    EnterCriticalSection( &g_csPlayerList );

    // Add each player
    APP_PLAYER_INFO* pNode = g_PlayerHead.pNext;
    while( pNode != &g_PlayerHead ) 
    {
        if( (pNode->dwFlags & DPNPLAYER_LOCAL) == 0 )
        {
            nIndex = (int)SendMessage( hTargetCombo, CB_ADDSTRING, 0, (LPARAM) pNode->strPlayerName );
            SendMessage( hTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) pNode );
            if( lCurItemData == (LONG_PTR) pNode )
                nNewSelect = nIndex;
        }

        dwNumberOfActivePlayers++;
        pNode = pNode->pNext;
    }

    LeaveCriticalSection( &g_csPlayerList );
     
    SendMessage( hTargetCombo, CB_SETCURSEL, nNewSelect, 0 );  

    // Update the number of players in game counter
    TCHAR strNumberPlayers[32];
    wsprintf( strNumberPlayers, TEXT("%d"), dwNumberOfActivePlayers );
    SetDlgItemText( hDlg, IDC_NUM_PLAYERS, strNumberPlayers );

    if( dwNumberOfActivePlayers > 1 )
    {
        EnableWindow( GetDlgItem( hDlg, IDC_SEND_READY ), TRUE );
    }
    else
    {
        EnableWindow( GetDlgItem( hDlg, IDC_SEND_READY ), FALSE );
        CheckDlgButton( hDlg, IDC_SEND_READY, BST_UNCHECKED );
        SetDlgItemText( hDlg, IDC_SEND_READY, TEXT("Send") );
    }

    // Populate the connection info box    
    hInfoTargetCombo = GetDlgItem( hDlg, IDC_CONNINFO_COMBO );
        
    nCurSelect   = (int)SendMessage( hInfoTargetCombo, CB_GETCURSEL, 0, 0 );
    lCurItemData = (LONG_PTR)SendMessage( hInfoTargetCombo, CB_GETITEMDATA, nCurSelect, 0 );
    
    // Clear combo box
    SendMessage( hInfoTargetCombo, CB_RESETCONTENT, 0, 0 );
    
    // Add "none"
    nIndex = (int)SendMessage( hInfoTargetCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("None") );
    SendMessage( hInfoTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) &g_PlayerHead );

    // Enter the player list CS, because we are about to read shared memory, and 
    // the data may be changed concurrently from the DirectPlay
    // message handler threads
    EnterCriticalSection( &g_csPlayerList );

    // Add each player    
    pNode = g_PlayerHead.pNext;
    nNewSelect = 0;
    while( pNode != &g_PlayerHead ) 
    {
        if( (pNode->dwFlags & DPNPLAYER_LOCAL) == 0 )
        {
            nIndex = (int)SendMessage( hInfoTargetCombo, CB_ADDSTRING, 0, (LPARAM) pNode->strPlayerName );
            SendMessage( hInfoTargetCombo, CB_SETITEMDATA, nIndex, (LPARAM) pNode );
            if( lCurItemData == (LONG_PTR) pNode )
                nNewSelect = nIndex;
        }

        dwNumberOfActivePlayers++;
        pNode = pNode->pNext;
    }

    LeaveCriticalSection( &g_csPlayerList );
     
    SendMessage( hInfoTargetCombo, CB_SETCURSEL, nNewSelect, 0 );  
}




//-----------------------------------------------------------------------------
// Name: FillOneTimeCombos()
// Desc: Fill the unchanging UI combos box
//-----------------------------------------------------------------------------
VOID FillOneTimeCombos( HWND hDlg )
{
    if( hDlg == NULL )
        return;

    HWND hRateCombo = GetDlgItem( hDlg, IDC_SEND_RATE_COMBO );
    SendMessage( hRateCombo, WM_SETREDRAW, FALSE, 0 );
    SendMessage( hRateCombo, CB_RESETCONTENT, 0, 0 );
    SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("1000") );
    SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("500") );
    SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("250") );
    SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("100") );
    SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("50") );
    SendMessage( hRateCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("0") );
    SendMessage( hRateCombo, WM_SETREDRAW, TRUE, 0 );
    SendMessage( hRateCombo, CB_SETCURSEL, 0, 0 );  
    
    HWND hSizeCombo = GetDlgItem( hDlg, IDC_SEND_SIZE_COMBO );
    SendMessage( hSizeCombo, WM_SETREDRAW, FALSE, 0 );
    SendMessage( hSizeCombo, CB_RESETCONTENT, 0, 0 );
    SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("512") );
    SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("256") );
    SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("128") );
    SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("64") );
    SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("32") );
    SendMessage( hSizeCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("16") );
    SendMessage( hSizeCombo, WM_SETREDRAW, TRUE, 0 );
    SendMessage( hSizeCombo, CB_SETCURSEL, 0, 0 );  
 
    HWND hTimeoutCombo = GetDlgItem( hDlg, IDC_SEND_TIMEOUT_COMBO );
    SendMessage( hTimeoutCombo, WM_SETREDRAW, FALSE, 0 );
    SendMessage( hTimeoutCombo, CB_RESETCONTENT, 0, 0 );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("5") );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("10") );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("20") );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("50") );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("100") );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("250") );
    SendMessage( hTimeoutCombo, CB_ADDSTRING, 0, (LPARAM) TEXT("500") );
    SendMessage( hTimeoutCombo, WM_SETREDRAW, TRUE, 0 );
    SendMessage( hTimeoutCombo, CB_SETCURSEL, 2, 0 );  
}




//-----------------------------------------------------------------------------
// Name: ReadCombos()
// Desc: Reads the state of the combos and updates the global vars
//-----------------------------------------------------------------------------
VOID ReadCombos( HWND hDlg )
{
    TCHAR strText[128];
    int nCurSelect;

    // Get target player
    HWND hTargetCombo  = GetDlgItem( hDlg, IDC_SEND_TARGET_COMBO );
    nCurSelect = (int)SendMessage( hTargetCombo, CB_GETCURSEL, 0, 0 );
    if( nCurSelect != CB_ERR ) 
    {
        g_pTargetPlayer = (APP_PLAYER_INFO*) SendMessage( hTargetCombo, CB_GETITEMDATA, 
                                                          nCurSelect, 0 );
    }

    // Get rate (in ms)
    HWND hRateCombo  = GetDlgItem( hDlg, IDC_SEND_RATE_COMBO );
    nCurSelect = (int)SendMessage( hRateCombo, CB_GETCURSEL, 0, 0 );
    if( nCurSelect != CB_ERR ) 
    {
        SendMessage( hRateCombo, CB_GETLBTEXT, nCurSelect, (LPARAM) strText );
        g_dwTimeBetweenSends = _ttoi( strText );
        KillTimer( g_hDlg, TIMERID_NETWORK );
        SetTimer( g_hDlg, TIMERID_NETWORK, g_dwTimeBetweenSends, NULL );
    }

    // Get size (in bytes)
    HWND hSizeCombo = GetDlgItem( hDlg, IDC_SEND_SIZE_COMBO );
    nCurSelect = (int)SendMessage( hSizeCombo, CB_GETCURSEL, 0, 0 );
    if( nCurSelect != CB_ERR ) 
    {
        SendMessage( hSizeCombo, CB_GETLBTEXT, nCurSelect, (LPARAM) strText );
        g_dwSendSize = _ttoi( strText );
    }

    // Get timeout (in ms)
    HWND hTimeoutCombo = GetDlgItem( hDlg, IDC_SEND_TIMEOUT_COMBO );
    nCurSelect = (int)SendMessage( hTimeoutCombo, CB_GETCURSEL, 0, 0 );
    if( nCurSelect != CB_ERR ) 
    {
        SendMessage( hTimeoutCombo, CB_GETLBTEXT, nCurSelect, (LPARAM) strText );
        g_dwSendTimeout = _ttoi( strText );
    }

    // Get the ConnectionInfo Target player
    HWND hConnTargetCombo  = GetDlgItem( hDlg, IDC_CONNINFO_COMBO );
    nCurSelect = (int)SendMessage( hConnTargetCombo, CB_GETCURSEL, 0, 0 );
    if( nCurSelect != CB_ERR ) 
    {
        g_pConnInfoTargetPlayer = (APP_PLAYER_INFO*) SendMessage( hConnTargetCombo, CB_GETITEMDATA, 
                                                      nCurSelect, 0 );
    }
}




//-----------------------------------------------------------------------------
// Name: DirectPlayMessageHandler
// Desc: Handler for DirectPlay messages.  This function is called by
//       the DirectPlay message handler pool of threads, so be care of thread
//       synchronization problems with shared memory
//-----------------------------------------------------------------------------
HRESULT WINAPI DirectPlayMessageHandler( PVOID pvUserContext, 
                                         DWORD dwMessageId, 
                                         PVOID pMsgBuffer )
{
    // Try not to stay in this message handler for too long, otherwise
    // there will be a backlog of data.  The best solution is to 
    // queue data as it comes in, and then handle it on other threads
    // as this sample shows

    // This function is called by the DirectPlay message handler pool of 
    // threads, so be care of thread synchronization problems with shared memory

    HRESULT hReturn = S_OK;

    switch( dwMessageId )
    {
        case DPN_MSGID_CREATE_PLAYER:
        {
            HRESULT hr;
            PDPNMSG_CREATE_PLAYER pCreatePlayerMsg;
            pCreatePlayerMsg = (PDPNMSG_CREATE_PLAYER) pMsgBuffer;

            APP_PLAYER_INFO* pPlayerInfo;
            if( FAILED( hr = LinkPlayer( pCreatePlayerMsg->dpnidPlayer, 
                                         &pPlayerInfo ) ) )
                return DXTRACE_ERR_MSGBOX( TEXT("LinkPlayer"), hr );

            // Tell DirectPlay to store this pPlayerInfo 
            // pointer in the pvPlayerContext.
            pCreatePlayerMsg->pvPlayerContext = pPlayerInfo;

            // Post a message to the dialog thread to update the 
            // UI.  This keeps the DirectPlay message handler 
            // from blocking
            if( g_hDlg != NULL )
                PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
            break;
        }

        case DPN_MSGID_DESTROY_PLAYER:
        {
            PDPNMSG_DESTROY_PLAYER pDestroyPlayerMsg;
            pDestroyPlayerMsg = (PDPNMSG_DESTROY_PLAYER)pMsgBuffer;
            APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pDestroyPlayerMsg->pvPlayerContext;

            // Enter the player list CS, because we are about to 
            // modify shared memory, and the data may be changed 
            // concurrently from other DirectPlay message handler threads 
            EnterCriticalSection( &g_csPlayerList );

            // Unlink pPlayerInfo from the chain, m_PlayerHead.
            pPlayerInfo->pPrev->pNext = pPlayerInfo->pNext;
            pPlayerInfo->pNext->pPrev = pPlayerInfo->pPrev;

            LeaveCriticalSection( &g_csPlayerList );

            // Stop sending if the target was destroyed
            if( g_pTargetPlayer )
            {
                if( g_pTargetPlayer->dpnidPlayer == pPlayerInfo->dpnidPlayer )
                    g_bSendingData = FALSE;
            }

            PLAYER_LOCK();                  // enter player context CS
            PLAYER_RELEASE( pPlayerInfo );  // Release player and cleanup if needed
            PLAYER_UNLOCK();                // leave player context CS

            // Post a message to the dialog thread to update the 
            // UI.  This keeps the DirectPlay message handler 
            // from blocking
            if( g_hDlg != NULL )
                PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
            break;
        }

        case DPN_MSGID_TERMINATE_SESSION:
        {
            PDPNMSG_TERMINATE_SESSION pTerminateSessionMsg;
            pTerminateSessionMsg = (PDPNMSG_TERMINATE_SESSION)pMsgBuffer;

            g_hrDialog = DPNERR_CONNECTIONLOST;
            EndDialog( g_hDlg, 0 );
            break;
        }

        case DPN_MSGID_HOST_MIGRATE:
        {
            PDPNMSG_HOST_MIGRATE pHostMigrateMsg;
            pHostMigrateMsg = (PDPNMSG_HOST_MIGRATE)pMsgBuffer;

            if( pHostMigrateMsg->dpnidNewHost == g_dpnidLocalPlayer )
                SetWindowText( g_hDlg, TEXT("DataRelay (Host)") );

            g_dpnidHostPlayer = pHostMigrateMsg->dpnidNewHost;
            if( g_hDlg != NULL )
                PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );

            break;
        }

        case DPN_MSGID_RECEIVE:
        {
            PDPNMSG_RECEIVE pReceiveMsg;
            pReceiveMsg = (PDPNMSG_RECEIVE)pMsgBuffer;
            APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pReceiveMsg->pvPlayerContext;
            if( NULL == pPlayerInfo )
                break;

            // Validate incoming data: A malicious user could modify or create an application
            // to send bogus information; to help guard against logical errors and denial 
            // of service attacks, the size of incoming data should be checked against what
            // is expected.
            if( pReceiveMsg->dwReceiveDataSize < sizeof(GAMEMSG_GENERIC) )
                break;

            GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*) pReceiveMsg->pReceiveData;
            if( pMsg->dwType == GAME_MSGID_GAMEPACKET )
            {
                GAMEMSG_GENERIC* pDataMsg = (GAMEMSG_GENERIC*) pMsg;

                // Make a new GAMEMSG_DATA_NODE and hand it off to the
                // app worker thread.  It will process the node, and
                // then update the UI to show that the packet was processed
                GAMEMSG_DATA_NODE* pDataMsgNode = new GAMEMSG_DATA_NODE;    
                if( NULL == pDataMsgNode )
                    break;

                ZeroMemory( pDataMsgNode, sizeof(GAMEMSG_DATA_NODE) ); 

                pDataMsgNode->dwType            = DATA_TYPE_NETPACKET_RECIEVE;
                pDataMsgNode->dwPacketId        = pDataMsg->dwPacketId;
                pDataMsgNode->pDataMsg          = pDataMsg;
                pDataMsgNode->dwReceiveDataSize = pReceiveMsg->dwReceiveDataSize;
                pDataMsgNode->pPlayerFrom       = pPlayerInfo;
                pDataMsgNode->hBufferHandle     = pReceiveMsg->hBufferHandle;

                // Enter the data list CS, because we are about to modify shared memory, and 
                // the data may be changed concurrently from other DirectPlay
                // message handler threads, as well as the the app worker thread
                EnterCriticalSection( &g_csDataList );

                g_dwDataRecieved += pReceiveMsg->dwReceiveDataSize;

                // Then add it to the circular linked list, g_DataHead
                // so it can be processed by a worker thread
                pDataMsgNode->pNext = &g_DataHead;
                pDataMsgNode->pPrev = g_DataHead.pPrev;
                g_DataHead.pPrev->pNext = pDataMsgNode;
                g_DataHead.pPrev        = pDataMsgNode;

                LeaveCriticalSection( &g_csDataList );

                // Tell the app worker thread that there 
                // is new network data availible
                SetEvent( g_hDPDataAvailEvent );

                // Tell DirectPlay to assume that ownership of the buffer 
                // has been transferred to the application, and so it will 
                // neither free nor modify it until ownership is returned 
                // to DirectPlay through the ReturnBuffer() call.
                hReturn = DPNSUCCESS_PENDING;
            }
            break;
        }

        case DPN_MSGID_SEND_COMPLETE:
        {
            PDPNMSG_SEND_COMPLETE pSendCompleteMsg;
            pSendCompleteMsg = (PDPNMSG_SEND_COMPLETE)pMsgBuffer;

            GAMEMSG_GENERIC* pGameMsg;
            pGameMsg = (GAMEMSG_GENERIC*) pSendCompleteMsg->pvUserContext;

            if( pSendCompleteMsg->hResultCode == DPNERR_TIMEDOUT )
            {
                // Make a new GAMEMSG_DATA_NODE and hand it off to the
                // app worker thread.  It will process the node, and
                // then update the UI to show that the packet timed out
                GAMEMSG_DATA_NODE* pDataMsgNode = new GAMEMSG_DATA_NODE;
                ZeroMemory( pDataMsgNode, sizeof(GAMEMSG_DATA_NODE) ); 
                
                pDataMsgNode->dwType     = DATA_TYPE_NETPACKET_TIMEOUT;
                pDataMsgNode->dwPacketId = pGameMsg->dwPacketId;

                // Enter the data list CS, because we are about to modify shared memory, and 
                // the data may be changed concurrently from other DirectPlay
                // message handler threads, as well as the the app worker thread
                EnterCriticalSection( &g_csDataList );

                // Then add it to the circular linked list, g_DataHead
                // so it can be processed by a worker thread
                pDataMsgNode->pNext = &g_DataHead;
                pDataMsgNode->pPrev = g_DataHead.pPrev;
                g_DataHead.pPrev->pNext = pDataMsgNode;
                g_DataHead.pPrev        = pDataMsgNode;

                LeaveCriticalSection( &g_csDataList );

                // Tell the app worker thread that there 
                // is new data availible. 
                SetEvent( g_hDPDataAvailEvent );
            }

            SAFE_DELETE( pGameMsg );

            break;
        }
    }

    // Make sure the DirectPlay MessageHandler calls the CNetConnectWizard handler, 
    // so it can be informed of messages such as DPN_MSGID_ENUM_HOSTS_RESPONSE.
    if( DPNSUCCESS_PENDING != hReturn && SUCCEEDED(hReturn) && g_pNetConnectWizard )
        hReturn = g_pNetConnectWizard->MessageHandler( pvUserContext, dwMessageId, pMsgBuffer );
    
    return hReturn;
}




//-----------------------------------------------------------------------------
// Name: LinkPlayer()
// Desc: Link a new player to the circular linked list, g_PlayerHead.  This
//       is called by the DirectPlay message handler threads, so we enter a 
//       CS to avoid thread syncoronization problems.
//-----------------------------------------------------------------------------
HRESULT LinkPlayer( DPNID dpnid, APP_PLAYER_INFO** ppPlayerInfo )
{
    HRESULT hr;
    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;
    if( NULL == pPlayerInfo )
    {
        hr = E_OUTOFMEMORY;
        goto LErrorReturn;
    }

    pPlayerInfo->dpnidPlayer = dpnid;
    pPlayerInfo->lRefCount   = 1;

    // Get the peer info and extract its name 
    hr = DPNERR_CONNECTING;

    // GetPeerInfo might return DPNERR_CONNECTING when connecting, 
    // so just keep calling it if it does
    while( hr == DPNERR_CONNECTING ) 
        hr = g_pDP->GetPeerInfo( dpnid, pdpPlayerInfo, &dwSize, 0 );                                
        
    if( hr == DPNERR_BUFFERTOOSMALL )
    {
        pdpPlayerInfo = (DPN_PLAYER_INFO*) new BYTE[ dwSize ];
        if( NULL == pdpPlayerInfo )
        {
            hr = E_OUTOFMEMORY;
            goto LErrorReturn;
        }

        ZeroMemory( pdpPlayerInfo, dwSize );
        pdpPlayerInfo->dwSize = sizeof(DPN_PLAYER_INFO);
        
        hr = g_pDP->GetPeerInfo( dpnid, 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()
            DXUtil_ConvertWideStringToGenericCch( pPlayerInfo->strPlayerName, 
                                               pdpPlayerInfo->pwszName, MAX_PLAYER_NAME );    
                                               
            pPlayerInfo->dwFlags     = pdpPlayerInfo->dwPlayerFlags;
            
            if( pdpPlayerInfo->dwPlayerFlags & DPNPLAYER_LOCAL )
                g_dpnidLocalPlayer = dpnid;
            if( pdpPlayerInfo->dwPlayerFlags & DPNPLAYER_HOST )
                g_dpnidHostPlayer = dpnid;
        }
        
        SAFE_DELETE_ARRAY( pdpPlayerInfo );
    }

    // Enter the player list CS, because we are about to modify shared memory, and 
    // the data may be changed concurrently from the DirectPlay
    // message handler threads 
    EnterCriticalSection( &g_csPlayerList );

    // Then add it to the circular linked list, g_PlayerHead,
    pPlayerInfo->pNext = g_PlayerHead.pNext;
    pPlayerInfo->pPrev = &g_PlayerHead;
    g_PlayerHead.pNext->pPrev = pPlayerInfo;
    g_PlayerHead.pNext        = pPlayerInfo;

    LeaveCriticalSection( &g_csPlayerList );

    if( ppPlayerInfo )
        *ppPlayerInfo = pPlayerInfo;

    return S_OK;

//-------------------------------------
// ERROR ONLY return path for clean up
//-------------------------------------
LErrorReturn:
    SAFE_DELETE( pPlayerInfo );
    SAFE_DELETE_ARRAY( pdpPlayerInfo );

    return hr;
}








//-----------------------------------------------------------------------------
// Name: DirectPlayLobbyMessageHandler
// Desc: Handler for DirectPlay lobby messages.  This function is called by
//       the DirectPlay lobby message handler pool of threads, so be careful of 
//       thread synchronization problems with shared memory
//-----------------------------------------------------------------------------
HRESULT WINAPI DirectPlayLobbyMessageHandler( PVOID pvUserContext, 
                                              DWORD dwMessageId, 
                                              PVOID pMsgBuffer )
{
    switch( dwMessageId )
    {
        case DPL_MSGID_CONNECT:
        {
            PDPL_MESSAGE_CONNECT pConnectMsg;
            pConnectMsg = (PDPL_MESSAGE_CONNECT)pMsgBuffer;

            // The CNetConnectWizard will handle this message for us,
            // so there is nothing we need to do here for this simple
            // sample.
            break;
        }

        case DPL_MSGID_DISCONNECT:
        {
            PDPL_MESSAGE_DISCONNECT pDisconnectMsg;
            pDisconnectMsg = (PDPL_MESSAGE_DISCONNECT)pMsgBuffer;

            // We should free any data associated with the lobby 
            // client here, but there is none.
            break;
        }

        case DPL_MSGID_RECEIVE:
        {
            PDPL_MESSAGE_RECEIVE pReceiveMsg;
            pReceiveMsg = (PDPL_MESSAGE_RECEIVE)pMsgBuffer;

            // The lobby client sent us data.  This sample doesn't
            // expected data from the client, but it is useful 
            // for more complex apps.
            break;
        }

        case DPL_MSGID_CONNECTION_SETTINGS:
        {
            PDPL_MESSAGE_CONNECTION_SETTINGS pConnectionStatusMsg;
            pConnectionStatusMsg = (PDPL_MESSAGE_CONNECTION_SETTINGS)pMsgBuffer;

            // The lobby client has changed the connection settings.  
            // This simple sample doesn't handle this, but more complex apps may
            // want to.
            break;
        }
    }

    // Make sure the DirectPlay MessageHandler calls the CNetConnectWizard handler, 
    // so the wizard can be informed of lobby messages such as DPL_MSGID_CONNECT
    if( g_pNetConnectWizard )
        return g_pNetConnectWizard->LobbyMessageHandler( pvUserContext, dwMessageId, 
                                                         pMsgBuffer );
    
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: FillMemoryRandom()
// Desc: Fill the provided 'pDest' memory block with random values for 'length' 
//       bytes. 
//-----------------------------------------------------------------------------
VOID FillMemoryRandom( PVOID pDest, SIZE_T length )
{
    BYTE* pByteBuffer = (BYTE*) pDest;

    // Fill the buffer with random values
    for( UINT b=0; b < length; b++ )
    {
        *pByteBuffer = (BYTE) (rand() % 256);
        pByteBuffer++;
    }
}




//-----------------------------------------------------------------------------
// Name: SendNetworkData()
// Desc: Sends a network packet of the specified size to the specified target.
//       This is called by the dialog UI thread.
//-----------------------------------------------------------------------------
HRESULT SendNetworkData()
{
    HRESULT hr;    

    if( !g_bSendingData || 
        IsDlgButtonChecked( g_hDlg, IDC_SEND_READY ) == BST_UNCHECKED )
    {
        KillTimer( g_hDlg, TIMERID_NETWORK );
        return S_OK;
    }

    if( g_pTargetPlayer == NULL )
        return E_FAIL;

    GAMEMSG_GENERIC* pGameMsg = NULL;
    DWORD            dwBufferSize;

    // Create a game packet based on the selected choices
    switch( g_dwSendSize )
    {
        case 512:
        {
            GAMEMSG_DATA_512* pMsg512 = new GAMEMSG_DATA_512;
            FillMemoryRandom( pMsg512->pBuffer, sizeof(pMsg512->pBuffer) );
            pGameMsg = pMsg512;
            dwBufferSize = sizeof(GAMEMSG_DATA_512);
            break;
        }
        
        case 256:
        {
            GAMEMSG_DATA_256* pMsg256 = new GAMEMSG_DATA_256;
            FillMemoryRandom( pMsg256->pBuffer, sizeof(pMsg256->pBuffer) );
            pGameMsg = pMsg256;
            dwBufferSize = sizeof(GAMEMSG_DATA_256);
            break;
        }

        case 128:
        {
            GAMEMSG_DATA_128* pMsg128 = new GAMEMSG_DATA_128;
            FillMemoryRandom( pMsg128->pBuffer, sizeof(pMsg128->pBuffer) );
            pGameMsg = pMsg128;
            dwBufferSize = sizeof(GAMEMSG_DATA_128);
            break;
        }

        case 64:
        {
            GAMEMSG_DATA_64* pMsg64 = new GAMEMSG_DATA_64;
            FillMemoryRandom( pMsg64->pBuffer, sizeof(pMsg64->pBuffer) );
            pGameMsg = pMsg64;
            dwBufferSize = sizeof(GAMEMSG_DATA_64);
            break;
        }

        case 32:
        {
            GAMEMSG_DATA_32* pMsg32 = new GAMEMSG_DATA_32;
            FillMemoryRandom( pMsg32->pBuffer, sizeof(pMsg32->pBuffer) );
            pGameMsg = pMsg32;
            dwBufferSize = sizeof(GAMEMSG_DATA_32);
            break;
        }

        default:
        case 16:
        {
            GAMEMSG_DATA_16* pMsg16 = new GAMEMSG_DATA_16;
            FillMemoryRandom( pMsg16->pBuffer, sizeof(pMsg16->pBuffer) );
            pGameMsg = pMsg16;
            dwBufferSize = sizeof(GAMEMSG_DATA_16);
            break;
        }
    }

    // Update the rest of the game msg 
    g_dwPacketId++;
    pGameMsg->dwPacketId   = g_dwPacketId;
    pGameMsg->dwType       = GAME_MSGID_GAMEPACKET;
            
    // Make a new GAMEMSG_DATA_NODE and hand it off to the
    // app worker thread.  It will process the node, and
    // then update the UI to show that a packet was sent
    GAMEMSG_DATA_NODE* pDataMsgNode = new GAMEMSG_DATA_NODE;
    if( NULL == pDataMsgNode )
        return E_OUTOFMEMORY;

    ZeroMemory( pDataMsgNode, sizeof(GAMEMSG_DATA_NODE) ); 

    pDataMsgNode->dwType            = DATA_TYPE_NETPACKET_SENT;
    pDataMsgNode->dwPacketId        = pGameMsg->dwPacketId;
    pDataMsgNode->dwReceiveDataSize = dwBufferSize;

    // Enter the data list CS, because we are about to modify shared memory, and 
    // the data may be changed concurrently from other DirectPlay
    // message handler threads, as well as the the app worker thread
    EnterCriticalSection( &g_csDataList );

    // Then add it to the circular linked list, g_DataHead
    // so it can be processed by a worker thread
    pDataMsgNode->pNext = &g_DataHead;
    pDataMsgNode->pPrev = g_DataHead.pPrev;
    g_DataHead.pPrev->pNext = pDataMsgNode;
    g_DataHead.pPrev        = pDataMsgNode;

    LeaveCriticalSection( &g_csDataList );

    // This var is only accessed by the dialog 
    // thread, so it is safe to access it directly
    g_dwDataSent += dwBufferSize;

    DPN_BUFFER_DESC bufferDesc;
    bufferDesc.dwBufferSize = dwBufferSize;
    bufferDesc.pBufferData  = (BYTE*) pGameMsg;

    DWORD dwFlags;
    dwFlags = DPNSEND_NOLOOPBACK | DPNSEND_NOCOPY;

    // Check for packet coalescence option, which attempts to
    // condense outgoing data into a single packet if several
    // small packets are waiting for transmission
    if( BST_CHECKED == IsDlgButtonChecked( g_hDlg, IDC_COALESCE ) )
        dwFlags |= DPNSEND_COALESCE;

    DPNHANDLE hAsync;
    hr = g_pDP->SendTo( g_pTargetPlayer->dpnidPlayer, &bufferDesc, 1,
                        g_dwSendTimeout, pGameMsg, &hAsync, dwFlags );

    // Ignore all errors except DPNERR_INVALIDPLAYER
    if( hr == DPNERR_INVALIDPLAYER )
    {
        // Stop sending if the target left game
        g_bSendingData = FALSE;
        PostMessage( g_hDlg, WM_APP_UPDATE_TARGETS, 0, 0 );
        return S_OK;
    }
    

    // Tell the app worker thread that there 
    // is new data availible. 
    SetEvent( g_hDPDataAvailEvent );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: ProcessNetDataProc()
// Desc: Worker thread that processes data found in g_DataHead, 
//       and updates the UI state based on the data 
//-----------------------------------------------------------------------------
#ifdef UNDER_CE
ULONG WINAPI ProcessNetDataProc( LPVOID lpParameter )
#else // !UNDER_CE
UINT WINAPI ProcessNetDataProc( LPVOID lpParameter )
#endif // UNDER_CE
{
    DWORD  dwResult;
    BOOL   bDone = FALSE;
    HANDLE ahHandles[2];

    ahHandles[0] = g_hDPDataAvailEvent;
    ahHandles[1] = g_hShutdownEvent;

    while( !bDone ) 
    { 
        dwResult = WaitForMultipleObjects( 2, ahHandles, FALSE, INFINITE );

        switch( dwResult )
        {
            case WAIT_OBJECT_0 + 0:
            {
                // g_hDPDataAvailEvent is signaled, so there is 
                // network data available to process
                if( FAILED( g_hrDialog = ProcessData() ) ) 
                {
                    DXTRACE_ERR_MSGBOX( TEXT("ProcessData"), g_hrDialog );
                    bDone = TRUE;
                }
                break;
            }

            case WAIT_OBJECT_0 + 1:
            {
                // g_hShutdownEvent is signaled, so shut down
                bDone = TRUE;
                break;
            }
        }
    }

    return 0;
}   




//-----------------------------------------------------------------------------
// Name: ProcessData()
// Desc: This is called by the app worker thread to process the data found in
//       g_DataHead.  The DirectPlay message handler threads add new nodes 
//       upon events such as packet packet recieve/send or timeout.
//-----------------------------------------------------------------------------
HRESULT ProcessData()
{
    TCHAR* strNewLogLine = NULL;

    // Enter the data list CS, because we are about to read shared memory, and 
    // the data may be changed concurrently from the DirectPlay
    // message handler threads 
    EnterCriticalSection( &g_csDataList );

    GAMEMSG_DATA_NODE* pDeleteNode; 
    GAMEMSG_DATA_NODE* pNode = g_DataHead.pNext;
    while( pNode != &g_DataHead )
    {
        // Allocate a string on the heap, and fill it
        // with text about what happened.
        strNewLogLine = new TCHAR[MAX_PATH];

        switch( pNode->dwType )
        {
            case DATA_TYPE_NETPACKET_RECIEVE:
                wsprintf( strNewLogLine, TEXT("Got %s's #%d (%d bytes)\r\n"), 
                        pNode->pPlayerFrom->strPlayerName,
                        pNode->dwPacketId,            
                        pNode->dwReceiveDataSize );           

                // The app can do more process of the network 
                // packet here here -- i.e. change the
                // state of the game.  This simple sample just
                // updates the UI.
                break;

            case DATA_TYPE_NETPACKET_SENT:
                wsprintf( strNewLogLine, TEXT("Sending #%d to %s (%d bytes)\r\n"), 
                                         pNode->dwPacketId,            
                                         g_pTargetPlayer->strPlayerName,
                                         pNode->dwReceiveDataSize );           
                break;

            case DATA_TYPE_NETPACKET_TIMEOUT:
                wsprintf( strNewLogLine, TEXT("Packet #%d timed out\r\n"), 
                         pNode->dwPacketId );           
                break;
        }

        // Post a message to the dialog thread, so it can
        // take the string on the heap, and appead it to the log.
        // If the log is appended from this thread, then if the dialog
        // thread tries to enter the g_csDataList CS, then it will be
        // locked until this thread release it, and the 
        // AppendTextToEditControl function relies on reponses from the 
        // dialog thread, so a race condition is possible unless
        // the dialog thread itself appeads the text.
        PostMessage( g_hDlg, WM_APP_APPEND_TEXT, (WPARAM) strNewLogLine, 0 );

        // The dialog thread will free the string
        strNewLogLine = NULL;

        if( pNode->hBufferHandle != NULL )
        {
            // Done with the buffer, so return it DirectPlay, 
            // so that the memory can be reused
            g_pDP->ReturnBuffer( pNode->hBufferHandle,0 );
        }

        // Unlink pPlayerInfo from the chain, g_DataHead.
        pNode->pPrev->pNext = pNode->pNext;
        pNode->pNext->pPrev = pNode->pPrev;

        pDeleteNode = pNode;
        pNode = pNode->pNext;

        assert( pDeleteNode != &g_DataHead );
        SAFE_DELETE( pDeleteNode );
    }

    LeaveCriticalSection( &g_csDataList );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: AppendTextToEditControl()
// Desc: Appends a string of text to the edit control
//-----------------------------------------------------------------------------
VOID AppendTextToEditControl( HWND hDlg, TCHAR* strNewLogLine )
{
    static TCHAR strText[1024*10];

    HWND hEdit = GetDlgItem( hDlg, IDC_LOG_EDIT );
    SendMessage( hEdit, WM_SETREDRAW, FALSE, 0 );
    GetWindowText( hEdit, strText, 1024*9 );

    _tcscat( strText, strNewLogLine );

    int nSecondLine = 0;
    if( SendMessage( hEdit, EM_GETLINECOUNT, 0, 0 ) > 8 )
        nSecondLine = (int)SendMessage( hEdit, EM_LINEINDEX, 1, 0 );

    SetWindowText( hEdit, &strText[nSecondLine] );

    SendMessage( hEdit, WM_SETREDRAW, TRUE, 0 );
    InvalidateRect( hEdit, NULL, TRUE );
    UpdateWindow( hEdit );
}




//-----------------------------------------------------------------------------
// Name: UpdateConnectionInfo()
// Desc: Using the info buffer passed in,  display some interesting tidbits about
//       the connection.
//-----------------------------------------------------------------------------
VOID UpdateConnectionInfo( HWND hDlg, PDPN_CONNECTION_INFO pConnectionInfo,
                           DWORD dwHighMessages, DWORD dwHighBytes, 
                           DWORD dwNormalMessages, DWORD dwNormalBytes,
                           DWORD dwLowMessages, DWORD dwLowBytes )
{
    TCHAR strText[1024];
    int   nDrops;
    int   nSends;

    HWND hEdit = GetDlgItem( hDlg, IDC_CONNINFO_EDIT );
    int nThumbIndex = GetScrollPos( hEdit, SB_VERT );

    if( pConnectionInfo )
    {
        nDrops = pConnectionInfo->dwPacketsDropped + pConnectionInfo->dwPacketsRetried;
        nDrops *= 10000;

        nSends = pConnectionInfo->dwPacketsSentGuaranteed + pConnectionInfo->dwPacketsSentNonGuaranteed;

        if(nSends)
            nDrops /= nSends; // percent nDrops * 100

        _stprintf(strText, 
#ifdef UNDER_CE
                           TEXT("Q HiPriM=%d\r\n")
                           TEXT("Q HiPriB=%d\r\n")
                           
                           TEXT("Q NormM=%d\r\n") 
                           TEXT("Q NormB=%d\r\n") 

                           TEXT("Q LoPriM=%d\r\n")
                           TEXT("Q LoPriB=%d\r\n")

                           TEXT("RTT=%dms\r\n") 
                           TEXT("BPS=%d\r\n")   
                           TEXT("Peak BPS=%d\r\n")
                                                  
                           TEXT("B Sent G=%d\r\n")
                           TEXT("F Sent G=%d\r\n")
                           TEXT("B Sent NG=%d\r\n")
                           TEXT("F Sent NG=%d\r\n")
                                                   
                           TEXT("B Retry G=%d\r\n")
                           TEXT("F Retry G=%d\r\n")
                           TEXT("B Drop NG=%d\r\n")
                           TEXT("F Drop NG=%d\r\n")
                                                   
                           TEXT("SentHiPri=%d\r\n")
                           TEXT("TimeoutHiPri=%d\r\n")  
                           TEXT("SentNorm=%d\r\n")
                           TEXT("TimeoutNorm=%d\r\n")  
                           TEXT("SentLoPri=%d\r\n")   
                           TEXT("TimeoutLoPri=%d\r\n")     
                                                                           
                           TEXT("B Rcvd G=%d\r\n")        
                           TEXT("F Rcvd G=%d\r\n")      
                           TEXT("B Rcvd NG=%d\r\n")    
                           TEXT("F Rcvd NG=%d\r\n")  
                           TEXT("Msgs Rcvd=%d\r\n")                
                                                                           
                           TEXT("Loss=%d.%02d%%\r\n"),
#else
                           TEXT("Send Queue Messages High Priority=%d\r\n")       \
                           TEXT("Send Queue Bytes High Priority=%d\r\n")     \
                           
                           TEXT("Send Queue Messages Normal Priority=%d\r\n")       \
                           TEXT("Send Queue Bytes Normal Priority=%d\r\n")     \

                           TEXT("Send Queue Messages Low Priority=%d\r\n")       \
                           TEXT("Send Queue Bytes Low Priority=%d\r\n")     \

                           TEXT("Round Trip Latency MS=%dms\r\n")                  \
                           TEXT("Throughput BPS=%d\r\n")                         \
                           TEXT("Peak Throughput BPS=%d\r\n")                     \
                                                                            \
                           TEXT("Bytes Sent Guaranteed=%d\r\n")                   \
                           TEXT("Packets Sent Guaranteed=%d\r\n")                 \
                           TEXT("Bytes Sent Non-Guaranteed=%d\r\n")                \
                           TEXT("Packets Sent Non-Guaranteed=%d\r\n")              \
                                                                            \
                           TEXT("Bytes Retried Guaranteed=%d\r\n")        \
                           TEXT("Packets Retried Guaranteed=%d\r\n")      \
                           TEXT("Bytes Dropped Non-Guaranteed=%d\r\n")    \
                           TEXT("Packets Dropped Non-Guaranteed=%d\r\n")  \
                                                                            \
                           TEXT("Messages Transmitted High Priority=%d\r\n")       \
                           TEXT("Messages Timed Out High Priority=%d\r\n")          \
                           TEXT("Messages Transmitted Normal Priority=%d\r\n")     \
                           TEXT("Messages Timed Out Normal Priority=%d\r\n")        \
                           TEXT("Messages Transmitted Low Priority=%d\r\n")        \
                           TEXT("Messages Timed Out Low Priority=%d\r\n")           \
                                                                            \
                           TEXT("Bytes Received Guaranteed=%d\r\n")               \
                           TEXT("Packets Received Guaranteed=%d\r\n")             \
                           TEXT("Bytes Received Non-Guaranteed=%d\r\n")            \
                           TEXT("Packets Received Non-Guaranteed=%d\r\n")          \
                           TEXT("Messages Received=%d\r\n")                      \
                                                                            \
                           TEXT("Loss Rate=%d.%02d%%\r\n"),
#endif // UNDER_CE
                           dwHighMessages, dwHighBytes,
                           dwNormalMessages, dwNormalBytes,
                           dwLowMessages, dwLowBytes,

                           pConnectionInfo->dwRoundTripLatencyMS, 
                           pConnectionInfo->dwThroughputBPS, 
                           pConnectionInfo->dwPeakThroughputBPS,

                           pConnectionInfo->dwBytesSentGuaranteed,
                           pConnectionInfo->dwPacketsSentGuaranteed,
                           pConnectionInfo->dwBytesSentNonGuaranteed,
                           pConnectionInfo->dwPacketsSentNonGuaranteed,

                           pConnectionInfo->dwBytesRetried,
                           pConnectionInfo->dwPacketsRetried,
                           pConnectionInfo->dwBytesDropped,
                           pConnectionInfo->dwPacketsDropped,

                           pConnectionInfo->dwMessagesTransmittedHighPriority,
                           pConnectionInfo->dwMessagesTimedOutHighPriority,
                           pConnectionInfo->dwMessagesTransmittedNormalPriority,
                           pConnectionInfo->dwMessagesTimedOutNormalPriority,
                           pConnectionInfo->dwMessagesTransmittedLowPriority,
                           pConnectionInfo->dwMessagesTimedOutLowPriority,

                           pConnectionInfo->dwBytesReceivedGuaranteed,
                           pConnectionInfo->dwPacketsReceivedGuaranteed,
                           pConnectionInfo->dwBytesReceivedNonGuaranteed,
                           pConnectionInfo->dwPacketsReceivedNonGuaranteed,
                           pConnectionInfo->dwMessagesReceived,

                           (nDrops/100), (nDrops % 100) );
    }
    else 
    {
        strText[0] = 0;
    }

    SetWindowText( hEdit, strText );  
    SendMessage( hEdit, EM_LINESCROLL, 0, nThumbIndex );
}




//-----------------------------------------------------------------------------
// Name: UpdateStats()
// Desc: Refresh the displayed data 
//-----------------------------------------------------------------------------
VOID UpdateStats( HWND hDlg )
{
    HRESULT hr;
    static DWORD s_dwLastTime = GetTickCount();
    DWORD dwCurTime = GetTickCount();

    if( (dwCurTime - s_dwLastTime) < 200 )
        return;

    TCHAR strDataIn[32];
    TCHAR strDataOut[32];

    FLOAT fSecondsPassed = (dwCurTime - s_dwLastTime) / 1000.0f;
    FLOAT fDataIn  = g_dwDataRecieved / fSecondsPassed;
    FLOAT fDataOut = g_dwDataSent / fSecondsPassed;

    s_dwLastTime     = dwCurTime;
    g_dwDataRecieved = 0;
    g_dwDataSent     = 0;

    _stprintf( strDataIn,  TEXT("%0.1f BPS"), fDataIn );
    _stprintf( strDataOut, TEXT("%0.1f BPS"), fDataOut );
    SetDlgItemText( hDlg, IDC_ACTUAL_SEND_RATE, strDataOut );
    SetDlgItemText( hDlg, IDC_ACTUAL_RECIEVE_RATE, strDataIn );

    //  If a player has been selected for Connection Info Display we will handle it next
    if( g_pConnInfoTargetPlayer->dpnidPlayer != 0 )
    {
        // Call GetConnectionInfo and display results
        DPN_CONNECTION_INFO dpnConnectionInfo;
        ZeroMemory( &dpnConnectionInfo, sizeof(DPN_CONNECTION_INFO) );
        dpnConnectionInfo.dwSize = sizeof(DPN_CONNECTION_INFO);
        hr = g_pDP->GetConnectionInfo( g_pConnInfoTargetPlayer->dpnidPlayer, 
                                                   &dpnConnectionInfo,
                                                   0);

        if( SUCCEEDED(hr) )
        {
            DWORD dwHighMessages, dwHighBytes;
            DWORD dwNormalMessages, dwNormalBytes;
            DWORD dwLowMessages, dwLowBytes;

            hr = g_pDP->GetSendQueueInfo( g_pConnInfoTargetPlayer->dpnidPlayer, 
                                          &dwHighMessages, &dwHighBytes, 
                                          DPNGETSENDQUEUEINFO_PRIORITY_HIGH );
            if( FAILED(hr) )
                DXTRACE_ERR_MSGBOX( TEXT("GetSendQueueInfo"), hr );

            hr = g_pDP->GetSendQueueInfo( g_pConnInfoTargetPlayer->dpnidPlayer, 
                                     &dwNormalMessages, &dwNormalBytes, 
                                     DPNGETSENDQUEUEINFO_PRIORITY_NORMAL );
            if( FAILED(hr) )
                DXTRACE_ERR_MSGBOX( TEXT("GetSendQueueInfo"), hr );

            hr = g_pDP->GetSendQueueInfo( g_pConnInfoTargetPlayer->dpnidPlayer, 
                                     &dwLowMessages, &dwLowBytes, 
                                     DPNGETSENDQUEUEINFO_PRIORITY_LOW );
            if( FAILED(hr) )
                DXTRACE_ERR_MSGBOX( TEXT("GetSendQueueInfo"), hr );

            UpdateConnectionInfo( hDlg, &dpnConnectionInfo,
                                  dwHighMessages, dwHighBytes,
                                  dwNormalMessages, dwNormalBytes,
                                  dwLowMessages, dwLowBytes );
        }
        else
        {
            // If the player goes away, the set the target to none
            SendDlgItemMessage( hDlg, IDC_CONNINFO_COMBO, CB_SETCURSEL, 0, 0 );  
            ReadCombos( hDlg );
        }
    }
    else 
    {
        // Clear the conn info window if no connection is selected to display        
        UpdateConnectionInfo( hDlg, NULL, 0, 0, 0, 0, 0, 0 );
    }
}




//-----------------------------------------------------------------------------
// Name: RefreshStatusBarText()
// Desc: Refresh the current status bar text based on connection state
//-----------------------------------------------------------------------------
HRESULT RefreshStatusBarText( DPNID idLocal, DPNID idHost )
{
    HRESULT hr = S_OK; 
    TCHAR strStatus[ 1024 ] = {0};
    TCHAR strAddress[ 256 ] = {0};
    DWORD dwNumAddresses = 0; // Count of address objects
    LPDIRECTPLAY8ADDRESS *rpAddresses = NULL; // Range of addresses    
    DWORD i = 0; // Loop variable

    // Margin the status text
    _tcsncpy( strStatus, TEXT("   "), 1023 );
    strStatus[ 1023 ] = 0;

    if( idLocal == NULL || idHost == NULL )
    {
        // If the host or local dpnid is not set, this should mean the player
        // creation messages have not yet arrived
        _tcsncat( strStatus, TEXT("Not connected"), 1023 - lstrlen( strStatus ) );
        strStatus[ 1023 ] = 0;
        dwNumAddresses = 0;
    }
    else if( idLocal == idHost )
    {
        // Else if the host and local dpnid's are equal, the local player is
        // the session host
        _tcsncat( strStatus, TEXT("Hosting"), 1023 - lstrlen( strStatus ) );
        strStatus[ 1023 ] = 0;
        dwNumAddresses = 0;

        // Determine the buffer size needed to hold the addresses
        hr = g_pDP->GetLocalHostAddresses( rpAddresses, &dwNumAddresses, 0 );
        if( DPNERR_BUFFERTOOSMALL != hr )
            goto LCleanReturn;  

        // Allocate the array
        rpAddresses = new LPDIRECTPLAY8ADDRESS[ dwNumAddresses ];
        if( NULL == rpAddresses )
        {
            hr = E_OUTOFMEMORY;
            goto LCleanReturn;
        }

        ZeroMemory( rpAddresses, dwNumAddresses * sizeof(LPDIRECTPLAY8ADDRESS) );

        // Retrieve the addresses
        hr = g_pDP->GetLocalHostAddresses( rpAddresses, &dwNumAddresses, 0 );
        if( FAILED(hr) )
            goto LCleanReturn;  
    }
    else
    {
        // Else the local player is connected to a remote session
        _tcsncat( strStatus, TEXT("Connected"), 1023 - lstrlen( strStatus ) );
        strStatus[ 1023 ] = 0;
        dwNumAddresses = 1;

        // Allocate the array
        rpAddresses = new LPDIRECTPLAY8ADDRESS[ dwNumAddresses ];
        if( NULL == rpAddresses )
        {
            hr = E_OUTOFMEMORY;
            goto LCleanReturn;
        }

        ZeroMemory( rpAddresses, dwNumAddresses * sizeof(LPDIRECTPLAY8ADDRESS) );

        // Retrieve the address
        hr = g_pDP->GetPeerAddress( idHost, &rpAddresses[0], 0 );
        if( FAILED(hr) )
            goto LCleanReturn;
    }

    // If we have addresses to report, determine a reasonable way to output them
    if( dwNumAddresses > 0 )
    {
        // Get the provider from the DirectPlay address
        GUID guidSP;
        hr = rpAddresses[0]->GetSP( &guidSP );
        if( FAILED(hr) )
            goto LCleanReturn;

        TCHAR strProvider[ 256 ] = {0};
  
        if( CLSID_DP8SP_IPX == guidSP )
            _tcsncpy( strProvider, TEXT(" via IPX"), 256 );
        else if( CLSID_DP8SP_TCPIP == guidSP )
            _tcsncpy( strProvider, TEXT(" via TCP/IP"), 256 );
        else if( CLSID_DP8SP_MODEM == guidSP )
            _tcsncpy( strProvider, TEXT(" via Modem"), 256 );
        else if( CLSID_DP8SP_SERIAL == guidSP )
            _tcsncpy( strProvider, TEXT(" via Serial"), 256 );
        else if( CLSID_DP8SP_BLUETOOTH == guidSP )
            _tcsncpy( strProvider, TEXT(" via Bluetooth"), 256 );
        
        _tcsncat( strStatus, strProvider, 1023 - lstrlen( strStatus ) );
        strStatus[ 1023 ] = 0;

        // If we are using TCP/IP or IPX, display the address list
        if( CLSID_DP8SP_IPX == guidSP ||
            CLSID_DP8SP_TCPIP == guidSP )
        {
            // Append the addresses to the status text
            for( i=0; i < dwNumAddresses; i++ )
            {
                // Get the IP address
                if( FAILED( GetHostnamePortString( strAddress, rpAddresses[ i ], 256 ) ) )
                    break;

                // Add formatting characters
                TCHAR* strSpacing = ( i != 0 ) ? TEXT(", ") : 
                                    ( idLocal == idHost ) ? TEXT(" at ") : TEXT(" to ");                      
        
                _tcsncat( strStatus, strSpacing, 1023 - lstrlen( strStatus ) );
                strStatus[ 1023 ] = 0;


                // Add the IP address string
                _tcsncat( strStatus, strAddress, 1023 - lstrlen( strStatus ) );
                strStatus[ 1023 ] = 0;
     
            }
        }
    }

    // Margin the status text
    _tcsncat( strStatus, TEXT("   "), 1023 - lstrlen( strStatus ) );
    strStatus[ 1023 ] = 0;

    // Set the text
    SendDlgItemMessage( g_hDlg, IDC_STATUS_BAR_TEXT, WM_SETTEXT, 0, (LPARAM) strStatus );
    hr = S_OK;
  
LCleanReturn:
    for( i=0; i < dwNumAddresses; i++ )
        SAFE_RELEASE( rpAddresses[i] );

    SAFE_DELETE_ARRAY( rpAddresses );

    return hr;
}




//-----------------------------------------------------------------------------
// Name: GetHostnamePortString
// Desc: Stores the hostname and port number of the given DirectPlay 
//       address in the provided string.
//-----------------------------------------------------------------------------
HRESULT GetHostnamePortString( TCHAR* str, LPDIRECTPLAY8ADDRESS pAddress, DWORD dwBufferLen )
{
    HRESULT hr = S_OK;

    // Sanity check
    if( NULL == str || NULL == pAddress )
        return E_FAIL;

    // Get the hostname string from the DirectPlay address
    WCHAR wstrHostname[ 256 ] = {0};
    DWORD dwSize = sizeof(wstrHostname);
    DWORD dwDataType = 0;
    hr = pAddress->GetComponentByName( L"hostname", wstrHostname, &dwSize, &dwDataType );
    if( FAILED(hr) )
        return hr;

    // Convert from wide character to generic
    TCHAR strHostname[ 256 ] = {0};
    DXUtil_ConvertWideStringToGenericCch( strHostname, wstrHostname, 256 );

    // Get the port value from the DirectPlay address
    DWORD dwPort = 0;
    dwSize = sizeof(DWORD);
    hr = pAddress->GetComponentByName( L"port", &dwPort, &dwSize, &dwDataType );
    if( FAILED(hr) )
        return hr;

    // Copy the address string and null terminate the result
    _sntprintf( str, dwBufferLen, TEXT("%s:%d"), strHostname, dwPort );
    str[ dwBufferLen-1 ] = TEXT('\0');

    return S_OK;
}