//-----------------------------------------------------------------------------
// File: MultiDI.cpp
//
// Desc: DirectInput framework class using semantic mapping with multiplayer
//       device ownership.  Feel free to use this class as a starting point 
//       for adding extra functionality.
//
// Copyright (C) 1995-2001 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------
#define STRICT
#define DIRECTINPUT_VERSION 0x0800
#include <basetsd.h>
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <shlwapi.h>    // defines SHDeleteKey
#include <dxerr8.h>
#include <d3d8types.h>  // included to get the D3DCOLOR_RGBA macro.
#include "MultiDI.h"
#include "DXUtil.h"
#include <assert.h>




//-----------------------------------------------------------------------------
// Name: CMultiplayerInputDeviceManager
// Desc: Constructor
// Args: strRegKey - A location in the registry where device ownership 
//       information should be stored
//-----------------------------------------------------------------------------
CMultiplayerInputDeviceManager::CMultiplayerInputDeviceManager( TCHAR* strRegKey )
{
    HRESULT hr = CoInitialize(NULL);
    m_bCleanupCOM = SUCCEEDED(hr);
    LONG nResult;

    // Initialize members
    m_pDI                       = NULL;
    m_hWnd                      = NULL;
    m_pdiaf                     = NULL;
    m_pUsers                    = NULL;
    m_pDeviceList               = NULL;
    m_AddDeviceCallback         = NULL;
    m_AddDeviceCallbackParam    = NULL;
    m_hKey                      = NULL;
    m_dwNumDevices              = 0;
    m_dwMaxDevices              = 0; 
    

    // Duplicate the registry location string since we'll need this again later
    m_strKey = _tcsdup( strRegKey );
    if( m_strKey == NULL )
        return;

    // Create a reg key to store device ownership data 
    nResult = RegCreateKeyEx( HKEY_CURRENT_USER, strRegKey, 0, NULL,
                              REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, 
                              &m_hKey, NULL );
    if(nResult != ERROR_SUCCESS)
        m_hKey = NULL;
}




//-----------------------------------------------------------------------------
// Name: ~CMultiplayerInputDeviceManager
// Desc: Destructor
//-----------------------------------------------------------------------------
CMultiplayerInputDeviceManager::~CMultiplayerInputDeviceManager()
{
    Cleanup();
    
    if( m_bCleanupCOM )
        CoUninitialize();

    RegCloseKey( m_hKey );

    free( m_strKey );
}




//-----------------------------------------------------------------------------
// Name: Create
// Desc: Initializes the class, and enums the devices.  See MultiMapper sample
//       for how to use this class.
//       It might fail if there are too many players for the 
//       number of devices availible, or if one player owns too many
//       devices preventing others from having a device.  Its up the app
//       to prevent this or respond to this.
//       Note: strUserName should be a array of sz strings 
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::Create( HWND hWnd, 
                                     TCHAR* strUserNames[], 
                                     DWORD dwNumUsers,
                                     DIACTIONFORMAT* pdiaf,
                                     LPDIMANAGERCALLBACK AddDeviceCallback, 
                                     LPVOID pCallbackParam,
                                     BOOL bResetOwnership, 
                                     BOOL bResetMappings )
{
    HRESULT hr;
    
    if( strUserNames == NULL || dwNumUsers == 0 )
        return E_INVALIDARG;

    Cleanup();
    
    // Store data
    m_hWnd = hWnd;

    // Create and init the m_pUsers array 
    m_dwNumUsers = dwNumUsers;
    m_pUsers     = new PlayerInfo*[dwNumUsers];
    for( DWORD i=0; i<dwNumUsers; i++ )
    {
        m_pUsers[i] = new PlayerInfo;
        ZeroMemory( m_pUsers[i], sizeof(PlayerInfo) );        
        m_pUsers[i]->dwPlayerIndex = i; // set the 0-based player index (for easy referencing)
        lstrcpyn( m_pUsers[i]->strPlayerName, strUserNames[i], MAX_PATH-1 );            
    }
    
    m_AddDeviceCallback = AddDeviceCallback;
    m_AddDeviceCallbackParam = pCallbackParam;

    // Create the base DirectInput object
    if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
                                         IID_IDirectInput8, (VOID**)&m_pDI, NULL ) ) )
        return DXTRACE_ERR_NOMSGBOX( TEXT("DirectInput8Create"), hr );
    
    if( FAILED( hr = SetActionFormat( pdiaf, TRUE, bResetOwnership, bResetMappings ) ) )
        return DXTRACE_ERR_NOMSGBOX( TEXT("SetActionFormat"), hr );
    
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: SetActionFormat
// Desc: Sets a new action format.  
//       It re-enumerates the devices if bReenumerate
//       It resets the ownership of the devices if bResetOwnership
//       It resets the mapper actions of the devices if bResetMappings
//       This function may fail if there are too many players for the 
//       number of devices availible, or if one player owns too many
//       devices preventing others from having a device.  Its up the app
//       to prevent this or respond to this.
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::SetActionFormat( DIACTIONFORMAT* pdiaf, 
                                              BOOL bReenumerate, 
                                              BOOL bResetOwnership,
                                              BOOL bResetMappings )
{
    HRESULT hr;
    DWORD iPlayer;
    DWORD iDevice;

    // Store the new action format
    m_pdiaf = pdiaf;
    
    // Only destroy and re-enumerate devices if the caller explicitly wants to.
    // This isn't thread safe, so be sure not to have any other threads using
    // this data unless you redesign this class
    if( bReenumerate )
    {
        // Set all players to not have a device yet
        for( iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
            m_pUsers[iPlayer]->bFoundDeviceForPlayer = FALSE;

        if( bResetOwnership )
        {
            // Set all devices as not assigned to a player
            for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
                m_pDeviceList[iDevice].pPlayerInfo = NULL;

            // Delete the device ownership keys
            DeleteDeviceOwnershipKeys();
        }

        // Devices must be unacquired to have a new action map set.
        UnacquireDevices();
        
        // Enumerate all available devices that map the pri 1 actions,
        // and store them in the m_pDeviceList array
        if( FAILED( hr = BuildDeviceList() ) )
            return DXTRACE_ERR_NOMSGBOX( TEXT("BuildDeviceList"), hr );
        
        // Assign devices to any players that don't have devices
        // Some games may want to change this functionality.
        if( FAILED( hr = AssignDevices() ) )
            return DXTRACE_ERR_NOMSGBOX( TEXT("AssignDevices"), hr );

        // Report an error if there are too many players for the 
        // number of devices availible, or if one player owns too many
        // devices preventing others from having a device
        if( FAILED( hr = VerifyAssignment() ) )
            return hr;

        // Now that every player has at least one device, build and set 
        // the action map, and use callback into the app to tell the 
        // app of the device assignment.
        if( FAILED( hr = AddAssignedDevices( bResetMappings ) ) )
            return DXTRACE_ERR_NOMSGBOX( TEXT("AddAssignedDevices"), hr );

        // For every device that's assigned to a player, save its device key 
        // and assigned player to registry, so the app remembers which devices
        // each player prefers
        if( FAILED( hr = SaveDeviceOwnershipKeys() ) )
            return DXTRACE_ERR_NOMSGBOX( TEXT("SaveDeviceOwnershipKeys"), hr );
    }
    else
    {
        // Just apply the new maps for each device owned by each user
        
        // Devices must be unacquired to have a new action map set.
        UnacquireDevices();
        
        // Apply the new action map to the current devices.
        for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
        {
            LPDIRECTINPUTDEVICE8 pdidDevice = m_pDeviceList[iDevice].pdidDevice;
            PlayerInfo* pPlayerInfo = m_pDeviceList[iDevice].pPlayerInfo;

            if( FAILED( hr = pdidDevice->BuildActionMap( m_pdiaf, pPlayerInfo->strPlayerName, DIDBAM_DEFAULT ) ) )
                return DXTRACE_ERR_NOMSGBOX( TEXT("BuildActionMap"), hr );
            if( FAILED( hr = pdidDevice->SetActionMap( m_pdiaf, pPlayerInfo->strPlayerName, DIDSAM_DEFAULT ) ) )
                return DXTRACE_ERR_NOMSGBOX( TEXT("SetActionMap"), hr );
        }
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: BuildDeviceList
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::BuildDeviceList()
{
    // Cleanup any previously enumerated devices
    CleanupDeviceList();
    
    // Build a simple list of all devices currently attached to the machine. This
    // array will be used to reassign devices to each user.
    m_dwMaxDevices = 5;
    m_dwNumDevices = 0;
    m_pDeviceList = NULL;
    m_pDeviceList = (DeviceInfo*) realloc( m_pDeviceList, m_dwMaxDevices*sizeof(DeviceInfo) );
    ZeroMemory( m_pDeviceList, m_dwMaxDevices*sizeof(DeviceInfo) );        

    // Enumerate available devices for any user.
    m_pDI->EnumDevicesBySemantics( NULL, m_pdiaf, StaticEnumSuitableDevicesCB, 
                                   this, DIEDBSFL_ATTACHEDONLY );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: EnumSuitableDevicesCB
// Desc: DirectInput device enumeratation callback.  Calls AddDevice()
//       on each device enumerated.
//-----------------------------------------------------------------------------
BOOL CALLBACK CMultiplayerInputDeviceManager::StaticEnumSuitableDevicesCB( LPCDIDEVICEINSTANCE pdidi, 
                                                               LPDIRECTINPUTDEVICE8 pdidDevice, 
                                                               DWORD dwFlags, DWORD dwDeviceRemaining,
                                                               VOID* pContext )
{
    // Add the device to the device manager's internal list
    CMultiplayerInputDeviceManager* pInputDeviceManager = (CMultiplayerInputDeviceManager*)pContext;
    return pInputDeviceManager->EnumDevice( pdidi, pdidDevice, 
                                            dwFlags, dwDeviceRemaining );    
}




//-----------------------------------------------------------------------------
// Name: EnumDevice
// Desc: Enums each device to see if its suitable to add 
//-----------------------------------------------------------------------------
BOOL CMultiplayerInputDeviceManager::EnumDevice( const DIDEVICEINSTANCE* pdidi, 
                                     const LPDIRECTINPUTDEVICE8 pdidDevice,
                                     DWORD dwFlags, DWORD dwRemainingDevices )
{    
    TCHAR strPlayerName[MAX_PATH];
    TCHAR strDeviceGuid[40];
    
    // Devices of type DI8DEVTYPE_DEVICECTRL are specialized devices not generally
    // considered appropriate to control game actions. We just ignore these.
    if( GET_DIDEVICE_TYPE(pdidi->dwDevType) != DI8DEVTYPE_DEVICECTRL )
    {
        // We're only interested in devices that map the pri 1 actions
        if( dwFlags & DIEDBS_MAPPEDPRI1 )
        {
            // Add new pdidDevice struct to array, and resize array if needed
            m_dwNumDevices++;
            if( m_dwNumDevices > m_dwMaxDevices )
            {
                m_dwMaxDevices += 5;
                m_pDeviceList = (DeviceInfo*) realloc( m_pDeviceList, m_dwMaxDevices*sizeof(DeviceInfo) );
                ZeroMemory( m_pDeviceList + m_dwMaxDevices - 5, 5*sizeof(DeviceInfo) );
            }
            
            DXUtil_ConvertGUIDToString( &pdidi->guidInstance, strDeviceGuid );
            DXUtil_ReadStringRegKey( m_hKey, strDeviceGuid, strPlayerName, MAX_PATH, TEXT("") );        
            
            // Add the device to the array m_pDeviceList
            DWORD dwCurrentDevice = m_dwNumDevices-1;
            ZeroMemory( &m_pDeviceList[dwCurrentDevice], sizeof(DeviceInfo) );

            m_pDeviceList[dwCurrentDevice].didi = *pdidi;            
            m_pDeviceList[dwCurrentDevice].pdidDevice = pdidDevice;        
            m_pDeviceList[dwCurrentDevice].pdidDevice->AddRef();
            m_pDeviceList[dwCurrentDevice].bMapsPri1Actions = ((dwFlags & DIEDBS_MAPPEDPRI1) == DIEDBS_MAPPEDPRI1);
            m_pDeviceList[dwCurrentDevice].bMapsPri2Actions = ((dwFlags & DIEDBS_MAPPEDPRI2) == DIEDBS_MAPPEDPRI2);
            
            if( lstrcmp( strPlayerName, TEXT("") ) != 0 )
            {
                m_pDeviceList[dwCurrentDevice].pPlayerInfo = LookupPlayer( strPlayerName );
                if( m_pDeviceList[dwCurrentDevice].pPlayerInfo )
                    m_pDeviceList[dwCurrentDevice].pPlayerInfo->bFoundDeviceForPlayer = TRUE;
            }
        }
    }
    
    // Continue enumerating 
    return DIENUM_CONTINUE;
}




//-----------------------------------------------------------------------------
// Name: AssignDevices
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::AssignDevices()
{    
    DWORD iDevice;
    DWORD iPlayer;

    // For any device that doesn't have a user assigned to it,
    // then assign it to the first user that needs a device
    for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
    {
        if( m_pDeviceList[iDevice].pPlayerInfo == NULL )
        {
            for( iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
            {
                if( !m_pUsers[iPlayer]->bFoundDeviceForPlayer )
                {
                    m_pDeviceList[iDevice].pPlayerInfo = m_pUsers[iPlayer];
                    m_pUsers[iPlayer]->bFoundDeviceForPlayer = TRUE;
                    break;
                }
            }                        
        }
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: VerifyAssignment
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::VerifyAssignment()
{    
    DWORD iPlayer;
    DWORD iDevice;

    // For each player, make sure that a device was found for this 
    // player, otherwise return failure.
    for( iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
    {
        if( !m_pUsers[iPlayer]->bFoundDeviceForPlayer )
        {
            if( GetNumDevices() < m_dwNumUsers )
                return E_DIUTILERR_TOOMANYUSERS;
            else
            {
                // Check to see if there's a device that isn't already
                // assigned to a player. If there is return 
                // E_DIUTILERR_PLAYERWITHOUTDEVICE otherwise return 
                // E_DIUTILERR_DEVICESTAKEN
                for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
                {
                    if( m_pDeviceList[iDevice].pPlayerInfo == NULL )
                        return E_DIUTILERR_PLAYERWITHOUTDEVICE;
                }
                
                return E_DIUTILERR_DEVICESTAKEN;
            }
        }                       
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: AddAssignedDevices
// Desc: For every device that's assigned to a player, set it action map
//       and add it to the game
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::AddAssignedDevices( BOOL bResetMappings )
{    
    DWORD iDevice;
    DWORD iAction;
    HRESULT hr = S_OK;
    DWORD *pdwAppFixed = NULL;

    // If flagged, we'll be remapping the actions to hardware defaults, and in
    // the process DirectInput will clear the DIA_APPFIXED flag, so we need 
    // to make a copy in order to reapply the flag later.
    if( bResetMappings )
    {
        pdwAppFixed = new DWORD[m_pdiaf->dwNumActions];
        
        // Verify memory allocation and collect DIA_APPFIXED settings
        if( pdwAppFixed )
        {
            for( iAction=0; iAction < m_pdiaf->dwNumActions; iAction++ )
                pdwAppFixed[iAction] = m_pdiaf->rgoAction[iAction].dwFlags & DIA_APPFIXED;
        }
    }

    // For every device that's assigned to a player, 
    // set it action map, and add it to the game
    for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
    {                
        LPDIRECTINPUTDEVICE8 pdidDevice = m_pDeviceList[iDevice].pdidDevice;
        PlayerInfo*          pPlayerInfo = m_pDeviceList[iDevice].pPlayerInfo;           
        
        if( pPlayerInfo != NULL )
        {   
            // Set the device's coop level
            hr = pdidDevice->SetCooperativeLevel( m_hWnd, DISCL_NONEXCLUSIVE|DISCL_FOREGROUND );
            if( FAILED(hr) )
                break;
            
            // Build and set the action map on this device.  This will also remove 
            // it from DirectInput's internal list of available devices.
            DWORD dwBuildFlags = bResetMappings ? DIDBAM_HWDEFAULTS : DIDBAM_DEFAULT;   
          	DWORD dwSetFlags = bResetMappings ? DIDSAM_FORCESAVE : DIDSAM_DEFAULT;

            hr = pdidDevice->BuildActionMap( m_pdiaf, pPlayerInfo->strPlayerName, dwBuildFlags );
            if( FAILED( hr ) )
            {
                // just print out a debug message and keep going
                DXTRACE_ERR_NOMSGBOX( TEXT("BuildActionMap"), hr );
                hr = S_OK;
                continue;
            }

            hr = pdidDevice->SetActionMap( m_pdiaf, pPlayerInfo->strPlayerName, dwSetFlags );
            if( FAILED( hr ) )
            {
                // just print out a debug message and keep going
                DXTRACE_ERR_NOMSGBOX( TEXT("SetActionMap"), hr );   
                hr = S_OK;
                continue;
            }
            

            // Callback into the app so it can adjust the device and set
            // the m_pDeviceList[iDevice].pParam field with a device state struct
            if( m_AddDeviceCallback )
                m_AddDeviceCallback( pPlayerInfo, &m_pDeviceList[iDevice], 
                                     &m_pDeviceList[iDevice].didi, m_AddDeviceCallbackParam );            
            
            // Check to see if the device is using relative axis -- sometimes app code
            // might want to know this.
            DIPROPDWORD dipdw;
            dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
            dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
            dipdw.diph.dwObj        = 0;
            dipdw.diph.dwHow        = DIPH_DEVICE;
            dipdw.dwData            = 0;
            pdidDevice->GetProperty( DIPROP_AXISMODE, &dipdw.diph );
            if( dipdw.dwData == DIPROPAXISMODE_REL )
                m_pDeviceList[iDevice].bRelativeAxis = TRUE;   
            
            // We made it through this iteration without breaking out do to errors
            hr = S_OK;
        }
        else
        {
            if( FAILED( hr = pdidDevice->BuildActionMap( m_pdiaf, NULL, DIDBAM_DEFAULT ) ) )
            {
                DXTRACE_ERR_NOMSGBOX( TEXT("BuildActionMap"), hr );
                break;
            }
            
            if( FAILED( hr = pdidDevice->SetActionMap( m_pdiaf, NULL, DIDSAM_NOUSER ) ) )
            {
                DXTRACE_ERR_NOMSGBOX( TEXT("SetActionMap"), hr );   
                break;
            }
        }
    }                              

    // If we stored DIA_APPFIXED flags earlier, we need to reapply those flags and
    // free the allocated memory
    if( bResetMappings && pdwAppFixed )
    {
        for( iAction=0; iAction < m_pdiaf->dwNumActions; iAction++ )
            m_pdiaf->rgoAction[iAction].dwFlags |= pdwAppFixed[iAction];
        
        delete [] pdwAppFixed;
    }

    return hr;
}




//-----------------------------------------------------------------------------
// Name: ConfigureDevices
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::ConfigureDevices( HWND hWnd, IUnknown* pSurface,
                                               VOID* ConfigureDevicesCB,
                                               DWORD dwFlags, LPVOID pvCBParam )
{
    HRESULT hr;
    DWORD iPlayer;
    
    // Determine how large of a string we'll need to hold all user names
    DWORD dwNamesSize = 0;
    for( iPlayer=0; iPlayer < m_dwNumUsers; iPlayer++ )
        dwNamesSize += lstrlen( m_pUsers[iPlayer]->strPlayerName ) +1;

    // Build multi-sz list of user names
    TCHAR* strUserNames = new TCHAR[dwNamesSize+1];
    
    // Verify allocation and cycle through user names
    if( strUserNames ) 
    {
        TCHAR* strTemp = strUserNames;
        for( iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
        {
            lstrcpy( strTemp, m_pUsers[iPlayer]->strPlayerName );
            strTemp += lstrlen(strTemp) + 1;
        }

        lstrcpy( strTemp, TEXT("\0") );
    }

    
    // Fill in all the params
    DICONFIGUREDEVICESPARAMS dicdp;
    ZeroMemory(&dicdp, sizeof(dicdp));
    dicdp.dwSize = sizeof(dicdp);
    dicdp.dwcFormats     = 1;
    dicdp.lprgFormats    = m_pdiaf;
    dicdp.hwnd           = hWnd;
    dicdp.lpUnkDDSTarget = pSurface;
    dicdp.dwcUsers       = m_dwNumUsers;
    dicdp.lptszUserNames = strUserNames;

    // Initialize all the colors here
    DICOLORSET dics;
    ZeroMemory(&dics, sizeof(DICOLORSET));
    dics.dwSize = sizeof(DICOLORSET);

    // Set UI color scheme (if not specified it uses defaults)
    dicdp.dics.dwSize = sizeof(dics);
    dicdp.dics.cTextFore        = D3DCOLOR_RGBA(255,255,255,255);
    dicdp.dics.cTextHighlight   = D3DCOLOR_RGBA(60,191,241,255);
    dicdp.dics.cCalloutLine     = D3DCOLOR_RGBA(255,255,255,128);
    dicdp.dics.cCalloutHighlight= D3DCOLOR_RGBA(60,191,241,255);
    dicdp.dics.cBorder          = D3DCOLOR_RGBA(140,152,140,128);
    dicdp.dics.cControlFill     = D3DCOLOR_RGBA(113,0,0,128);
    dicdp.dics.cHighlightFill   = D3DCOLOR_RGBA(0,0,0,128);
    dicdp.dics.cAreaFill        = D3DCOLOR_RGBA(0,0,0,128);
        
    if( dwFlags & DICD_EDIT )
    {
        // Re-enum so we can catch any new devices that have been recently attached
        for( iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
            m_pUsers[iPlayer]->bFoundDeviceForPlayer = FALSE;
        if( FAILED( hr = BuildDeviceList() ) )
        {
            DXTRACE_ERR_NOMSGBOX( TEXT("BuildDeviceList"), hr );
            goto LCleanup;
        }
    }
        
    // Unacquire the devices so that mouse doesn't 
    // control the game while in control panel
    UnacquireDevices();

    if( FAILED( hr = m_pDI->ConfigureDevices( (LPDICONFIGUREDEVICESCALLBACK)ConfigureDevicesCB, 
                                  &dicdp, dwFlags, pvCBParam ) ) )
    {
        DXTRACE_ERR_NOMSGBOX( TEXT("ConfigureDevices"), hr );   
        goto LCleanup;
    }

    if( dwFlags & DICD_EDIT )
    {
        // Update the device ownership 
        if( FAILED( hr = UpdateDeviceOwnership() ) )
        {
            DXTRACE_ERR_NOMSGBOX( TEXT("UpdateDeviceOwnership"), hr );    
            goto LCleanup;
        }

        // Now save the device keys that are assigned players to registry, 
        if( FAILED( hr = SaveDeviceOwnershipKeys() ) )
        {
            DXTRACE_ERR_NOMSGBOX( TEXT("SaveDeviceOwnershipKeys"), hr );
            goto LCleanup;
        }

        // Report an error if there is a player that doesn't not
        // have a device assigned
        if( FAILED( hr = VerifyAssignment() ) )
            goto LCleanup;
        
        // Now that every player has at least one device, build and set 
        // the action map, and use callback into the app to tell the 
        // app of the device assignment.
        if( FAILED( hr = AddAssignedDevices( FALSE ) ) )
        {
            DXTRACE_ERR_NOMSGBOX( TEXT("AddAssignedDevices"), hr );
            goto LCleanup;
        }
    }
    
    hr = S_OK;

LCleanup:

    if( strUserNames )
        delete [] strUserNames;

    return hr;
}




//-----------------------------------------------------------------------------
// Name: UpdateDeviceOwnership
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::UpdateDeviceOwnership()
{
    DWORD   iPlayer;
    DWORD   iDevice;

    // Set all players to not have a device yet
    for( iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
        m_pUsers[iPlayer]->bFoundDeviceForPlayer = FALSE;

    // Set all devices as not assigned to a player
    for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
        m_pDeviceList[iDevice].pPlayerInfo = NULL;

    UnacquireDevices();
    
    // Update the device ownership by quering the DIPROP_USERNAME property
    for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
    {
        LPDIRECTINPUTDEVICE8 pdidDevice = m_pDeviceList[iDevice].pdidDevice;

        TCHAR strPlayerName[MAX_PATH];
        DIPROPSTRING dips;
        dips.diph.dwSize       = sizeof(DIPROPSTRING); 
        dips.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
        dips.diph.dwObj        = 0; // device property 
        dips.diph.dwHow        = DIPH_DEVICE;                     
        pdidDevice->GetProperty( DIPROP_USERNAME, &dips.diph );                    
        DXUtil_ConvertWideStringToGeneric( strPlayerName, dips.wsz );                    

        if( lstrcmp( strPlayerName, TEXT("") ) != 0 )
        {
            m_pDeviceList[iDevice].pPlayerInfo = LookupPlayer( strPlayerName );
            if( m_pDeviceList[iDevice].pPlayerInfo )
                m_pDeviceList[iDevice].pPlayerInfo->bFoundDeviceForPlayer = TRUE;
        }
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: UnacquireDevices
// Desc:
//-----------------------------------------------------------------------------
VOID CMultiplayerInputDeviceManager::UnacquireDevices()
{
    // Unacquire every device

    if( m_pDeviceList )
    {
        // All devices have been assigned a to a user in 
        // the new array, so clean up the local array
        for( DWORD iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
        {
            LPDIRECTINPUTDEVICE8 pdidDevice = m_pDeviceList[iDevice].pdidDevice;
            
            // Set the device's coop level
            pdidDevice->Unacquire();
        }
    }
}




//-----------------------------------------------------------------------------
// Name: SetFocus
// Desc: Sets the DirectInput focus to a new HWND
//-----------------------------------------------------------------------------
VOID CMultiplayerInputDeviceManager::SetFocus( HWND hWnd ) 
{
    m_hWnd = hWnd;
    
    if( m_pDeviceList )
    {
        // All devices have been assigned a to a user in 
        // the new array, so clean up the local array
        for( DWORD iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
        {
            LPDIRECTINPUTDEVICE8 pdidDevice = m_pDeviceList[iDevice].pdidDevice;

            // Set the device's coop level
            pdidDevice->Unacquire();
            pdidDevice->SetCooperativeLevel( m_hWnd, DISCL_NONEXCLUSIVE|DISCL_FOREGROUND );
        }
    }
}




//-----------------------------------------------------------------------------
// Name: GetDevices
// Desc: returns an array of DeviceInfo*'s
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::GetDevices( DeviceInfo** ppDeviceInfo, 
                                         DWORD* pdwCount )
{
    if( NULL==ppDeviceInfo || NULL==pdwCount )
        return E_INVALIDARG;
    
    (*ppDeviceInfo) = m_pDeviceList;
    (*pdwCount)     = m_dwNumDevices;
    
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: LookupPlayer
// Desc: searchs m_pUsers by player name
//-----------------------------------------------------------------------------
CMultiplayerInputDeviceManager::PlayerInfo* CMultiplayerInputDeviceManager::LookupPlayer( TCHAR* strPlayerName )
{
    for( DWORD iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
    {
        PlayerInfo* pCurPlayer = m_pUsers[iPlayer];
        if( lstrcmp( pCurPlayer->strPlayerName, strPlayerName ) == 0 )
            return pCurPlayer;
    }
    
    return NULL;
}




//-----------------------------------------------------------------------------
// Name: SaveDeviceOwnershipKeys
// Desc: For every device that's assigned to a player, save its device key 
//       and assigned player to registry.
//-----------------------------------------------------------------------------
HRESULT CMultiplayerInputDeviceManager::SaveDeviceOwnershipKeys()
{
    TCHAR strDeviceGuid[40];
    DWORD iDevice;

    for( iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
    {                
        PlayerInfo* pPlayerInfo = m_pDeviceList[iDevice].pPlayerInfo;           

        DXUtil_ConvertGUIDToString( &m_pDeviceList[iDevice].didi.guidInstance, strDeviceGuid );

        if( pPlayerInfo != NULL )
            DXUtil_WriteStringRegKey( m_hKey, strDeviceGuid, pPlayerInfo->strPlayerName );        
        else
            RegDeleteValue( m_hKey, strDeviceGuid );
    }                              

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DeleteDeviceOwnershipKeys
// Desc: Delete all the ownership keys
//-----------------------------------------------------------------------------
VOID CMultiplayerInputDeviceManager::DeleteDeviceOwnershipKeys()
{
    HKEY hKey;
    TCHAR *strRegKey;

    // Prepare strings to delete the key
    strRegKey = _tcsdup( m_strKey );
    if( strRegKey == NULL )
        return;

    TCHAR* strTemp = _tcsrchr( strRegKey, TEXT('\\') );
    
    // Unless the registry path string was malformed, we're ready to delete
    // and recreate the key
    if( strTemp ) 
    {
        *strTemp = 0;
        strTemp++;

        RegCloseKey( m_hKey );

        // Delete the reg key
        RegOpenKey( HKEY_CURRENT_USER, strRegKey, &hKey );
        SHDeleteKey( hKey, strTemp );
        RegCloseKey( hKey );
    
        // Create the key again now that all the subkeys have been deleted
        RegCreateKeyEx( HKEY_CURRENT_USER, m_strKey, 0, NULL,
                        REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, 
                        &m_hKey, NULL );
    }


    // Clean up memory allocation
    free( strRegKey );
}




//-----------------------------------------------------------------------------
// Name: Cleanup
// Desc:
//-----------------------------------------------------------------------------
VOID CMultiplayerInputDeviceManager::Cleanup()
{
    CleanupDeviceList();
    
    if( m_pUsers )
    {
        for( DWORD iPlayer=0; iPlayer<m_dwNumUsers; iPlayer++ )
            SAFE_DELETE( m_pUsers[iPlayer] );
        SAFE_DELETE( m_pUsers );
    }
    
    // Release() base object
    SAFE_RELEASE( m_pDI );
    
}
    



//-----------------------------------------------------------------------------
// Name: CleanupDeviceList
// Desc: Clean up the device array
//-----------------------------------------------------------------------------
VOID CMultiplayerInputDeviceManager::CleanupDeviceList()
{
    for( DWORD iDevice=0; iDevice<m_dwNumDevices; iDevice++ )
        SAFE_RELEASE( m_pDeviceList[iDevice].pdidDevice );
    free( m_pDeviceList );
    m_pDeviceList = NULL;
    m_dwMaxDevices = 0;
    m_dwNumDevices = 0;    
}




