Developing for the Microsoft SideWinder Game Pad

Microsoft Corporation
June 24, 1996 (updated August 15, 1996)

Introduction

The SideWinder™ game pad introduces some of the most advanced gaming-device technology available on the market. It incorporates digital overdrive for speed, and reliability for enhanced game play. The game pad is simple to use and to write for, contains an 8-position directional pad with 10 buttons, does not drift or require calibration, and polls very quickly. The SideWinder game pad allows you to connect up to four other game pads and includes pass-through mode, so you can plug in the SideWinder 3D Pro joystick.

Adding support for the SideWinder game pad in your next game is easy.

Supporting the SideWinder Game Pad

SideWinder game pad includes a minidriver that works seamlessly with DirectInput™. To find out if a SideWinder game pad is installed, you don't have to interpret the Registry settings; you simply enumerate the currently selected joystick devices. The following console application code fragment lists the OEM product name for all 16 joysticks:

#include <windows.h>
#include <mmsystem.h>
#include <regstr.h>
#include <stdio.h>
// Function prototypes:
MMRESULT joyGetOEMProductName(UINT id, LPTSTR pszName);
//
// --- Main Console application ---
// Enumerates JOYSTICKID 1 to 16, and displays the OEM product name for all 16
// joysticks.  Uses the routine joyGetOEMProductName.
//
void main(void)
{
 TCHAR szNameDevice[256];
 UINT i;
 for (i=JOYSTICKID1; i< joyGetNumDevs(); i++)
 {
  printf("JOYSTICKID#%d : ", (i+1));
  if (joyGetOEMProductName(i, (LPTSTR) &szNameDevice[0]) != ERROR_SUCCESS)
  {
   printf("None\n");
  }
  else
  {
   printf("%s\n", szNameDevice);
  }
 }
}
// ----------------------------------------------------------------------------
//
// Function: joyGetOEMProductName
// Parameters: UINT id  - Joystick ID from JOYSTICKID1 to JOYSTICK16
//    TCHAR * pszName - String storage for the OEM Product name
//         for the selected Joystick device specified
//         by the parameter "id"
//
// Returns:  If Successful, pszName contains OEM product name for JOYSTICKID
//    else Failure, returns MMRESULT error code, and pszName is cleared.
//
// Comments:
// JOYSTICKID1 to JOYSTICKID16 is zero-based, while the registry entries are
//  1-based.  This routine expects the parameter "id" to be using JOYSTICKID1
//  to JOYSTICKID16, which are defined in the multimedia header file mmsystem.h.
//  The following registry keys/values are defined in the header file 
//  regstr.h:
//   REGSTR_PATH_JOYCONFIG
//   REGSTR_PATH_JOYOEM
//   REGSTR_VAL_JOYOEMNAME 
// 
// ----------------------------------------------------------------------------
MMRESULT joyGetOEMProductName(UINT id, TCHAR * pszName)
{
 JOYCAPS JoyCaps;
 TCHAR szKey[256];
 TCHAR szValue[256];
 UCHAR szOEMKey[256];
 HKEY hKey;
 DWORD dwcb;
 LONG lr;
// Note: JOYSTICKID1-16 is zero-based, registry entries for VJOYD are 1-based.
 id++;  
 if (id > joyGetNumDevs() ) return JOYERR_NOCANDO;
// Open .. MediaResources\CurrentJoystickSettings
 joyGetDevCaps((id-1), &JoyCaps, sizeof(JoyCaps));
 sprintf(szKey,
   "%s\\%s\\%s",
   REGSTR_PATH_JOYCONFIG,
   JoyCaps.szRegKey,
   REGSTR_KEY_JOYCURR);
 lr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPTSTR) &szKey, 0, KEY_ALL_ACCESS, &hKey);
 if (lr != ERROR_SUCCESS) return JOYERR_NOCANDO;
// Get OEM Key name. If the query is unsuccesful, then Null the string and 
// return  an Error.
 dwcb = sizeof(szOEMKey);
 sprintf(szValue, "Joystick%d%s", id, REGSTR_VAL_JOYOEMNAME);
 lr = RegQueryValueEx(hKey, szValue, 0, 0, (LPBYTE) &szOEMKey, (LPDWORD) &dwcb);
 RegCloseKey(hKey); if (lr != ERROR_SUCCESS)
 {
  *pszName = 0;
  return JOYERR_NOCANDO;
 }
// Open OEM Key from ...MediaProperties.
 sprintf(szKey, "%s\\%s", REGSTR_PATH_JOYOEM, szOEMKey);
 lr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_ALL_ACCESS, &hKey);
 if (lr != ERROR_SUCCESS) return JOYERR_NOCANDO;
// Get OEM Name.
 dwcb = sizeof(szValue);
 lr = RegQueryValueEx( hKey,
           REGSTR_VAL_JOYOEMNAME,
           0, 0,
           (LPBYTE) pszName,
           (LPDWORD) &dwcb);
 RegCloseKey(hKey);
 if (lr != ERROR_SUCCESS)
  return JOYERR_NOCANDO;
 else
  return JOYERR_NOERROR;
}

Tip: Don't forget to link your code with winmm.lib.

To determine whether the SideWinder game pad is currently selected, do a string compare of the OEM product name—which is obtained from the joyGetOEMProductName routine—with the string "Microsoft SideWinder game pad".

The SideWinder game pad has a directional pad (d-pad) and 10 buttons (A, B, C, X, Y, Z, left trigger, right trigger, Macro, and Start). All of the game pad's capabilities are found in the JOYCAPS structure, shown in the following sample code.

#include <mmsystem.h>
JOYCAPS jc;
. . .
MMRESULT ret = joyGetDevCaps(JOYSTICKID1, &jc, sizeof(jc));
. . .

The following JOYCAPS members sample code shows the game pad control capabilities.

SideWinder Game Pad Control Capability

JOYCAPS Member Control Capability
jc.wXmin = 0 Minimum d-Pad x axis
jc.wXmax = 65535 Maximum d-Pad x axis
jc.wYmin = 0 Minimum d-Pad y axis
jc.wYmax = 65535 Maximum d-Pad y axis
jc.wZmin = 0 Not applicable
jc.wZmax = 65535 Not applicable
jc.wRmin = 0 Not applicable
jc.wRmax = 65535 Not applicable
jc.wNumButtons = 10 10 buttons available

The following table illustrates the button assignments for the SideWinder game pad.

Button Assignments for SideWinder Game Pad

JOY_BUTTONx SideWinder Game Pad
JOY_BUTTON1 A
JOY_BUTTON2 B
JOY_BUTTON3 C
JOY_BUTTON4 X
JOY_BUTTON5 Y
JOY_BUTTON6 Z
JOY_BUTTON7 Left trigger
JOY_BUTTON8 Right trigger
JOY_BUTTON9 Start
JOY_BUTTON10 Macro*

*You can use the Microsoft Game Device Profiler (which ships with the SideWinder game pad) to record your favorite moves. To play a game pad macro, press the Macro button, then press the button (A, B, C, X, Y, Z, or one of the triggers) to which you assigned the macro.

Note   The Mode button on the game pad switches the game pad between digital overdrive and pass-through mode; you cannot poll this button.

Connecting Multiple Game Pads at Once

You can connect up to four SideWinder game pads together on a Windows® 95 system using the SideWinder game pad drivers. To connect multiple game pads, daisy-chain them; you don't need a hub device.

To program your game for multiple game pads, you need to determine how many SideWinder game pads are connected to the user's system. DirectInput supports up to 16 joysticks (using JOYSTICKID1 through JOYSTICKID16).

Note   A compiler error using JOYSTICKID3 to JOYSTICKID16 might mean that the joystick IDs aren't defined in the Mmsystem.h header file that was shipped with the SDK. To resolve this error, define the joystick IDs yourself as enumerated types from 1 to 16.

The following sample code module enumerates the number of joysticks (and game pads) that are currently connected to the user's system.

#include <windows.h>
#include <mmsystem.h>
. . .
/****************************************************************************
**
** UINT joyGetNumDevicesConnected(void)
**
** Description : Determine the number of joysticks that are connected.
**
** Parameters : None.
**
** Returns  : UINT - Number of devices connected.
**
*****************************************************************************/
UINT joyGetNumDevicesConnected(void)
{
UINT JoyID;
 UINT nDevicesConnected=0;
 JOYINFOEX ji;
 ji.dwSize = sizeof(JOYINFOEX);
 for (JoyID=JOYSTICKID1; JoyID<joyGetNumDevs(); JoyID++)
 {
  if (joyGetPosEx(JoyID, &ji)==JOYERR_NOERROR)
  {
   nDevicesConnected++;
  }
 }
 return nDevicesConnected;
}

Polling the Game Pad

Polling a typical joystick can take anywhere from 1.2 to nearly 8 milliseconds. That's way too slow for most game developers, who would rather use that time for graphics rendering and play action.

Because the SideWinder game pad uses digital overdrive technology, you can quickly poll all 10 game pad buttons in a single poll call. This typically takes only 210 microseconds (µs) for 1 game pad, 360 µs when all 4 are connected. This makes it unnecessary to poll each button and axis separately. For details about the game pad buttons, see "Button Assignments for SideWinder Game Pad" above.

The DirectInput API joyGetPosEx is used to poll the game pad. The following code module demonstrates how this API is used by getting the SideWinder game pad data from DirectInput:

// Reminder: You need mmsystem.h and you must link with winmm.lib.
#include <windows.h>
#include <mmsystem.h>
BOOL GetJoyData(UINT uJoyId, LPJOYINFOEX lpji)
{
 memset(lpji, 0, sizeof(JOYINFOEX)); // For good measure.
 lpji->dwSize = sizeof(JOYINFOEX);
 // Note: With game pad, it takes no more time to return all 
 // information from the joystick than it does to get only
 // the button states or axis.
 //
 lpji->dwFlags = JOY_RETURNALL;
 // JoyGetPosEx will fill in the joyinfoex struct with all the
 // joystick information.
 //
 switch(joyGetPosEx(uJoyID, lpji))
 {
  case JOYERR_NOERROR: // No problem.
   break;
  case MMSYSERR_NODRIVER:
   MessageBox( NULL, 
      "The game pad driver is not present.",
      "JOYSTICK ERROR",MB_OK);
   return FALSE;
  case MMSYSERR_INVALPARAM:
   MessageBox( NULL, 
      "An invalid parameter was passed.",
      "JOYSTICK ERROR",MB_OK);
   return FALSE;
  case MMSYSERR_BADDEVICEID:        
   MessageBox( NULL, 
      "The specified JOYSTICKID identifier is invalid.",
      "JOYSTICK ERROR",MB_OK);
   return FALSE;
  case JOYERR_UNPLUGGED:        
   MessageBox( NULL, 
      "Your game pad is unplugged.",
      "JOYSTICK ERROR",MB_OK);
   return FALSE;
  default:
   MessageBox( NULL, 
      "Unknown game pad error.",
      "JOYSTICK ERROR",MB_OK);
   return FALSE;
 }   // End of switch.
 return TRUE; 
} // GetJoyData()
. . .

Using the Game Pad with MS-DOS Games

You can use the SideWinder game pad in an MS-DOS® box under Windows 95. If you run an MS-DOS game under Windows 95, the minidriver provides you with game pad data in digital overdrive mode.

When Digital Overdrive Is Used

Environment Digital Overdrive
Windows 95 DirectInput Yes
DOS box while running Windows 95 Yes
Pure DOS No

Questions and Answers

Does the user need to recalibrate the game pad?

No, the game pad is self-calibrating.

Can I write non-Windows 95 games (for example MS-DOS native games) that take advantage of the digital overdrive mode?

At the moment, no. If you're interested in doing this, send e-mail to swinddev@microsoft.com.

How do I support multiple SideWinder game pads?

All of the DirectInput functions, except for joyGetNumDevs, require a joystick ID. Up to 16 different joysticks can be supported. The following code module (basically a wrapper for the registry information) creates a data structure (context) of up to 16 possible joystick connections.

#include <windows.h>
#include <mmsystem.h>
. . .
typedef struct _CONTEXTINFO
{
 UINT id;     // Keep the Joystick ID here.
 char szOEMProductName[128];  // OEM product name.
 JOYCAPS JoyCaps;   // joystick capabilities.
} CONTEXTINFO;
typedef struct _JOYCONTEXT
{
 UINT nDevices;    // Number of joysticks connected (number of CONTEXTINFO).
 CONTEXTINFO ContextInfo[16];  // Up to 16 possible joystick device Contexts.
} JOYCONTEXT, *PJOYCONTEXT;
/****************************************************************************
**
** void joyGetContext(PJOYCONTEXT pjc)
**
** Description :  Get the current joystick devices context.
** Parameters : PJOYCONTEXT pjc - pointer to JOYCONTEXT structure containing
**          the current state of the connected joystick
**          devices (see above declaration).
** Returns  : None.
*****************************************************************************/
void joyGetContext(PJOYCONTEXT pjc)
{
 UINT JoyID;
 UINT i=0;
 JOYINFOEX ji;
 ji.dwSize = sizeof(JOYINFOEX);
 for (JoyID=JOYSTICKID1; JoyID<joyGetNumDevs(); JoyID++)
 {
  if (joyGetPosEx(JoyID, &ji)==JOYERR_NOERROR)
  {
   pJoyContext->ContextInfo[i].id = JoyID;
   joyGetOEMProductName(JoyID, pjc->ContextInfo[i].szOEMProductName);
   joyGetDevCaps(JoyID, &pjc->ContextInfo[i].JoyCaps);
   i++;
  }
 }
 pJoyContext->nDevices= i;
}

How can I detect when the game pad settings are changed?

The SideWinder family of products has a built-in mechanism for informing the minidrivers of the device status. You can daisy-chain up to four SideWinder game pads together, or you can connect a SideWinder 3D Pro joystick to a SideWinder game pad. When a device's status changes, a notification message is broadcast to all top-level windows, including disabled or invisible windows. This notification can be used to:

To be notified of changes in joystick settings, the application must first retrieve the message ID of the notification event posted by the DirectInput driver subsystem. To retrieve the message ID, use the RegisterWindowMessage Windows API call, which passes "MSJSTICK_VJOYD_MSGSTR" as its argument:

uMsgId = RegisterWindowMessage("MSJSTICK_VJOYD_MSGSTR" );

With the message ID retrieved, you can use the application's windows message queue to check for the occurrence of this notification, as shown in the following example.

#include <windows.h>
#include <mmsystem.h>
. . .
UINT uMsgId;
int WinMain(...)
{
// Retrieve message ID that joystick driver subsystem posts.
 uMsgId = RegisterWindowMessage("MSJSTICK_VJOYD_MSGSTR");
 
 // Init Instance, Register Classes, Create Window etc. . . .
// Acquire messages from queue and dispatch messages until a WM_QUIT 
// message is received. 
  while (GetMessage(&msg,NULL, NULL, NULL)) 
 {
   TranslateMessage(&msg);  // Translates virtual key codes.
   DispatchMessage(&msg);  // Dispatches message to window.
  }
return (msg.wParam);   // Returns the value from PostQuitMessage.
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Did the joystick configuration change?
if(msg == uMsgId)
{
 // YES! 
 // This is where we can inform the user of the changes in joystick
 // configuration and/or update our joystick device context. . . 
 return (DefWindowProc(hWnd, msg, wParam, lParam));
}
// Perform normal message processing. . .
switch (msg)
{
 . . .
}
 return (NULL);
}

If I need more information, who do I talk to?

Send your question to swinddev@microsoft.com. Make sure you include your name and your company name.