Friday, February 21, 2014

Xbox One controller in Chrome on OSX

I've been working lately on better Gamepad API support in Chrome (call it my "20% project"). It's been a fun change of pace from WebGL, primarily because it's much more straightforward code. In the process of learning the ropes, I decided to try something a little crazy: I noticed that our code path for supporting the 360 controller on OSX didn't rely on any drivers, but instead used IOKit to parse the data packets manually. (Check out that code here.) So I figured, hey! Why not try the same thing with the Xbox One controller, ignoring the fact that it's currently supported on exactly zero non-console platforms.

Somewhat unexpectedly, it really wasn't too hard!

 


Allow me to preface the rest of the post by stressing that I have absolutely no idea what I'm doing. This is entirely a result of me futzing around with APIs that I only learned about 3 days ago, and dumping a lot of hex out to the screen to see what happened when I poked a button. I'm probably doing something laughably wrong, but I could care less. This is fun! :)

The only real "trick" involved here is that the controller is basically non-responsive until you send it some magic bytes. In my current implementation I'm sending  (0x05, 0x20) which I gleaned from Kyle Lemon's github repo. This lights up the "Xbox" logo on the front and the controller starts reporting sensible values. It seems like other sets of bytes work for this as well, not sure what the common thread is.

From there the buttons and axes are all reported in one tightly packed 18 byte blob, which is pretty easy to parse. It seems each data packet has a 4 byte header, the first byte of which tells you the type of data it contains. For buttons the type is 0x20, and the structure looks like this (once you strip off the header):

struct XboxOneButtonData {
  // Buttons
  bool sync   : 1;
  bool dummy1 : 1;  // Always 0.
  bool start  : 1;  // Not entirely sure what these are
  bool back   : 1;  // called on the new controller
  
  bool a : 1;
  bool b : 1;
  bool x : 1;
  bool y : 1;
  
  bool dpad_up    : 1;
  bool dpad_down  : 1;
  bool dpad_left  : 1;
  bool dpad_right : 1;
  
  bool bumper_left       : 1;
  bool bumper_right      : 1;
  bool stick_left_click  : 1;
  bool stick_right_click : 1;
  
  // Axes
  uint16 trigger_left;
  uint16 trigger_right;
  
  int16 stick_left_x;
  int16 stick_left_y;
  int16 stick_right_x;
  int16 stick_right_y;
};

The triggers have a range of 0-1023, and the sticks have a range of -32767 to 32768 (the full int16 range). There's a separate packet type (0x07) that gets sent for when you push the guide button, so that's easily detectable but I'm not currently monitoring it in the Chrome code. The only other packet type I've observed is some sort of heartbeat (0x03), but I'm not sure what purpose it serves.

Beyond that I haven't explored how anything else works. I don't know how to trigger the vibration motors, for example, but in this context I don't care because the gamepad API doesn't have an interface for it anyway. The one big thing I'm missing, and the main blocker before I would consider releasing this, is that you have to somehow explicitly tell the controller to shut down (turn off the LED) when you are done and I don't know how to do it. This means that the controller stays alive until you unplug the USB cable once you've visited a gamepad-enabled page. If anyone happens to know more in this regard I wouldn't mind a hint, and until then I'll poke around myself and see what I can find.

[UPDATE: After talking with some people online it seems like the controller staying on until disconnected might just be how it's designed to work. Game consoles do have different usage patterns than PCs, after all. As a result, I've finished cleaning up the code a bit and submitted the Chrome patch.

In terms of platform support, I'm only really looking at OSX right now. I'll leave it up to Microsoft to support their own controller. (It sounds like they'll eventually make it XInput compatible later this year.) I'm also not too concerned about the Linux side of things, since once a more complete picture of the protocol is available support will inevitably make it's way into the little OS that could.

[UPDATE #2: Microsoft now has official drivers for using the Xbox One controller on Windows]

Anyway, that's been my little side project for the last couple of days. Thought it would be interesting to share with everyone!

7 comments:

  1. very cool stuff ! how did you sent the initial raw bytes to the controller to make it responsive? it's not even recognised as a HID and as far as i know (not much,really) USB is not an I/O port , but more a rather complicated bus type

    ReplyDelete
    Replies
    1. (Warning! I'm pretty new to the world of HID, so I may be using the wrong terminology here and there)

      The controller IS a HID device, but where typically you would expect a device like this to advertise itself as a type 4 or 5 device (Joystick/Gamepad) this one is advertised as a type 255 (vendor specific). Thus you can communicate with it using your platforms USB libraries but you have to target this device specifically and know it's custom protocol.

      To communicate with the controller I used IOKit to open up the appropriate interface (found through trial and error) and write to the appropriate endpoint's pipe. You can see the code that sends the "init" bytes in Chrome here: https://code.google.com/p/chromium/codesearch#chromium/src/content/browser/gamepad/xbox_data_fetcher_mac.cc&sq=package:chromium&l=601

      Reading the data packets is very similar: https://code.google.com/p/chromium/codesearch#chromium/src/content/browser/gamepad/xbox_data_fetcher_mac.cc&sq=package:chromium&l=586
      https://code.google.com/p/chromium/codesearch#chromium/src/content/browser/gamepad/xbox_data_fetcher_mac.cc&sq=package:chromium&l=557

      Delete
    2. this makes me wanna try to write an Xinput wrapper for this (Microsoft isn't gonna release this anytime soon) anyway this is really cool! :) and of course thanks for sharing!

      Delete
  2. Any further progress on this hobby project? I'm sure a few of us Mac users would pay some scratch for a driver. :-)

    ReplyDelete
    Replies
    1. I actually did some research on what it takes to build an OSX driver, played with it a little, crashes my machine a few times, and decided that I could spend my time more productively elsewhere. Sorry!

      It does seem like you could hack the new protocol into the Tattibogle drivers without too much trouble, but those drivers are notoriously flaky anyway (as in: Kernel panic when you unplug the controller type flaky) so I don't know ho much I would recommend going along that path.

      Delete
    2. I really hope someone picks this up so that I can play Towerfall on my Mac. I can boot into Windows to play it there, but the semi-official windows x64 driver *does not work* on my macbook! Extremely frustrated with that.

      Delete
  3. Very cool, thanks for getting this into Chrome! I'm not a gamer, but wanted to do some output of a modded game controller I've been helping a client develop; never imagined it'd be as easy on OS X as plugging the thing in and pulling up http://html5gamepad.com/ :-)

    ReplyDelete