1058 lines
37 KiB
C++
1058 lines
37 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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) Microsoft Corporation. All Rights Reserved.
|
|
//-----------------------------------------------------------------------------
|
|
#define STRICT
|
|
#define DIRECTINPUT_VERSION 0x0800
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include <basetsd.h>
|
|
#include <tchar.h>
|
|
#include <stdio.h>
|
|
#include <windows.h>
|
|
#include <dxerr9.h>
|
|
#include <d3d9types.h> // included to get the D3DCOLOR_RGBA macro.
|
|
#include "MultiDI.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_READ | KEY_WRITE, 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( TEXT("DirectInput8Create"), hr );
|
|
|
|
if( FAILED( hr = SetActionFormat( pdiaf, TRUE, bResetOwnership, bResetMappings ) ) )
|
|
return DXTRACE_ERR( 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( 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( 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( 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( 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( TEXT("BuildActionMap"), hr );
|
|
if( FAILED( hr = pdidDevice->SetActionMap( m_pdiaf, pPlayerInfo->strPlayerName, DIDSAM_DEFAULT ) ) )
|
|
return DXTRACE_ERR( 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;
|
|
|
|
DeviceInfo* pListNew = NULL;
|
|
pListNew = (DeviceInfo*) realloc( m_pDeviceList, m_dwMaxDevices*sizeof(DeviceInfo) );
|
|
|
|
// Verify allocation
|
|
if( NULL == pListNew )
|
|
return DXTRACE_ERR( TEXT("BuildDeviceList"), E_OUTOFMEMORY );
|
|
else
|
|
m_pDeviceList = pListNew;
|
|
|
|
ZeroMemory( m_pDeviceList, m_dwMaxDevices*sizeof(DeviceInfo) );
|
|
|
|
// Enumerate available devices for any user.
|
|
HRESULT rs = 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;
|
|
|
|
DeviceInfo* pListNew = NULL;
|
|
pListNew = (DeviceInfo*) realloc( m_pDeviceList, m_dwMaxDevices*sizeof(DeviceInfo) );
|
|
|
|
// Verify allocation
|
|
if( NULL == pListNew )
|
|
{
|
|
DXTRACE_ERR( TEXT("EnumDevice"), E_OUTOFMEMORY );
|
|
return DIENUM_STOP;
|
|
}
|
|
else
|
|
m_pDeviceList = pListNew;
|
|
|
|
ZeroMemory( m_pDeviceList + m_dwMaxDevices - 5, 5*sizeof(DeviceInfo) );
|
|
}
|
|
|
|
DXUtil_ConvertGUIDToStringCch( &pdidi->guidInstance, strDeviceGuid, 40 );
|
|
DXUtil_ReadStringRegKeyCch( 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( 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( 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( TEXT("BuildActionMap"), hr );
|
|
break;
|
|
}
|
|
|
|
if( FAILED( hr = pdidDevice->SetActionMap( m_pdiaf, NULL, DIDSAM_NOUSER ) ) )
|
|
{
|
|
DXTRACE_ERR( 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(204,204,255,255);
|
|
dicdp.dics.cCalloutLine = D3DCOLOR_RGBA(255,255,255,128); //
|
|
dicdp.dics.cCalloutHighlight= D3DCOLOR_RGBA(204,204,255,255);
|
|
dicdp.dics.cBorder = D3DCOLOR_RGBA(153,153,204,128);
|
|
dicdp.dics.cControlFill = D3DCOLOR_RGBA( 51, 51, 102, 128); //
|
|
dicdp.dics.cHighlightFill = D3DCOLOR_RGBA(0,0,0,128);
|
|
dicdp.dics.cAreaFill = D3DCOLOR_RGBA(0,0,50,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( 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( TEXT("ConfigureDevices"), hr );
|
|
goto LCleanup;
|
|
}
|
|
|
|
if( dwFlags & DICD_EDIT )
|
|
{
|
|
// Update the device ownership
|
|
if( FAILED( hr = UpdateDeviceOwnership() ) )
|
|
{
|
|
DXTRACE_ERR( TEXT("UpdateDeviceOwnership"), hr );
|
|
goto LCleanup;
|
|
}
|
|
|
|
// Now save the device keys that are assigned players to registry,
|
|
if( FAILED( hr = SaveDeviceOwnershipKeys() ) )
|
|
{
|
|
DXTRACE_ERR( 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( 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_ConvertWideStringToGenericCch( strPlayerName, dips.wsz, MAX_PATH );
|
|
|
|
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_ConvertGUIDToStringCch( &m_pDeviceList[iDevice].didi.guidInstance, strDeviceGuid, 40 );
|
|
|
|
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 );
|
|
RegDeleteKey( 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_READ | KEY_WRITE, 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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Name: DXUtil_ConvertGUIDToStringCch()
|
|
// Desc: Converts a GUID to a string
|
|
// cchDestChar is the size in TCHARs of strDest. Be careful not to
|
|
// pass in sizeof(strDest) on UNICODE builds
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT DXUtil_ConvertGUIDToStringCch( const GUID* pGuidSrc, TCHAR* strDest, int cchDestChar )
|
|
{
|
|
int nResult = _sntprintf( strDest, cchDestChar, TEXT("{%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X}"),
|
|
pGuidSrc->Data1, pGuidSrc->Data2, pGuidSrc->Data3,
|
|
pGuidSrc->Data4[0], pGuidSrc->Data4[1],
|
|
pGuidSrc->Data4[2], pGuidSrc->Data4[3],
|
|
pGuidSrc->Data4[4], pGuidSrc->Data4[5],
|
|
pGuidSrc->Data4[6], pGuidSrc->Data4[7] );
|
|
|
|
if( nResult < 0 )
|
|
return E_FAIL;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Name: DXUtil_ConvertWideStringToAnsi()
|
|
// Desc: This is a UNICODE conversion utility to convert a WCHAR string into a
|
|
// CHAR string.
|
|
// cchDestChar is the size in TCHARs of strDestination
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT DXUtil_ConvertWideStringToAnsiCch( CHAR* strDestination, const WCHAR* wstrSource,
|
|
int cchDestChar )
|
|
{
|
|
if( strDestination==NULL || wstrSource==NULL || cchDestChar < 1 )
|
|
return E_INVALIDARG;
|
|
|
|
int nResult = WideCharToMultiByte( CP_ACP, 0, wstrSource, -1, strDestination,
|
|
cchDestChar*sizeof(CHAR), NULL, NULL );
|
|
strDestination[cchDestChar-1] = 0;
|
|
|
|
if( nResult == 0 )
|
|
return E_FAIL;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Name: DXUtil_ConvertAnsiStringToGeneric()
|
|
// Desc: This is a UNICODE conversion utility to convert a WCHAR string into a
|
|
// TCHAR string.
|
|
// cchDestChar is the size in TCHARs of tstrDestination. Be careful not to
|
|
// pass in sizeof(strDest) on UNICODE builds
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT DXUtil_ConvertWideStringToGenericCch( TCHAR* tstrDestination, const WCHAR* wstrSource,
|
|
int cchDestChar )
|
|
{
|
|
if( tstrDestination==NULL || wstrSource==NULL || cchDestChar < 1 )
|
|
return E_INVALIDARG;
|
|
|
|
#ifdef _UNICODE
|
|
wcsncpy( tstrDestination, wstrSource, cchDestChar );
|
|
tstrDestination[cchDestChar-1] = L'\0';
|
|
return S_OK;
|
|
#else
|
|
return DXUtil_ConvertWideStringToAnsiCch( tstrDestination, wstrSource, cchDestChar );
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Name: DXUtil_ReadStringRegKeyCch()
|
|
// Desc: Helper function to read a registry key string
|
|
// cchDest is the size in TCHARs of strDest. Be careful not to
|
|
// pass in sizeof(strDest) on UNICODE builds.
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT DXUtil_ReadStringRegKeyCch( HKEY hKey, LPCTSTR strRegName, TCHAR* strDest,
|
|
DWORD cchDest, LPCTSTR strDefault )
|
|
{
|
|
DWORD dwType;
|
|
DWORD cbDest = cchDest * sizeof(TCHAR);
|
|
|
|
if( ERROR_SUCCESS != RegQueryValueEx( hKey, strRegName, 0, &dwType,
|
|
(BYTE*)strDest, &cbDest ) )
|
|
{
|
|
_tcsncpy( strDest, strDefault, cchDest );
|
|
strDest[cchDest-1] = 0;
|
|
return S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
if( dwType != REG_SZ )
|
|
{
|
|
_tcsncpy( strDest, strDefault, cchDest );
|
|
strDest[cchDest-1] = 0;
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Name: DXUtil_WriteStringRegKey()
|
|
// Desc: Helper function to write a registry key string
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT DXUtil_WriteStringRegKey( HKEY hKey, LPCTSTR strRegName,
|
|
LPCTSTR strValue )
|
|
{
|
|
if( NULL == strValue )
|
|
return E_INVALIDARG;
|
|
|
|
DWORD cbValue = ((DWORD)_tcslen(strValue)+1) * sizeof(TCHAR);
|
|
|
|
if( ERROR_SUCCESS != RegSetValueEx( hKey, strRegName, 0, REG_SZ,
|
|
(BYTE*)strValue, cbValue ) )
|
|
return E_FAIL;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|