Adding Xbox controller support to Light Gun Commando

Tuesday, 24th January 2023

After building a USB Guncon 2 adaptor for my Light Gun Commando project the Xbox seemed like it should be the next console to support as that also uses USB for its controllers. The xboxdevwiki seemed like a good starting point for information about its controller protocol. I wanted to be able to compare against real Xbox controllers too, so I bought an Xbox controller cable online and cut it in half so I could use the plug end to connect my circuit to the Xbox and the socket end to connect Xbox controllers to my PC.

This extension cable proved to be the first hurdle, as connecting an Xbox controller to my PC did absolutely nothing! When I checked the connections I found out why – in spite of the Xbox controller using the USB protocol and official cables using the USB standard colours (plus a yellow wire for video sync), this extension cable used its own colour scheme where +5V was on the black wire and ground was on the red wire. I suppose the moral of the story is to double check with a multimeter before wiring anything up, but fortunately no harm was done!

After connecting the Xbox controllers to my PC I started writing a program to query them using the documentation on the xboxdevwiki. I have put together an application similar to USBView that can display information about a connected Xbox controller, as well as preview the live state of a controller and send output reports to it. The source code for this is available as XboxControllerAnalyser on my Github account, the code is rough and ready as it was really just intended to be my development testbed but I thought it worth sharing in case someone else found it useful.

With a decent grasp of the Xbox's controller protocol, I started implementing a simple controller using the V-USB library on an ATmega328p. This had worked for the Guncon 2, and I was able to build something that worked in my Xbox Controller Analyser application, matching the reports from a real controller.

Unfortunately, I was unable to get the controller working on a real Xbox. Logging the activity it looked like the Xbox would start issuing a few requests, but then give up after a few attempts. The reports I was sending back matched what I could see from a real controller, so I wasn't really sure what the issue was. Real Xbox controllers contain a USB hub, with the being a USB device connected to the internal hub (the other ports on the hub are exposed via the accessory slots on the controller) but my device was a plain USB device directly connected to the console. Maybe that was it?

I think a more likely explanation is that the V-USB firmware is only capable of implementing low-speed USB devices. These limit their endpoint size to 8 bytes (and a maximum poll rate of once every 10ms) whereas the Xbox's input reports are 20 bytes long and the controllers report a poll interval of 4ms. I had set the interrupt endpoint to return the 20 byte reports in 8 byte chunks, and could read these back successfully with a single "read 20 bytes from this endpoint" request on my PC, but maybe the Xbox wasn't so happy about this.

Rather than rely on V-USB's software bit-banged USB I decided to switch to an ATmega32U4 instead, in the form of a cheap Arduino ProMicro board. This is a similar 8-bit AVR chip to what I'd been using already, but has hardware USB support and a good library called LUFA to assist with the USB hardware.

Sure enough, after adapting my code from V-USB to LUFA I was able to get the Xbox responding to a Master System control pad!

Of course, the real goal here is to implement an Xbox light gun. The xboxdevwiki doesn't provide much particular information about how the Xbox's light guns work, so I needed to connect an original Xbox one to my PC to get a better idea of what was going on. The first thing I noticed is that the guns would not enumerate properly if they were not fed with a video sync signal. This is normally supplied by the Xbox console via the fourth pin in its controller ports (with a yellow wire) and used by the light guns to determine the timing of the video signal and therefore where the gun was aimed. I ended up connecting the Xbox guns to both my PC (VCC, D+, D-, GND) and Xbox (video sync, ground) with the Xbox displaying a bright image on a CRT so I had a good reference for something the gun could "see".

After doing this I found the Xbox light guns would enumerate fully and would send data back to the PC in much the same way that a regular Xbox controller would. There are only really two major differences:

  1. The controller subtype byte (sent in response to GET_DESCRIPTOR) is set to 0x50 instead of 0x01 for a "Duke", 0x02 for a Controller-S, 0x20 for an arcade stick etc.
  2. There are three additional bits set in the byte at offset 3 in the input reports (sent in response to GET_CAPABILITIES or GET_REPORT):
enum {
    XID_LIGHT_GUN_LIGHT_VISIBLE = 0x20,
    XID_LIGHT_GUN_UNKNOWN_1 = 0x40,
    XID_LIGHT_GUN_UNKNOWN_2 = 0x80,
} XID_Controller_Input_LightGunFlags_t;

The three additional bits result in a value of 0xE0 in the byte at offset 3 (between the bitmask for digital buttons at offset 2 and the analogue "A" button at offset 4) as reported by GET_CAPABILITIES. I don't know what two of these are for (they were always cleared in the reponse to GET_REPORT), but one (0x20) is the bit that determines whether the light gun can see any light in a frame (set if the gun can see light, cleared if it can't).

Light guns will report their position via the left analogue stick, with (0, 0) being the centre of the screen, (-32768, -32768) being the bottom-left corner and (+32767, +32767) being the top-right corner. If the light gun can't see the screen then it will also report a position of (0, 0) but with bit 5 of the third byte of the report cleared.

Another difference with light guns is that they'll generally report fewer axes and buttons than a regular controller, and will also only report a single force feedback motor. Oddly enough two of my light guns report that they only have a left force feedback motor, but only respond to requests made to the right motor. In practice this still works in games as they set both motors at the same time anyway, but it did strike me as a bit odd!

With all this in place I was able to play some House of the Dead III and Silent Scope with my Wii remote, so I was very happy to get it working at long last. There was only one minor complication, which was that I couldn't get past the calibration screen in The House of the Dead without unplugging my circuit, bypassing the calibration with a regular controller, then plugging my circuit back in (Silent Scope had no such issue). After some further debugging it turns out the Xbox was sending a second type of output report to the controller that I wasn't handling, and though Silent Scope didn't care The House of the Dead III wasn't taking kindly to being ignored.

This light gun calibration output report is sent in the same fashion as the force feedback output report (via SET_REPORT) but with a wValue of 0x0201 instead of 0x0200. It's ten bytes in length and takes the following format:

typedef struct
{
    uint8_t bReportId; /**< Report ID. */
    uint8_t bLength;   /**< Size of the report, in bytes. */
    int16_t wInnerX;   /**< X offset to centre calibration target (0, 0). */
    int16_t wInnerY;   /**< Y offset to centre calibration target (0, 0). */
    int16_t wOuterX;   /**< X offset to top-left calibration target (-25000, 25000). */
    int16_t wOuterY;   /**< Y offset to top-left calibration target (-25000, 25000). */
} ATTR_PACKED XID_LightGun_Calibration_Output_Report_t;

Light guns are always calibrated in a two step process, and it's the light gun itself that handles the calibration, not the game software (the light gun should adjust its output to compensate for the calibration values it was previously sent). The first step's centre target is expected to be at (0, 0) and the second step's top-left target is expected to be at (-25000, +25000).

  1. The game will first send a calibration report with all four offsets reset to (0, 0), (0, 0) to reset any offsets and scaling.
  2. The game will now display a target in the centre of the screen and ask you to shoot it. This will tell it how "wrong" the gun is when aimed at where it thinks (0, 0) should be on the screen. If the gun normally shoots slightly too high and further to the right, it might report (1000, 3000) for example.
  3. The game sends a calibration report to the gun to negate the offset, e.g. (-1000, -3000), (0, 0) using our example of (1000, 3000) from before.
  4. The game will now display a target in at (-25000, 25000) on the screen and ask you to shoot it. This will tell it how "wrong" the gun is when aimed at the top left of the screen.
  5. The coordinates received from the gun will be subtracted from the expected (-25000, 25000) and sent back to the gun in the second part of the calibration report along with the values previously determined for the centre position. If the first calibration was enough to adjust the screen offset we might have seen the "perfect" coordinates of (-25000, 25000) in which case the report would be (-1000, -3000), (0, 0) again. However, if our gun read the coordinates slightly closer the the middle of the screen at (-20000, 22000) then the report would be (-1000, -3000), (5000, -3000).

All this is fairly academic, as in my case the Wii remote is already calibrated itself and sending "perfect" coordinates back to the Xbox, so I just ignore the calibration values – but by receiving the report rather than just ignoring it The House of the Dead III no longer gets stuck on the calibration screen.

The third and final light gun title for the Xbox is Starsky & Hutch, which is a two-player game where one person drives and the other shoots. It's just about manageable by one player if you have a steering wheel, but I don't have an Xbox wheel. I do have a rather good PlayStation 2 one, however, so after building an Xbox light gun I quickly cobbled together a PlayStation to Xbox controller adaptor and was soon able to drive and shoot my way terribly through Bay City:

This adaptor supports the DualShock 2 (including analogue face buttons), DualShock, Dual Analog, neGcon and Jogcon. It's not quite ready for general use (button remapping is currently handled by recompiling the code for specific games, not ideal!) but when I've got it tidied up a bit I'll share it as I was unable to find any other working projects that implement Xbox controllers on cheap microcontrollers.

FirstPreviousNextLast RSSSearchBrowse by dateIndexTags