What you'll find here is a small Linux program (sixproxy) that transforms the packets sent over bluetooth by a PS3 controller to a PS3 system. You could use it to remap buttons, change the way the axes are handled, or anything else you can think of, but right now it just removes the dead zone in Wipeout HD. The "Background" section explains my motivations. After that, you'll find the (somewhat steep) requirements, an overview and the tutorial proper.
EDIT 2014-04-28: The hack presented here is now almost five years old (2009-08-16). It depends unfortunately on the internals of the bluetooth driver in Linux version 2.6.X and this has probably changed quite a lot in a recent (3.X) Linux kernel.
I'm afraid I don't have time to "maintain" this hack anymore, so if my kernel patch doesn't seem to work, I won't be able to help you.
I'm a big fan of the Wipeout series of racing games. I got a Playstation 3 specifically to be able to play the latest instalment: Wipeout HD. That version adds beautiful hi-res graphics to the old recipe, but also something I didn't quite expect: sixaxis motion-sensing steering.
In the wipeout games, you can control your ship with one of several methods:
To these, Wipeout HD added another method: the sixaxis motion-sensing (or just "the sixaxis" for short) found in the PS3 controllers. It works by tilting the controller left and right for turning, and forward and backard to control the ship's pitch. Because of its lack of feedback and fixed reference point (unlike a racing wheel, for example) and extreme sensibility, most people considere it more like a curiosity than a real gaming option. But when I tried it, it replaced in my heart the d-pad I had been using since the original Wipeout almost instantly.
- The d-pad, the original steering method and still the most popular,
- The analog joystick, found on later revisions of the Playstation controller
- Non-standard controllers: the neGcon, racing wheels, etc.
But there's a quirk. Most joysticks have something called a dead zone: an area of motion around the center that registers as no motion. Because the springs that bring back a joystick to its centered position and the motion captors are almost always slightly off, this helps the joystick to come back to its center by turning that center into a small circle (or square) instead of a point. On the other hand, having a dead zone means that the "smoothness" of the joystick's motion is broken: it may be smooth on the left and on the right, but passing over the dead zone feels like a bump.
Now, despite the complete lack of dead zone in the PS3 controllers themselves, both the analog stick and the sixaxis do exhibit a dead zone in Wipeout HD. Frankly, why a control method that has no reference point (apart from gravity) needs a dead zone is beyond me. I guess the developpers didn't like the idea of a ship revolving slowly when the controller was put down on the table... But what it means in-game is that going straight becomes unnecessarily difficult!
To illustrate, imagine a very straight, very narrow catwalk (c.f. Anulpha Pass). Because you can't be perfectly aligned, you have to make tiny corrections to stay centered. But unfortunately, these tiny corrections are around the center of motion input, so the dead zone prevents you from doing this. You actually have to keep "finding" the edge of the dead zone for any motion to register. At higher speeds, it's just plain hell.
A few weeks ago, I found this tutorial that explains how to use a PS3 controller under Linux over bluetooth. And that's when it hit me: you can connect a controller to a Linux PC, so if you could connect a Linux PC (as a controller) to a PS3, you could then make a bluetooth input proxy. By that, I mean a program that takes it input from the controller, possibly modifies it and then sends it to the PS3. In particular, you could remove the dead zone.
Here's what you'll need:
- a Playstation 3,
- two controllers (original Sixaxis or Dualshock 3), already paired with the PS3,
- a PC under linux within 10 meters of the PS3,
- a bluetooth adapter in the PC (mine is a Broadcom USB dongle),
- the Bluez tools (hciconfig, hcidump),
- a build environment (gcc and libc-dev),
- the sixpair command,
- the "bdaddr" command from Bluez (which isn't available in Debian's "bluez" binary packages but is in the source),
- and last but not least, your own self-compiled kernel.
Yes, you need to patch your kernel.
I know, this sucks.
Right now, I have tested with two USB dongles: one based on a Broadcom (BCM) chip sold by Connectland, and one based on a Cambridge Silicon Radio (CSR) chip, sold by TnB. The Broadcom dongle works fine, but the CSR one makes my program segfault and is thus unusable for now.
About the kernel patch: in order to forward the bluetooth packets as faithfully as possible between the controller and the PS3, I decided to bind my proxy at a low level in the bluetooth stack. Because it is bound directly to an HCI socket, the only kernel module you need is "bluetooth", plus your driver (btusb for a USB dongle). All higher-level modules (l2cap, hidp, etc.) are unnecessary, and in fact shouldn't be loaded. But for some reason, all established connections will timeout within two seconds if not handed over to a higher level module, so my patch is to remove that timeout. I have yet to find a way to do it from userspace.
This hack works by fooling the controller and the PS3 into talking to the bluetooth adapter on the PC instead of directly to each other. The proxy (the program running on the PC) then forwards the packets from the controller to the PS3 and vice versa, optionally modifying them.
In order to make everybody talk to the PC, the devices need to be paired. Since on the PS3 pairing is done over USB, what you need to pair the controller to the PC is to plug it by USB and run sixpair. But to make the PS3 talk to the PC, you have to pair the second controller to the PS3, turn it off and then give this controller's address to your bluetooth adapter. This way, the PS3 will think it is talking to the second controller while in fact it will be talking to the PC.
Once you have patched your bluetooth kernel module, compiled bdaddr, sixpair and sixproxy and paired everybody, all you need to do is run sixproxy and turn the controller on. The controller will connect to sixproxy, which will in turn connect to the PS3 and everything should be up and running. You may have to do some tweaking for the dead zone size, but that's it.
First, download the patch to remove the HCI connection timeout and apply it to net/bluetooth/hci_conn.c in your kernel. If you need your bluetooth for other stuff, you may want to keep your old module and use the patched one just for this hack. After all, this timeout may serve some purpose, right?
If that timeout is not edited out, everything will seem to work right (the controller will get numbered and the PS3 will react to inputs) for exactly two seconds before everything stops.
You'll need to compile sixpair, bdaddr (if you don't already have it available in one of your bluez packages), and of course sixproxy.
To compile sixpair, you'll need the libusb-dev libraries, and for the other two, libbluetooth-dev. For bdadder, you'll also need oui.c and oui.h (also part of the bluez source tree).
The actual commands are:
- gcc -o sixpair -lusb sixpair.c
- gcc -o bdaddr -lbluetooth bdaddr.c oui.c
- gcc -o sixproxy -lbluetooth sixproxy.c
Setting up Bluetooth on the PC
First, make sure all bluetooth kernel modules are unloaded and that the bluetooth daemons are not running. Then load your bluetooth driver ("hci_usb" on 2.6.25 or "btusb" on 2.6.29 for me) with modprobe. It should pull the "bluetooth" module along with it and only this one. Make sure with lsmod.
Plug your adapter in and run "hciconfig" (with no arg) to see it. Then do "hciconfig hci0 reset pscan" to set it up. You may have to run "hciconfig hci0 reset" and "hciconfig hci0 pscan" separately. After that "hciconfig" will display the adapter's bdaddr (later refered to as the "PC" address). Write it down.
Each bluetooth device has a unique Bluetooth Device Address (bdaddr) looking like 01:02:03:04:05:06 (like an ethernet MAC address). You have four devices: controller A, controller B, PS3 and PC. You need to
- Assign the address of controller B to the PC,
- Tell controller A to connect to this address,
- Tell sixproxy to connect to the address of the PS3.
Select which controller you'll be using for the hack as controller A. From the other (controller B), we will only use the bdaddr. In order to find this address out, we first need to pair it to the PC. Plug controller B by USB to the PC, run sixpair and unplug it again. The bdaddr reported by sixpair as the old bluetooth master is that of the PS3, so write that down too. After that, run "hcidump -V" to sniff the air and turn controller B on with the PS button. You should at last see its bdaddr in the connection request. Write it down and stow controller B away, we're done with it. (hint: hold down the PS button for ten seconds to force the controller to shutdown).
We then change the PC's bdaddr to that of controller B, in order to make the PS3 talk to us. For that, you need the "bdaddr" command: run "./bdaddr -r <controller B's bdaddr>" and check with hciconfig.
Note: my two adapters react differently to "bdaddr": the BCM dongle is correctly setup with the above command, and will fall back to its original bdaddr upon dis/reconnection. The CSR dongle, on the other hand, needs to be reset manually for the change to be taken into account, and it will keep the new address even when unplugged. That's why you wrote the old one down earlier.
Now, all that's left is to pair controller A with the PC: just like before, plug it in, run sixpair and pull it out. All your devices are now paired and ready to talk to one another. Run "hciconfig hci0 reset" and "hciconfig hci0 pscan" again, just to be sure.
You can now switch your PS3 on. Once it's up and running, run the command "./sixproxy <bdaddr of the PS3>" on the PC: it should wait for the controller to connect to it. Switch your controller on with the PS button and check that sixproxy is then connecting to the PS3. If all goes according to plan, the controller should be assigned its number-led and... just work! Feel free to sniff around with "hcidump -V" if you're curious.
Once running smoothly, you should obviously play Wipeout HD. In order to tweak the dead-zone inverter function, you can change the following parameters on the fly with the keyboard:
... you even get feedback when doing it! That's some serious user interfacing, here. Localization is left as an exercise.
- "a" and "q": Sixaxis horizontal center
- "z" and "s": Sixaxis horizontal radius
- "e" and "d": Sixaxis vertical center
- "r" and "f": Sixaxis vertical radius
- "t" and "g": Analog stick horizontal center
- "y" and "h": Analog stick horizontal radius
- "u" and "j": Analog stick vertical center
- "i" and "k": Analog stick vertical radius