v 1
July 5, 2020

HID Virtual Driver Kit

The to Emulate

Joystick, Keyboard and Mouse for Windows 7, 8, 8.1, 10 64 bit

Using the Gamepad Driver

Overview

The Gamepad driver allows you to:

  • Send X, Y, RX, RY, Z axis positions.
  • Press and/or release the hat button.
  • Press and/or release up to 10 buttons.

This is not the same as an XBox Controller, which is quite different from the HID Gamepad specification. So if a game has support for XBox controllers, it unlikely that this game pad will work. Maybe someday we will find a good way to emulate an XBox controller. In effect, the Gamepad acts as a limited joystick.

The Gamepad Reader Utility

While developing code that uses the gamepad driver, use the reader utility to consume data from the gamepad driver. This is easier than using the Windows Game Controller control panel app, or an actual game that consumes gamepad data. It will be way faster and will help you understand the data that we need to send to the driver, especially the hat and button bit arrays.

The Windows Game Controller control panel app. It kind of works, but the gauges only update if you click on it.
This is the Sender app sending data to the gamepad driver which is being read by the Reader app. Using the Reader for development makes things easier to understand.

The Gamepad Driver Sender Utility

Use the SDK Gamepad Driver Sender to send axis data to the gamepad driver. The gamepad driver will appear to Windows as if it is an actual gamepad gamepad. Be sure to press the ‘Connect to gamepad Driver’ before sending data to the driver.

NOTE: As of version 2.1, the gamepad driver sender and receiver apps do not include code to send and receive hat and button presses. The method to do so would be very similar to joysticks, so see the joystick app for more info. The driver can handle button and hat presses fine.

Axis Values

The gamepad driver supports five analog axis. Gamepads also can have a lot of jitter. You can see this if you tweak the Reader code to open a real physical gamepad you have laying around. Leaving the stick in the neutral position should only send an unchanged value if you aren’t touching the stick. But a lot of sticks, expensive ones included, will have a ‘noise’ while in neutral. That’s why games have deadzones.

Gamepads also report stick deflection in as a linear value, and this is why games have axis curves. Linear values can be really frustrating to use by end-users.

And finally, gamepads axis can also drift over time and go out of calibration.

A virtual gamepad driver has none of these problems. The value you send to the driver is the value the driver consumer (a game?) will use. You can deadzone, smooth and curve the data all you want and then send it to the gamepad driver. It is in your control, and that’s a good thing.

Each of the X, Y, Z, Rx, Ry, Rz, Slider, Dial and Wheel axis accept values from 0 to 32767. Using Ry as an example (roll in an airplane), 0 would be deflected fully left, 16384 is neutral, 32767 is full right deflection.

Sending Data to the Driver

First, iterate through all drivers until you find ‘Tetherscript Virtual Gamepad’ and connect to that driver. Then send a packed record as a ‘Feature Report’ to the driver. More info on feature reports below:

https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/

Gamepad driver reports are based sdk/delphi/common/hut1_12v2.pdf

Delphi

Here’s the gamepad driver report Delphi data format. In our Delphi source we call this a Feature, but really it is a standard HID Input Report. This is just the terminology left over from Delphi’s JCL and JVCL libraries.:

type
PTSetFeatureGamepad = ^TSetFeatureGamepad;
TSetFeatureGamepad = packed record
  ReportID: Byte;
  CommandCode: Byte;
  X: Word;
  Y: Word;
  rX: Word;
  rY: Word;
  Z: Word;
  buttons: array[0..1] of Byte;
  padding: array[0..1] of Byte;
end;

C# – no example app exists yet for the gamepad

c

This corresponds to the following c struct (this is the actual receiving input report struct from the driver source):

typedef struct _HIDMINI_CONTROL_INFO {
  UCHAR ReportId;
  UCHAR ControlCode;
  unsigned short X;
  unsigned short Y;
  unsigned short rX;
  unsigned short rY;
  unsigned short Z;
  BYTE buttons[2];
  BYTE padding[2];
} HIDMINI_CONTROL_INFO, * PHIDMINI_CONTROL_INFO;

  • ReportID: is always 1 or 2. Set it to 2 to set axis and button data to the driver. Set it to 1 to reset the driver (and unstick all the buttons).
  • CommandCode: This is 2. Also known as a ControlCode. Set it to 1 to reset axis, hat and buttons. Set it to 2 to send axis, hat and button info.
  • X, Y, Z: These are translational axis positions. These are more appropriate for a 6DOF device where you can slide along an axis, as well as rotating around the axis. Values are (0-65535).
  • RX, RY: These are rotational axis positions. These are the standard axis where you have pitch=RY and Roll=RY. Values are (0-65535).
  • hat: The hat position is represented by the six least significant bits of the buttons[0] byte.
  • buttons: The state of the ten buttons is represented by the two most significant bits of the buttons[0] byte along with the entire buttons[1] byte. This is an array of 10 bits, which corresponds to 10 buttons. If a bit is set to 0, the button is up. Set it to a 1 and the button is pressed. So to press a virtual gamepad button and release it, you set a bit to 1, wait x ms (try 50ms), then set the bit to zero. Each byte has represents 8 buttons, with the right-most bit representing the first button and the left-most bit representing the last buttons, as shown below (this can be confusing). Hat bits are in bold.
    • 00000000 00000000 : Hat and all buttons are unpressed.
    • 00000001 00000000 : Hat is pressed upper-right and all buttons are unpressed.
    • 00001000 00000000 : Hat is pressed down and all buttons are unpressed.
    • 01100000 00000000 : Hat is pressed upper-left and button 1 is unpressed.
    • 10100000 00000000 : Hat is pressed upper-left and button 2 is unpressed.
    • 00100000 00000001 : Hat is pressed upper-left and button 3 is unpressed.
    • 00100000 10000000 : Hat is pressed upper-left and button 10 is unpressed.
    • Easy, right?
  • padding – always two bytes. Set these to bytes to zero.

Handling Stuck Gamepad Buttons

As an example, you may press gamepad button 1 and then it is possible that you may have forgotten, or due to your app crashing, that the button remains pressed. There are three ways to unstick a button.

  1. Clear the button by setting its byte array bit to zero.
  2. Send a reset command to the driver. This sets all axis to mid-range (center) and releases the hat and all buttons.
  3. Reboot.

Resetting the Driver

To reset the driver, send the report with a CommandCode = 1 and any values for axis, hat and buttons. The driver will center all axis and release the hat and all buttons.

You can also just send the axis centering values and release the hat and buttons yourself with a CommandCode = 2. It’s up to you. Both methods are equivalent.

Your First Hello-Driver App – a Suggestion

  1. Start the Gamepad Reader utility and connect to the driver.
  2. In your app, iterate through the drivers and find and connect to the gamepad driver.
  3. In your app, send data with a command code of 2, X = 10000, hat = 0, button array all zeros.
  4. In the reader utility, you should see the X axis deflected a bit left of center.