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

The Guncon 3 USB device

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.

Device Descriptor bLength 18 bDescriptorType DEVICE (0x01) bcdUSB 1.10 (0x0110) bDeviceClass Defined in Interface (0x00) bDeviceSubClass Defined in Interface (0x00) bDeviceProtocol Defined in Interface (0x00) bMaxPacketSize0 8 idVendor 0x0b9a idProduct 0x0800 bcdDevice 80.00 (0x8000) iManufacturer None (0) iProduct None (0) iSerialNumber None (0) bNumConfigurations 1

Configuration Descriptor Length 9 bDescriptorType CONFIGURATION (0x02) wTotalLength 32 bNumInterfaces 1 bConfigurationValue 1 iConfiguration None (0) bmAttributes.Reserved 0 bmAttributes.RemoteWakeup RemoteWakeup Not Supported (0b0) bmAttributes.SelfPowered Bus Powered (0b0) bMaxPower 100mA (0x32)

Interface Descriptor bLength 9 bDescriptorType INTERFACE (0x04) bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass Vendor Specific (0xff) bInterfaceSubClass Unknown (0x00) bInterfaceProtocol Unknown (0x00) iInterface None (0)

Endpoint Descriptor bLength 7 bDescriptorType ENDPOINT (0x05) bEndpointAddress 2 OUT (0b00000010) bmAttributes.TransferType Interrupt (0b11) wMaxPacketSize.PacketSize 8 wMaxPacketSize.Transactions One transaction per microframe if HS (0b00) Interval 16

Endpoint Descriptor bLength 7 bDescriptorType ENDPOINT (0x05) bEndpointAddress 2 IN (0b10000010) bmAttributes.TransferType Interrupt (0b11) wMaxPacketSize.PacketSize 15 wMaxPacketSize.Transactions One transaction per microframe if HS (0b00) Interval 4

Reversing

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, addi , clrldi and 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[4] = 0x10; in C, which could be compiled to

stw r4 , 4 ( r5 )

However GCC will assume that the address could overflow and will truncate it with clrldi .

addi r6 , r5 , 4 clrldi r6 , r6 , 32 stw r4 , 0 ( r6 )

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 defined 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 code I 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 ( 0x0b9a and 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 $0787A08 .

The 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 GUNCON_UNIT_t .

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_UNIT_t 00000000 field_0: .byte ? 00000001 field_1: .byte ? 00000002 field_2: .byte ? 00000002 00000003 state?: .byte ? 00000003 00000004 dev_id: .long ? 00000004 00000008 cpipe: .long ? 00000008 0000000C ipipe: .long ? 0000000C 00000010 opipe: .long ? 00000010 00000014 field_14: .long ? 00000018 send_buffer: .quad ? 00000018 00000020 sendbufferrelated: .quad ? 00000020 00000028 buffer: .long ? 00000028 0000002C buffer_offset: .long ? 00000030 .byte ? 00000031 .byte ? 00000032 .byte ? 00000033 .byte ? 00000034 .byte ? 00000035 .byte ? 00000036 field_36: .byte ? 00000037 GUNCONUNITt ends

The guncon_attached function is passed a GUNCON_UNIT_t struct that has been partially initialised with the dev_id and 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[32][16] . 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.

The 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.

int checksum ( uint8_t * data , uint8_t expected ) { uint8_t checksum , temp_sum ; temp_sum = ( ( data [ 14 ] ^ data [ 13 ] ) + data [ 12 ] + data [ 11 ] - data [ 10 ] - data [ 9 ] ) ^ data [ 8 ] ; checksum = ( ( ( data [ 7 ] ^ temp_sum ) - data [ 6 ] - data [ 5 ] ) ^ data [ 4 ] ) + data [ 3 ] + data [ 2 ] - data [ 1 ] ; if ( checksum != expected ) { GUNCON_DEBUG ( "checksum mismatch: %02x != %02x

" , checksum , expected ) ; return - 1 ; } return 0 ; }

This is the commented version of the IDA disassembly:

0787D40 checksum: 0787D40 n = r26 0787D40 addi r27 , r5 , GUNCON_UNIT_t.buffer 0787D44 li n, 0 0787D48 clrldi r28 , r27 , 32 0787D4C addi r5 , r5 , GUNCON_UNIT_t.send_buffer 0787D50 addi r3 , r28 , GUNCON_BUFFER.buffer_data 0787D54 clrldi r27 , r3 , 32 0787D58 lwz r7 , GUNCON_BUFFER.buffer_offset( r28 ) 0787D5C clrlslwi r30 , r7 , 27 , 4 0787D60 add r6 , r30 , r27 0787D64 clrldi r30 , r6 , 32 0787D68 ld r3 , guncon_recv_buffer.field_b( r30 ) 0787D6C ld r6 , guncon_recv_buffer( r30 ) 0787D70 srdi r31 , r3 , 8 0787D74 srdi r10 , r3 , 16 0787D78 srdi r9 , r3 , 24 0787D7C xor r12 , r31 , r10 0787D80 srdi r11 , r3 , 32 0787D84 add r4 , r12 , r9 0787D88 srdi r8 , r3 , 40 0787D8C add r0 , r4 , r11 0787D90 srdi r7 , r3 , 48 0787D94 subf r31 , r8 , r0 0787D98 srdi r9 , r3 , 56 0787D9C subf r12 , r7 , r31 0787DA0 srdi r11 , r6 , 8 0787DA4 xor r10 , r12 , r9 0787DA8 srdi r8 , r6 , 16 0787DAC xor r4 , r6 , r10 0787DB0 srdi r7 , r6 , 24 0787DB4 subf r3 , r11 , r4 0787DB8 srdi r31 , r6 , 32 0787DBC subf r0 , r8 , r3 0787DC0 srdi r9 , r6 , 40 0787DC4 xor r12 , r0 , r7 0787DC8 srdi r11 , r6 , 48 0787DCC add r10 , r12 , r31 0787DD0 add r4 , r10 , r9 0787DD4 subf r3 , r11 , r4 0787DD8 clrldi r10 , r3 , 56 0787DDC 0787DDC decode: 0787DDC slwi r4 , n, 3 0787DDC 0787DDC 0787DE0 clrldi r31 , r5 , 32 0787DE4 add r8 , r4 , r31 0787DE8 clrldi r11 , r8 , 32 0787DEC lbz r0 , 7 ( r11 ) 0787DF0 cmpw cr1 , r0 , r10 0787DF4 bne cr1 , invalid_data

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 send_buffer (aka 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 KEY_TABLE .

int Guncon 3_decode ( uint8_t * data , const unsigned char * key ) { int32_t x , y , key_index ; uint8_t bkey , keyr , key_offset , byte ; key_offset = ( ( ( ( ( key [ 1 ] ^ key [ 2 ] ) - key [ 3 ] - key [ 4 ] ) ^ key [ 5 ] ) + key [ 6 ] - key [ 7 ] ) ^ data [ 15 ] ) + ( unsigned char ) 0x26 ; key_index = 4 ; for ( x = 13 ; x >= 1 ; x -- ) { byte = data [ x ] ; for ( y = 0 ; y < 3 ; ++ y ) { key_offset -- ; bkey = KEY_TABLE [ key_offset + 0x41 ] ; keyr = key [ key_index ] ; if ( -- key_index == 0 ) key_index = 7 ; if ( ( bkey & 3 ) == 0 ) byte = ( byte - bkey ) - keyr ; else if ( ( bkey & 3 ) == 1 ) byte = ( ( byte + bkey ) + keyr ) ; else byte = ( ( byte ^ bkey ) ^ keyr ) ; } data [ x ] = byte ; } return 0 ; }

And now for the disassembly...

0787DDC decode: 0787DDC slwi r4 , n, 3 0787DDC 0787DDC 0787DE0 clrldi r31 , r5 , 32 0787DE4 add r8 , r4 , r31 0787DE8 clrldi r11 , r8 , 32 0787DEC lbz r0 , 7 ( r11 ) 0787DF0 cmpw cr1 , r0 , r10 0787DF4 bne cr1 , invalid_data 0787DF8 ld r6 , 0 ( r11 ) 0787DFC lbz r7 , 15 ( r30 ) 0787E00 srdi r0 , r6 , 48 0787E04 srdi r12 , r6 , 40 0787E08 srdi r10 , r6 , 32 0787E0C xor r9 , r0 , r12 0787E10 srdi r31 , r6 , 24 0787E14 subf r11 , r10 , r9 0787E18 srdi r5 , r6 , 16 0787E1C subf r4 , r31 , r11 0787E20 lwz r31 , guncon_table_p 0787E24 .drop r31 0787E24 srdi r8 , r6 , 8 0787E28 xor r3 , r4 , r5 0787E2C add r12 , r3 , r8 0787E30 li r3 , 5 0787E34 subf r0 , r6 , r12 0787E38 li r12 , 13 0787E38 0787E3C xor r10 , r0 , r7 0787E40 rotrdi r7 , r6 , 16 0787E44 addi r9 , r10 , 38 0787E48 clrldi r11 , r9 , 56 0787E4C add r5 , r11 , r31 0787E50 clrldi r6 , r5 , 32 0787E54 b decode_start 0787E58 0787E58 0787E58 decode_write: 0787E58 byte = r10 0787E58 addi r12 , r12 , - 1 0787E5C clrldi r5 , r31 , 32 0787E60 cmpdi cr1 , r12 , 0 0787E64 stb byte, 0 ( r5 ) 0787E68 beq cr1 , decode_end 0787E6C 0787E6C decode_start: 0787E6C add r31 , r12 , r30 0787E70 .drop r31 0787E70 li r5 , 4 0787E74 clrldi r8 , r31 , 32 0787E78 lbz byte, 0 ( r8 ) 0787E7C 0787E7C inner_loop: 0787E7C 0787E7C addi r5 , r5 , - 1 0787E80 addi r0 , r6 , - 1 0787E80 0787E84 cmpdi cr7 , r5 , 0 0787E88 li r4 , 7 0787E8C li r11 , 48 0787E90 beq cr7 , decode_write 0787E94 clrldi r6 , r0 , 32 0787E98 lbz r9 , 0 ( r6 ) 0787E9C addi r0 , r3 , - 1 0787EA0 cmpdi cr6 , r0 , 0 0787EA4 mtspr CTR, r0 0787EA8 beq cr6 , change_rbits_skip 0787EAC li r4 , 0 0787EB0 li r11 , 56 0787EB4 0787EB4 change_rbits_skip: 0787EB4 key = r7 0787EB4 clrldi r3 , r9 , 62 0787EB8 rldcl key, key, r11 , 0 0787EBC cmpdi r3 , 0 0787EC0 mfspr r11 , CTR 0787EC4 cmpdi cr6 , r3 , 1 0787EC8 bkey = r9 0787EC8 subf r8 , bkey, byte 0787ECC add r3 , r4 , r11 0787ED0 beq key_0 0787ED4 xor r0 , byte, bkey 0787ED8 beq cr6 , key_1 0787EDC xor byte, r0 , key 0787EE0 byte = r10 0787EE0 b inner_loop 0787EE4 0787EE4 0787EE4 invalid_data: 0787EE4 addi n, n, 1 0787EE8 cmpdi cr7 , n, 2 0787EEC bne cr7 , decode 0787EEC 0787EEC 0787F5C 0787F5C key_0: 0787F5C key = r7 0787F5C byte = r10 0787F5C bkey = r9 0787F5C subf byte, key, r8 0787F60 b inner_loop 0787F64 0787F64 0787F64 key_1: 0787F64 add r4 , byte, bkey 0787F68 add byte, r4 , key 0787F6C b inner_loop 0787F70 0787F70 0787F70 decode_end: 0787F70 lwz r11 , GUNCON_BUFFER.buffer_offset( r28 ) 0787F74 cmpdi cr6 , r26 , 0 0787F78 addi r12 , r11 , 1 0787F7C clrlslwi r0 , r11 , 27 , 4 0787F80 clrlslwi r10 , r12 , 27 , 4 0787F84 add r8 , r0 , r27 0787F88 add r9 , r10 , r27 0787F8C clrldi r31 , r8 , 32 0787F90 lbz r7 , 0 ( r31 ) 0787F94 clrldi r26 , r12 , 32 0787F98 clrldi r30 , r9 , 32 0787F9C addi r6 , r7 , 1 0787FA0 stb r6 , 0 ( r30 ) 0787FA4 stw r26 , GUNCON_BUFFER.buffer_offset( r28 ) 0787FA8 bne cr6 , new_send_buffer

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.

Driver for Guncon 3

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.

BTN_TRIGGER = (data[1] & 0x20)); BTN_A1 = (data[0] & 0x04)); BTN_A2 = (data[0] & 0x02)); BTN_B1 = (data[1] & 0x04)); BTN_B2 = (data[1] & 0x02)); BTN_C1 = (data[1] & 0x80)); BTN_C2 = (data[0] & 0x08)); BTN_JOY_A = (data[2] & 0x80)); BTN_JOY_B = (data[2] & 0x40));

The Joystick axes 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.

JOY_A_X = ( int8_t ) data [ 11 ] JOY_A_Y = ( int8_t ) data [ 12 ] ; JOY_B_X = ( int8_t ) data [ 9 ] ; JOY_B_Y = ( int8_t ) data [ 10 ] ;

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.

AIM_X = ( ( short ) data [ 4 ] << 8 ) | ( short ) data [ 3 ] ; AIM_Y = ( ( short ) data [ 6 ] << 8 ) | ( short ) data [ 5 ] ; AIM_Z = ( data [ 8 ] << 8 ) + data [ 7 ]

Output from `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 xpad and 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.