From e929ab60588c1e7af89dfe270a7fe197c63d6e93 Mon Sep 17 00:00:00 2001 From: Kyle Lemons Date: Fri, 22 Nov 2013 00:25:46 -0800 Subject: [PATCH] Basic decoding of XBox One controller --- xbox/.gitignore | 1 + xbox/XBOXONE | 155 ++++++++++++++++++++++++++++++++++++++++ xbox/xbox.go | 184 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 325 insertions(+), 15 deletions(-) create mode 100644 xbox/.gitignore create mode 100644 xbox/XBOXONE diff --git a/xbox/.gitignore b/xbox/.gitignore new file mode 100644 index 0000000..bd6bac6 --- /dev/null +++ b/xbox/.gitignore @@ -0,0 +1 @@ +xbox diff --git a/xbox/XBOXONE b/xbox/XBOXONE new file mode 100644 index 0000000..485dbbb --- /dev/null +++ b/xbox/XBOXONE @@ -0,0 +1,155 @@ +2013/11/21 - Got my xbox one controller. Woo! Hack hack hack. + +Here's what we have so far: + +When the xbox one controller turns on, it gives you something like: +02 20 11 1c 7e ed 8a 56 0f 45 00 00 5e 04 d1 02 01 00 01 00 17 01 02 00 01 00 01 00 01 00 01 00 + ^ + '-- This byte seems to count upward every time the read returns, roughly twice a second + +Should you try to write +04 20 +The behavior will change (this is what put us onto this line of investigation). + +If you subsequently write any of these: +04 20 ?? 1c 7e ed 8a 56 0f 45 00 00 5e 04 d1 02 01 00 01 00 17 01 02 00 01 00 01 00 01 00 01 00 +04 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +04 20 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF + +The light will turn on (YAY!) and then you can start reading again. + +You will read something like the following a few times: +04 f0 01 3a ba 01 10 00 01 00 00 00 00 00 00 00 00 00 00 00 ba 00 7b 00 16 00 1f 00 20 00 27 00 2d 00 4a 00 00 00 00 00 00 00 00 00 02 01 00 00 00 01 00 01 00 00 06 01 02 03 04 06 07 05 01 05 +04 f0 02 3a ba 01 10 00 01 00 00 00 00 00 00 00 00 00 00 00 ba 00 7b 00 16 00 1f 00 20 00 27 00 2d 00 4a 00 00 00 00 00 00 00 00 00 02 01 00 00 00 01 00 01 00 00 06 01 02 03 04 06 07 05 01 04 + +Whenever you hit the xbox button, you will read these: +07 20 01 02 01 5b +07 20 02 02 00 5b + +(Note that at various points, the counter byte (the third byte) seems to go back to 1) + +1..16 + 0x20 +2013/11/21 23:20:05 read 32 bytes: 02 20 09 1c 7e ed 8a 56 0f 45 00 00 5e 04 d1 02 01 00 01 00 17 01 02 00 01 00 01 00 01 00 01 00 [err: ] +2013/11/21 23:20:05 read 8 bytes: 03 20 01 04 80 00 00 00 [err: ] +2013/11/21 23:20:05 read 18 bytes: 20 00 01 0e 00 00 00 00 00 00 97 0a 6f 01 b6 06 37 f8 [err: ] + +Actually, the magic number appears to be [05 20]. +It seems like [04 20] asks for some calibration data. + +Here is an edited session log of me hitting buttons: + +2013/11/21 23:33:53 Found Microsoft Xbox One controller +2013/11/21 23:33:53 sent 2 bytes: 05 20 [err: ] +2013/11/21 23:33:53 read 8 bytes: 03 20 01 04 80 00 00 00 [err: ] + +a +2013/11/21 23:34:03 read 18 bytes: 20 00 06 0e 10 00 00 00 00 00 73 0b 0f 01 b0 05 7c f8 [err: ] +2013/11/21 23:34:03 read 18 bytes: 20 00 07 0e 00 00 00 00 00 00 73 0b 0f 01 b0 05 7c f8 [err: ] + +b +2013/11/21 23:35:23 read 18 bytes: 20 00 22 0e 40 00 00 00 00 00 73 0b 0f 01 b0 05 02 fc [err: ] +2013/11/21 23:35:24 read 18 bytes: 20 00 23 0e 00 00 00 00 00 00 73 0b 0f 01 b0 05 02 fc [err: ] + +y +2013/11/21 23:35:27 read 18 bytes: 20 00 27 0e 00 00 00 00 00 00 73 0b 0f 01 b0 05 02 fc [err: ] +2013/11/21 23:35:27 read 18 bytes: 20 00 28 0e 80 00 00 00 00 00 73 0b 0f 01 b0 05 02 fc [err: ] + +down +2013/11/21 23:35:35 read 18 bytes: 20 00 44 0e 00 02 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:35:35 read 18 bytes: 20 00 45 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +up +2013/11/21 23:35:40 read 18 bytes: 20 00 46 0e 00 01 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:35:40 read 18 bytes: 20 00 47 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +left +2013/11/21 23:35:45 read 18 bytes: 20 00 48 0e 00 04 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:35:45 read 18 bytes: 20 00 49 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +right +2013/11/21 23:35:50 read 18 bytes: 20 00 4a 0e 00 08 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:35:50 read 18 bytes: 20 00 4b 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +2013/11/21 23:35:53 read 8 bytes: 03 20 07 04 80 01 00 00 [err: ] + +share +2013/11/21 23:36:01 read 18 bytes: 20 00 50 0e 08 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:01 read 18 bytes: 20 00 51 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +menu +2013/11/21 23:36:08 read 18 bytes: 20 00 56 0e 04 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:08 read 18 bytes: 20 00 57 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +sync +2013/11/22 00:02:00 read 18 bytes: 20 00 0a 0e 01 00 00 00 00 00 50 0e cf fc 4e 05 37 f8 [err: ] +2013/11/22 00:02:00 read 18 bytes: 20 00 0b 0e 00 00 00 00 00 00 50 0e cf fc 4e 05 37 f8 [err: ] + +lb +2013/11/21 23:36:14 read 18 bytes: 20 00 5a 0e 00 10 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:14 read 18 bytes: 20 00 5b 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +rb +2013/11/21 23:36:17 read 18 bytes: 20 00 5e 0e 00 20 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:17 read 18 bytes: 20 00 5f 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +lt +2013/11/21 23:36:25 read 18 bytes: 20 00 64 0e 00 00 a4 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 65 0e 00 00 06 02 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 66 0e 00 00 f4 03 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 67 0e 00 00 ff 03 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 68 0e 00 00 1a 02 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 69 0e 00 00 22 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 6a 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 6b 0e 00 00 b7 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 6c 0e 00 00 9a 02 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 6d 0e 00 00 ff 03 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 6e 0e 00 00 59 03 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:25 read 18 bytes: 20 00 6f 0e 00 00 22 01 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +rt +2013/11/21 23:36:28 read 18 bytes: 20 00 72 0e 00 00 00 00 12 01 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 73 0e 00 00 00 00 88 02 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 74 0e 00 00 00 00 ff 03 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 75 0e 00 00 00 00 30 03 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 76 0e 00 00 00 00 90 01 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 77 0e 00 00 00 00 40 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 78 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 79 0e 00 00 00 00 22 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 7a 0e 00 00 00 00 d5 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 7b 0e 00 00 00 00 f6 01 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 7c 0e 00 00 00 00 a5 03 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:28 read 18 bytes: 20 00 7d 0e 00 00 00 00 ff 03 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:29 read 18 bytes: 20 00 7e 0e 00 00 00 00 47 03 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:29 read 18 bytes: 20 00 7f 0e 00 00 00 00 c8 01 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:29 read 18 bytes: 20 00 80 0e 00 00 00 00 94 00 73 0b 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:29 read 18 bytes: 20 00 81 0e 00 00 00 00 00 00 73 0b 0f 01 85 05 41 f7 [err: ] + +2013/11/21 23:36:33 read 8 bytes: 03 20 09 04 80 01 00 00 [err: ] + +left stick +2013/11/21 23:36:34 read 18 bytes: 20 00 83 0e 00 00 00 00 00 00 69 0a 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 84 0e 00 00 00 00 00 00 c9 09 0f 01 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 85 0e 00 00 00 00 00 00 3b 09 0f 01 85 05 41 f7 [err: ] +... +2013/11/21 23:36:34 read 18 bytes: 20 00 9c 0e 00 00 00 00 00 00 ff 7f f1 b1 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 9d 0e 00 00 00 00 00 00 ff 7f b5 d1 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 9e 0e 00 00 00 00 00 00 ff 7f 60 d6 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 9f 0e 00 00 00 00 00 00 ff 7f 6e 07 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 a0 0e 00 00 00 00 00 00 ff 7f 3f 0c 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 a1 0e 00 00 00 00 00 00 ff 7f 88 38 85 05 41 f7 [err: ] +2013/11/21 23:36:34 read 18 bytes: 20 00 a2 0e 00 00 00 00 00 00 ff 7f cd 3b 85 05 41 f7 [err: ] + +right stick +2013/11/21 23:36:38 read 18 bytes: 20 00 e3 0e 00 00 00 00 00 00 69 0a c3 fe 3e 05 46 ef [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 e4 0e 00 00 00 00 00 00 69 0a c3 fe 3e 05 24 ec [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 e5 0e 00 00 00 00 00 00 69 0a c3 fe 3e 05 f6 e7 [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 e6 0e 00 00 00 00 00 00 69 0a c3 fe 21 08 d6 e3 [err: ] +... +2013/11/21 23:36:38 read 18 bytes: 20 00 ee 0e 00 00 00 00 00 00 69 0a c3 fe ff 7f 24 e2 [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 ef 0e 00 00 00 00 00 00 69 0a c3 fe ff 7f 3b e6 [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 f0 0e 00 00 00 00 00 00 69 0a c3 fe ff 7f 3a 0d [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 f1 0e 00 00 00 00 00 00 69 0a c3 fe ff 7f 04 12 [err: ] +2013/11/21 23:36:38 read 18 bytes: 20 00 f2 0e 00 00 00 00 00 00 69 0a c3 fe ff 7f 3e 3b [err: ] + +Open questions: +Are battery levels included in one of the status lines? diff --git a/xbox/xbox.go b/xbox/xbox.go index 03a96f8..53a6719 100644 --- a/xbox/xbox.go +++ b/xbox/xbox.go @@ -15,6 +15,9 @@ package main import ( + "encoding/binary" + "flag" + "fmt" "log" "math" "time" @@ -22,15 +25,26 @@ import ( "github.com/kylelemons/gousb/usb" ) +var ( + readonly = flag.Bool("readonly", false, "Only read from the controller") + debug = flag.Int("debug", 0, "USB debugging control") +) + +type modelInfo struct { + config, iface, setup, endIn, endOut uint8 + kind string +} + func main() { + flag.Parse() + ctx := usb.NewContext() defer ctx.Close() - //ctx.Debug(10) - - type modelInfo struct { - config, iface, setup, endIn, endOut uint8 + if *debug != 0 { + ctx.Debug(*debug) } + var model modelInfo devs, err := ctx.ListDevices(func(desc *usb.Descriptor) bool { @@ -62,7 +76,37 @@ func main() { Vendor Specific Class -------------- */ - model = modelInfo{1, 0, 0, 1, 1} + model = modelInfo{1, 0, 0, 1, 1, "360"} + + case desc.Vendor == 0x045e && desc.Product == 0x02d1: + log.Printf("Found Microsoft Xbox One controller") + /* + 250.006 045e:02d1 Unknown (Microsoft Corp.) + Protocol: Vendor Specific Class + Config 01: + -------------- + Interface 00 Setup 00 + Vendor Specific Class + Endpoint 1 OUT interrupt - unsynchronized data [64 0] + Endpoint 1 IN interrupt - unsynchronized data [64 0] + -------------- + Interface 01 Setup 00 + Vendor Specific Class + Interface 01 Setup 01 + Vendor Specific Class + Endpoint 2 OUT isochronous - unsynchronized data [228 0] + Endpoint 2 IN isochronous - unsynchronized data [228 0] + -------------- + Interface 02 Setup 00 + Vendor Specific Class + Interface 02 Setup 01 + Vendor Specific Class + Endpoint 3 OUT bulk - unsynchronized data [64 0] + Endpoint 3 IN bulk - unsynchronized data [64 0] + -------------- + */ + model = modelInfo{1, 0, 0, 1, 1, "one"} + case desc.Vendor == 0x1689 && desc.Product == 0xfd00: log.Printf("Found Razer Onza Tournament controller") /* @@ -90,7 +134,7 @@ func main() { Vendor Specific Class -------------- */ - model = modelInfo{1, 0, 0, 1, 2} + model = modelInfo{1, 0, 0, 1, 2, "360"} default: return false } @@ -131,16 +175,25 @@ func main() { log.Fatalf("out: openendpoint: %s", err) } - // https://github.com/Grumbel/xboxdrv/blob/master/PROTOCOL - - var b [32]byte - for { - n, err := in.Read(b[:]) - log.Printf("read %d bytes: % x [err: %v]", n, b[:n], err) - if err != nil { - break + switch { + case *readonly: + var b [512]byte + for { + n, err := in.Read(b[:]) + log.Printf("read %d bytes: % x [err: %v]", n, b[:n], err) + if err != nil { + break + } } + case model.kind == "360": + XBox360(controller, in, out) + case model.kind == "one": + XBoxOne(controller, in, out) } +} + +func XBox360(controller *usb.Device, in, out usb.Endpoint) { + // https://github.com/Grumbel/xboxdrv/blob/master/PROTOCOL const ( Empty byte = iota // 00000000 ( 0) no LEDs @@ -195,6 +248,15 @@ func main() { time.Sleep(1 * time.Second) setPlayer(Player1) + var b [512]byte + for { + n, err := in.Read(b[:]) + log.Printf("read %d bytes: % x [err: %v]", n, b[:n], err) + if err != nil { + break + } + } + /* time.Sleep(1 * time.Second) setPlayer(Player2) @@ -206,7 +268,7 @@ func main() { led(Waiting) */ - var last, cur [32]byte + var last, cur [512]byte decode := func() { n, err := in.Read(cur[:]) if err != nil || n != 20 { @@ -328,3 +390,95 @@ func main() { decode() } } + +func XBoxOne(controller *usb.Device, in, out usb.Endpoint) { + read := func() (tag, code byte, data []byte, err error) { + var b [64]byte + + n, err := in.Read(b[:]) + log.Printf("read %d bytes: % x [err: %v]", n, b[:n], err) + + if err != nil { + return 0, 0, nil, err + } + if n < 2 { + return 0, 0, nil, fmt.Errorf("only read %d bytes", n) + } + + return b[0], b[1], b[2:], nil + } + + write := func(data ...byte) error { + n, err := out.Write(data) + log.Printf("sent %d bytes: % x [err: %v]", n, data, err) + if n < len(data) { + return fmt.Errorf("only sent %d of %d bytes", n, len(data)) + } + return err + } + + dieIf := func(err error, format string, args ...interface{}) { + if err == nil { + return + } + msg := fmt.Sprintf(format, args...) + log.Fatalf("%s: %s", msg, err) + } + + var err error + + // Initializ + err = write(0x05, 0x20) + dieIf(err, "initialization") + + decode := func(data []byte) { + if len(data) != 16 { + log.Printf("Only got %d bytes (want 16)", len(data)) + } + var ( + _ = data[0] // sequence number + _ = data[1] // unknown + btn1 = data[2] // ybxaSM?N S=share, M=Menu, N=Sync + btn2 = data[3] // ?lr?RLDU r=R-Thumb, l=L-Thumb, R=D-Right, L=D-Left, D=D-Down, U=D-Up + + lt = binary.LittleEndian.Uint16(data[4:6]) // left trigger, 0..1024 + rt = binary.LittleEndian.Uint16(data[6:8]) // right trigger, 0..1024 + + lx = int16(binary.LittleEndian.Uint16(data[8:10])) // right stick X + ly = int16(binary.LittleEndian.Uint16(data[10:12])) // right stick X + rx = int16(binary.LittleEndian.Uint16(data[12:14])) // right stick X + ry = int16(binary.LittleEndian.Uint16(data[14:16])) // right stick X + ) + + // btn1, least to most significant + for i, btn := range []string{ + "SYNC", "BTN1|0x02", "MENU", "SHARE", + "A", "X", "B", "Y", + } { + if btn1&(1<