The Guncon 3 is a light gun for the Play Station 3 that was bundled with Time Crisis 4. It is a USB device with two joysticks, 9 buttons and an IR LED based pointer (similar to the Wiimote). It is only compatible with the PS3 games Time Crisis 4 (TC4), Time Crisis 4: Razing Storm and Deadstorm Pirates. While it is only supported by a small number of games I found it to be a very good and accurate light gun, better than my AimTrak and with more buttons. The only problem is the lack of support for any other device - there are no drivers for Windows, Linux, etc. I hoped to fix this problem by creating a Linux kernel module to support the Guncon 3.
Table of Contents
When plugged in to a computer the Guncon 3 appears as a USB Hub with an attached device with the following device descriptor and configuration descriptor.
|bDeviceClass||Defined in Interface (0x00)|
|bDeviceSubClass||Defined in Interface (0x00)|
|bDeviceProtocol||Defined in Interface (0x00)|
|bmAttributes.RemoteWakeup||RemoteWakeup Not Supported (0b0)|
|bmAttributes.SelfPowered||Bus Powered (0b0)|
|bInterfaceClass||Vendor Specific (0xff)|
|bEndpointAddress||2 OUT (0b00000010)|
|wMaxPacketSize.Transactions||One transaction per microframe if HS (0b00)|
|bEndpointAddress||2 IN (0b10000010)|
|wMaxPacketSize.Transactions||One transaction per microframe if HS (0b00)|
I was hoping that the USB config and endpoint descriptors would provide useful information, however there isn’t much apart from a few clues. We can see that there are 2 Interrupt endpoints on the USB device, one input and one output. If you are not familiar with the specifics of the USB protocol then all you really need to know is that USB devices can have endpoints of different types. The endpoints are unidirectional and are used to send data to and from the device. The interrupt endpoints must be polled by the driver and always send/receive a fixed amount of data. So this device has input (relative to the host) endpoint that will send a 15 byte packet, and an output endpoint that will receive 8 an 8 byte packet.
Connecting the device to my PC and polling the input endpoint did not return any data, so at that point I tried to send some random data to the output endpoint (I used a Python script with pyusb to send and receive data). After sending 8 bytes to the Guncon data started appearing on the input endpoint (yay!). It seems that the Guncon requires a setup packet to initialise, however the data that gets sent back from Guncon appears to be scrambled in some way (boo!). The data packets being sent from the Guncon appear to repeat, for example there will be the same pair of packets sent many many times in a row:
56 C8 30 71 97 4B 3F F4 27 46 F1 40 98 EF F4 BE B4 CC 4C 53 9B D8 EE 5B 72 FC 7D 6C E7 F3 56 C7 30 71 97 4B 3F F4 27 46 F1 40 7D 29 F4 BE B4 CC 4C 53 9B D8 EE 5B 72 FC 7D 6C E7 F3 56 C7 30 71 97 4B 3F F4 27 46 F1 40 7D 29 F4 BE B4 CC 4C 53 9B D8 EE 5B 72 FC 7D 6C E7 F3 56 C7 30 71 97 4B 3F F4 27 46 F1 40 7D 29 F4 BE B4 CC 4C 53 9B D8 EE 5B 72 FC 7D 6C E7 F3 ...
When you move a joystick or press a button you can see all of bytes in the output data changing and then changing back when the button is released. This suggests that the data is scrambled in some way, possibly encrypted. I determined that I could go no further with only the data from a PC, so I got a Play Station 3 and Time Crisis 4.
I used a Beagle USB 12 protocol analyser and hooked the Guncon up to the PS3 and started Time Crisis 4 (TC4), before starting TC4 the PS3 reports the Guncon as an unknown USB device - the game must provide a driver, because as soon as the game starts you can see the USB enumeration and setup packets, along with the magic initialisation packet sent to the output endpoint. In all cases I have observed the initialisation packet starts with
0x01, in this case the packet was
01 12 6F 32 24 60 17 21. I then observed the same behaviour as when it was connected to the PC, apparently scrambled data packets.
It did not seem likely that I would be able to decipher the scrambling based only on the observations of the data packets, so I took the next logic step - disassembly. You need to get a copy of the decrypted .elf file from your copy of TC4, and I cannot explain how to do this - there are a number of guides available online for decrypting EBOOT.BIN. Once I had the TC4 elf file I could load it up in IDA Pro 64Bit, I am using 6.8 (which was the latest version when I was doing this, 6.9 is out now…) An extra plug-in called PowerPC AltiVec Extension is required to decode the extra AltiVec instructions used in the CellBE processor.
I used a IDA script to analyse the TC4 elf (analyze_self.idc from kakaroto/ps3ida which will find the TOC address (which you can set in IDA under Options->General->Analyse->Processor specific analysis options) and it will find the function imports and exports (which is very useful for understanding the USB driver functions).
Note: The Cell Processor is a 64-bit RISC PowerPC with 32x64bit general purpose registers. TC4 was compiled with GCC 4.1.1, this is useful to note because most of the time when pointers are referenced there will be some pretty dubious ASM generated,
stw will be used instead of
stwu and relative addressing. When a pointer to a buffer is accessed,
uint8_t *buffer; and you wish to write to an offset you may write something like:
buffer = 0x10; in C, which could be compiled to
# r4=0x10, r5=buffer stw r4, 4(r5) # *(buffer+4) = 0x10;
However GCC will assume that the address could overflow and will truncate it with
#r4=0x10, r5=buffer addi r6, r5, 4 # temp = buffer + 4; clrldi r6, r6, 32 # ensure the address is with-in 32 bits stw r4, 0(r6) # *(buffer+4) = 0x10;
This pattern appears quite a bit in the code we will look at, and it makes everything look a bit messy.
The way the USB driver seems to work with the PS3 is that a few callbacks are define for probing, attaching and detaching devices - probe is called when a new device is plugged in and tests to see if the new device is supported, attach and detach are called to setup or teardown a supported device. Most of the other USB functions take a callback argument and pass around a pointer to a struct that describe the device.
To find the various functions in the TC4 I code guessed that there might be some debugging messages still in the code (they were left there to help us…), searching in the Strings Window (View->Open Subviews->Strings) for
"guncon" reveals a fair number of references - had there not been any strings there is still the Vendor and Product ID (
0x0800), which appear in the probe function. The interesting strings are
Guncon 3: probe ... and
Guncon 3: attach_done..., the
attach_done string appears in a subroutine that I called
guncon_attached and starts at
guncon_attached function takes 3 arguments,
int32_t x, int32_t y, GUNCON_UNIT_t* unit - I do not understand exactly what the first 2 arguments are but the last argument is a pointer to a struct that describes the connection to the Guncon 3 which I called
This is the current state of the reversed
GUNCON_UNIT_t, not all of the fields are complete but it was sufficient to work out the communication functions.
guncon_attached function is passed a
GUNCON_UNIT_t struct that has been partially initialised with the
pipes set up, the function clears the
buffer and sets the
send_buffer/_related with a key of some sorts, I don’t fully understand the method used to generate the key but the buffer
send_buffer_related appears to be derived from
send_buffer (hence the name). At the end of the function the 8 byte
send_buffer_related is sent to the Guncon using the InterruptTransfer function.
The InterruptTransfer function sets up a callback to a subroutine at
$07880E4 that I called
guncon_recv. The callback has the same arguments as all the USB callbacks (
int32_t x, int32_t y, GUNCON_UNIT_t*), and it simply sets up the output polling. Another InterruptTransfer call is made with a pointer to the receive buffer and a callback to a subroutine at
$0787CC4 which I called
guncon_DecodeGunData - this is where the really interesting bits happen.
The receive buffer is a ring buffer that appears to defined as
uint8_t buffer. The
buffer_offset points to the current offset in the buffer for the data to be written, the Guncon only sends 15 bytes but the buffer is 16 byte aligned for speed reasons - each of the buffer elements can be stored in two registers which speeds up processing.
guncon_DecodeGunData function has 4 parts, error testing/housekeeping, check summing, decrypting and setting up the next interrupt poll. The housekeeping and set up for the next call are not very interesting and I won’t cover them. However, the checksum and decrypt are quite interesting. The checksum code starts at
$787D40 and I have written a rough C port of it.
This is the commented version of the IDA disassembly:
0000000000787D40 checksum: # CODE XREF: guncon_DecodeGunData+2Cj 0000000000787D40 n = r26 # this block appears to calculate a checksum of the values returned 0000000000787D40 addi r27, r5, GUNCON_UNIT_t.buffer 0000000000787D44 li n, 0 # r26 = 0; 0000000000787D48 clrldi r28, r27, 32 0000000000787D4C addi r5, r5, GUNCON_UNIT_t.send_buffer 0000000000787D50 addi r3, r28, GUNCON_BUFFER.buffer_data 0000000000787D54 clrldi r27, r3, 32 # buffer_base 0000000000787D58 lwz r7, GUNCON_BUFFER.buffer_offset(r28) # buffer_offset 0000000000787D5C clrlslwi r30, r7, 27,4 # r30 = (r7 * 16) % (31 << 4); 0000000000787D60 add r6, r30, r27 0000000000787D64 clrldi r30, r6, 32 # data = buffer[buffer_offset] 0000000000787D68 ld r3, guncon_recv_buffer.field_b(r30) # data 0000000000787D6C ld r6, guncon_recv_buffer(r30) # data 0000000000787D70 srdi r31, r3, 8 # b >> 8 0000000000787D74 srdi r10, r3, 16 # b >> 16 0000000000787D78 srdi r9, r3, 24 # b >> 24 0000000000787D7C xor r12, r31, r10 # (b >> 8) ^ (b >> 16) 0000000000787D80 srdi r11, r3, 32 # b >> 32 0000000000787D84 add r4, r12, r9 # ((b >> 8) ^ (b >> 16)) + (b >> 24) 0000000000787D88 srdi r8, r3, 40 # b >> 40 0000000000787D8C add r0, r4, r11 # ((b >> 8) ^ (b >> 16)) + (b >> 24) + (b >> 32) 0000000000787D90 srdi r7, r3, 48 # b >> 48 0000000000787D94 subf r31, r8, r0 # ((b >> 8) ^ (b >> 16)) + (b >> 24) + (b >> 32) - (b >> 40) 0000000000787D98 srdi r9, r3, 56 # b >> 56 0000000000787D9C subf r12, r7, r31 # ((b >> 8) ^ (b >> 16)) + (b >> 24) + (b >> 32) - (b >> 40) - (b >> 48) 0000000000787DA0 srdi r11, r6, 8 # a >> 8 0000000000787DA4 xor r10, r12, r9 # (((b >> 8) ^ (b >> 16)) + (b >> 24) + (b >> 32) - (b >> 40) - (b >> 48)) ^ (b >> 56) 0000000000787DA8 srdi r8, r6, 16 # a >> 16 0000000000787DAC xor r4, r6, r10 # a ^ b_sum 0000000000787DB0 srdi r7, r6, 24 # a >> 24 0000000000787DB4 subf r3, r11, r4 # (a ^ b_sum) - (a >> 8) 0000000000787DB8 srdi r31, r6, 32 # a >> 32 0000000000787DBC subf r0, r8, r3 # (a ^ b_sum) - (a >> 8) - (a >> 16) 0000000000787DC0 srdi r9, r6, 40 # a >> 40 0000000000787DC4 xor r12, r0, r7 # ((a ^ b_sum) - (a >> 8) - (a >> 16)) ^ (a >> 24) 0000000000787DC8 srdi r11, r6, 48 # a >> 48 0000000000787DCC add r10, r12, r31 # a ^ b_sum - (a >> 8) - (a >> 16) ^ (a >> 24) + (a >> 32) 0000000000787DD0 add r4, r10, r9 # a ^ b_sum - (a >> 8) - (a >> 16) ^ (a >> 24) + (a >> 32) + (a >> 40) 0000000000787DD4 subf r3, r11, r4 # a ^ b_sum - (a >> 8) - (a >> 16) ^ (a >> 24) + (a >> 32) + (a >> 40) - (a >> 48) 0000000000787DD8 clrldi r10, r3, 56 # r3 & 0xff 0000000000787DDC 0000000000787DDC decode: # CODE XREF: guncon_DecodeGunData+228j 0000000000787DDC slwi r4, n, 3 # r4 = r26 * 8; 0000000000787DDC # r26 is incremented later, the first time it's 0 0000000000787DDC # this loop wil run twice, probably decoding each quad of the data from the gun 0000000000787DE0 clrldi r31, r5, 32 # r31 = send_buffer 0000000000787DE4 add r8, r4, r31 # send_buffer[n] 0000000000787DE8 clrldi r11, r8, 32 0000000000787DEC lbz r0, 7(r11) # r11 = send_buffer[n] 0000000000787DF0 cmpw cr1, r0, r10 # compare checksum 0000000000787DF4 bne cr1, invalid_data # checksum mismatch
You can see that the see the address masking I mentioned before a few times in this snippet. The checksum is calculated and compared the last byte in
key), if the checksum does not match then there is some retry logic and if that fails a new
send_buffer/key is generated and sent to the Guncon. I don’t know if this a standard checksum algorithm or if it is something Namco came up with? The next part of the function deals with the decryption of the data from the Guncon.
There is a decryption table at
$1009AA90 that is used to decrypt the data sent from the Guncon - this is probably the main reason no other games or drivers can use this lightgun (they don’t have the decryption algorithm), which is a really big shame because it’s an excellent light gun! Again I will show my recoded C version and then the relevant disassembly.
The key is used in combination with the last byte of the data to compute an offset in the
KEY_TABLE that is used as the starting point to decrypt the rest of the data. The 15th byte in the data is padding used so that the checksum will work out, the 1st of the buffer is not part of the data so this leaves 13 bytes for joystick state data. Only bytes 13 to 1 can be decoded, and only those are decoded by the function. The operation performed on each byte is determined by the byte in
KEY_TABLE, it is either added, subtracted, or XORed with a key byte and the byte from
And now for the disassembly…
0000000000787DDC decode: # CODE XREF: guncon_DecodeGunData+228j 0000000000787DDC slwi r4, n, 3 # r4 = r26 * 8; 0000000000787DDC # r26 is incremented later, the first time it's 0 0000000000787DDC # this loop wil run twice, probably decoding each quad of the data from the gun 0000000000787DE0 clrldi r31, r5, 32 # r31 = send_buffer 0000000000787DE4 add r8, r4, r31 # send_buffer[n] 0000000000787DE8 clrldi r11, r8, 32 0000000000787DEC lbz r0, 7(r11) # r11 = send_buffer[n] 0000000000787DF0 cmpw cr1, r0, r10 # compare checksum 0000000000787DF4 bne cr1, invalid_data # checksum mismatch 0000000000787DF8 ld r6, 0(r11) # send_buffer as quad 0000000000787DFC lbz r7, 15(r30) # last byte of buffer 0000000000787E00 srdi r0, r6, 48 0000000000787E04 srdi r12, r6, 40 0000000000787E08 srdi r10, r6, 32 0000000000787E0C xor r9, r0, r12 0000000000787E10 srdi r31, r6, 24 0000000000787E14 subf r11, r10, r9 0000000000787E18 srdi r5, r6, 16 0000000000787E1C subf r4, r31, r11 0000000000787E20 lwz r31, guncon_table_p # guncon_table+0x41 0000000000787E24 .drop r31 # 0x1009AAD11009A910 0000000000787E24 srdi r8, r6, 8 0000000000787E28 xor r3, r4, r5 0000000000787E2C add r12, r3, r8 0000000000787E30 li r3, 5 0000000000787E34 subf r0, r6, r12 0000000000787E38 li r12, 13 # loop_counter = 13 // there are 15 bytes in the buffer, the last byte is a checksum and 0000000000787E38 # // the first byte is a counter and the 14th byte is ignored... 0000000000787E3C xor r10, r0, r7 0000000000787E40 rotrdi r7, r6, 16 # rotate right 16 0000000000787E44 addi r9, r10, 38 # key_hash + 0x26 (0x26 is the minimum offset if key_hash is 0) 0000000000787E48 clrldi r11, r9, 56 # & 0xff // this has a wrap around 0000000000787E4C add r5, r11, r31 # key_table_[key_hash] 0000000000787E50 clrldi r6, r5, 32 # restrict to 32bit address space 0000000000787E54 b decode_start # buffer[r12] 0000000000787E58 # --------------------------------------------------------------------------- 0000000000787E58 0000000000787E58 decode_write: # CODE XREF: guncon_DecodeGunData+1CCj 0000000000787E58 byte = r10 # r12-- 0000000000787E58 addi r12, r12, -1 0000000000787E5C clrldi r5, r31, 32 0000000000787E60 cmpdi cr1, r12, 0 # if r12 == 0 0000000000787E64 stb byte, 0(r5) # buffer[r12] = decoded 0000000000787E68 beq cr1, decode_end 0000000000787E6C 0000000000787E6C decode_start: # CODE XREF: guncon_DecodeGunData+190j 0000000000787E6C add r31, r12, r30 # buffer[r12] 0000000000787E70 .drop r31 0000000000787E70 li r5, 4 0000000000787E74 clrldi r8, r31, 32 0000000000787E78 lbz byte, 0(r8) # buffer[i] 0000000000787E7C 0000000000787E7C inner_loop: # CODE XREF: guncon_DecodeGunData+21Cj 0000000000787E7C # guncon_DecodeGunData+29Cj ... 0000000000787E7C addi r5, r5, -1 # r5-- // first loop r5 = 3 0000000000787E80 addi r0, r6, -1 # key_ptr-- // KEY_TABLE offset is decremented first, before the loop test it made 0000000000787E80 # for (int y = 4; y > 0; --y, --key_ptr) 0000000000787E84 cmpdi cr7, r5, 0 0000000000787E88 li r4, 7 0000000000787E8C li r11, 48 0000000000787E90 beq cr7, decode_write # if r5 == 0 0000000000787E94 clrldi r6, r0, 32 0000000000787E98 lbz r9, 0(r6) # *key_ptr; // KEY_TABLE[key_offset] 0000000000787E9C addi r0, r3, -1 0000000000787EA0 cmpdi cr6, r0, 0 0000000000787EA4 mtspr CTR, r0 0000000000787EA8 beq cr6, change_rbits_skip # if r3 == 0 0000000000787EAC li r4, 0 0000000000787EB0 li r11, 56 0000000000787EB4 0000000000787EB4 change_rbits_skip: # CODE XREF: guncon_DecodeGunData+1E4j 0000000000787EB4 key = r7 # clear top 62 bits... 0000000000787EB4 clrldi r3, r9, 62 0000000000787EB8 rldcl key, key, r11,0 # rotate right (16 or 8 bits) 0000000000787EBC cmpdi r3, 0 0000000000787EC0 mfspr r11, CTR 0000000000787EC4 cmpdi cr6, r3, 1 0000000000787EC8 bkey = r9 0000000000787EC8 subf r8, bkey, byte # r8 = byte - bkey 0000000000787ECC add r3, r4, r11 0000000000787ED0 beq key_0 # byte = r8 - key 0000000000787ED4 xor r0, byte, bkey # r0 = byte ^ bkey 0000000000787ED8 beq cr6, key_1 # byte = byte + bkey + key 0000000000787EDC xor byte, r0, key # byte = (byte ^ bkey) ^ key 0000000000787EE0 byte = r10 0000000000787EE0 b inner_loop # r5-- // first loop r5 = 3 0000000000787EE4 # --------------------------------------------------------------------------- 0000000000787EE4 0000000000787EE4 invalid_data: # CODE XREF: guncon_DecodeGunData+130j 0000000000787EE4 addi n, n, 1 # Resend the init packet when 'n' reaches 2 0000000000787EE8 cmpdi cr7, n, 2 0000000000787EEC bne cr7, decode # r4 = r26 * 8; 0000000000787EEC # r26 is incremented later, the first time it's 0 0000000000787EEC # this loop wil run twice, probably decoding each quad of the data from the gun # TRUNCATED FOR BREVITY # This section retries the interrupt request # --------------------------------------------------------------------------- 0000000000787F5C 0000000000787F5C key_0: # CODE XREF: guncon_DecodeGunData+20Cj 0000000000787F5C key = r7 # byte = r8 - key 0000000000787F5C byte = r10 0000000000787F5C bkey = r9 0000000000787F5C subf byte, key, r8 0000000000787F60 b inner_loop # r5-- // first loop r5 = 3 0000000000787F64 # --------------------------------------------------------------------------- 0000000000787F64 0000000000787F64 key_1: # CODE XREF: guncon_DecodeGunData+214j 0000000000787F64 add r4, byte, bkey # byte = byte + bkey + key 0000000000787F68 add byte, r4, key 0000000000787F6C b inner_loop # r5-- // first loop r5 = 3 0000000000787F70 # --------------------------------------------------------------------------- 0000000000787F70 0000000000787F70 decode_end: # CODE XREF: guncon_DecodeGunData+1A4j 0000000000787F70 lwz r11, GUNCON_BUFFER.buffer_offset(r28) 0000000000787F74 cmpdi cr6, r26, 0 # r26 is the outer loop counter 0000000000787F78 addi r12, r11, 1 0000000000787F7C clrlslwi r0, r11, 27,4 0000000000787F80 clrlslwi r10, r12, 27,4 0000000000787F84 add r8, r0, r27 0000000000787F88 add r9, r10, r27 0000000000787F8C clrldi r31, r8, 32 # buffer[i % 31] 0000000000787F90 lbz r7, 0(r31) 0000000000787F94 clrldi r26, r12, 32 0000000000787F98 clrldi r30, r9, 32 # buffer[i+1 % 31] 0000000000787F9C addi r6, r7, 1 0000000000787FA0 stb r6, 0(r30) 0000000000787FA4 stw r26, GUNCON_BUFFER.buffer_offset(r28) # buffer_offset++; 0000000000787FA8 bne cr6, new_send_buffer # if the first decode failed, then resend the sendbuffer # Code after here sends the request for next interrupt request
Deciphering the code for the PS3 USB driver took me a little while, but most of the time was spent looking up PowerPC instructions like
clrlslwi. This was the first time that I have looked at PowerPC ASM, but it isn’t too dissimilar to some other things I have worked on so picking it up wasn’t too tricky. There may be some mistakes in the comments for ASM as most of the comments are still from the first pass I did in IDA. However, the C versions of the functions have been tested and do work.
Source code available: beardypig/guncon3 (provided as-is and possibly not in a full usable state)
Once the decryption algorithm was reversed I could decrypt all the information coming from the Guncon. To write the driver I had to look at the decoded data coming from the Guncon and work out what was what, which is quite easily done by pressing the buttons one at a time :) By watching the data when the buttons are pressed in turn we can easily see which bit is which for the buttons.
The Joystick axis are also easy to work out, moving the joysticks one axis at a time shows that there is only 1 byte the changes. The unsigned bytes center to 127 and go from 0 to 255, this means that each axis for the joystick is a signed char.
The last piece of the puzzle is the aiming, pointing the gun around at the IR LEDs sees the remaining 6 bytes changing. With some trial and error I found there are 3 shorts, two of which correspond roughly to left/right and up/down movement of the gun and the other corresponds roughly to the distance from the sensors to the guns. I say roughly because there are maximum and minimum values, and it depends on the size of your TV and how far apart the sensors are. There is a need for some calibration as the IR LEDs can be placed at different distances, the extra value sent by the Guncon is probably used to adjust the other values and as a reference for the calibration.
jstest-gtk with the Guncon 3
I really wanted to use this light gun in MAME for all the old arcade shooting games, so I wrote a kernel module so that it will work as a joystick. I haven’t really done much kernel programming before and nothing to do with the
joydev module, I based my module heavily on other already existing modules including the
guncon2 modules. There are known bugs and omissions, you may have noticed there was not information about how the key was generated - I am currently using a fixed key that works with the two Guncon 3 guns that I have.
There also appears to be a strange bug where 100s of button presses being generated when the joysticks are held in a specific position, I have yet to track it down. It doesn’t appear to be generated by the Guncon, as the raw data remains stable and I believe there is a bug in my kernel module code :)
There is also no way to calibrate the aiming for the gun and it is not perfect, but should work OK if you are able to position the IR LEDs as far apart as they go and use the gun at a 2-3m distance from the LEDs. I plan continue updating the kernel module for the Guncon 3 and I hope that other people will be interested enough to help out :)
This information is provided for educational and interoperability purposes.