|
| 1 | +package gcadapter |
| 2 | + |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "log" |
| 6 | + "sync" |
| 7 | + |
| 8 | + "github.com/google/gousb" |
| 9 | +) |
| 10 | + |
| 11 | +const ( |
| 12 | + gcAdapterVendorID gousb.ID = 0x057E |
| 13 | + gcAdapterProductID gousb.ID = 0x0337 |
| 14 | + startEndpoint int = 0x02 |
| 15 | + readEndpoint int = 0x81 |
| 16 | +) |
| 17 | + |
| 18 | +var startPayload = []byte{0x13} |
| 19 | + |
| 20 | +// GCAdapter represents a Gamecube controller usb adapter |
| 21 | +type GCAdapter struct { |
| 22 | + ctx *gousb.Context |
| 23 | + dev *gousb.Device |
| 24 | + cfg *gousb.Config |
| 25 | + intf *gousb.Interface |
| 26 | + endpoint *gousb.InEndpoint |
| 27 | + controllers map[uint8]*rawGCInput |
| 28 | + offsets map[uint8]*Offsets |
| 29 | + mutex sync.RWMutex |
| 30 | +} |
| 31 | + |
| 32 | +// NewGCAdapter connects to a Gamecube controller usb adapter and returns a pointer to it. |
| 33 | +// An adapter should be closed with adapter.Close() once it's not required anymore or when the program closes. |
| 34 | +// The best way of achieving that is calling defer adapter.Close() right after adapter, err := NewGCAdapter() |
| 35 | +func NewGCAdapter() (*GCAdapter, error) { |
| 36 | + var adapter *GCAdapter = nil |
| 37 | + ctx := gousb.NewContext() |
| 38 | + dev, err := ctx.OpenDeviceWithVIDPID(gcAdapterVendorID, gcAdapterProductID) |
| 39 | + if dev == nil { |
| 40 | + err = errors.New("GC Adapter: no adapter found") |
| 41 | + } |
| 42 | + if err != nil { |
| 43 | + // log.Fatalf("list: %s", err) |
| 44 | + return adapter, err |
| 45 | + } |
| 46 | + |
| 47 | + dev.SetAutoDetach(true) |
| 48 | + |
| 49 | + cfg, err := dev.Config(1) |
| 50 | + if err != nil { |
| 51 | + // log.Fatalf("%s.Config(1): %v", dev, err) |
| 52 | + return adapter, err |
| 53 | + } |
| 54 | + |
| 55 | + intf, err := cfg.Interface(0, 0) |
| 56 | + if err != nil { |
| 57 | + // log.Fatalf("%s.Interface(0, 0): %v", cfg, err) |
| 58 | + return adapter, err |
| 59 | + } |
| 60 | + |
| 61 | + outep, err := intf.OutEndpoint(startEndpoint) |
| 62 | + if err != nil { |
| 63 | + // log.Fatalf("%s.OutEndpoint(%d): %v", outep, startEndpoint, err) |
| 64 | + return adapter, err |
| 65 | + } |
| 66 | + |
| 67 | + _, err = outep.Write(startPayload) |
| 68 | + if err != nil { |
| 69 | + // log.Fatalf("Can't write payload %d: %v", out, err) |
| 70 | + return adapter, err |
| 71 | + } |
| 72 | + |
| 73 | + inep, err := intf.InEndpoint(readEndpoint) |
| 74 | + if err != nil { |
| 75 | + // log.Fatalf("%s.InEndpoint(%d): %v", inep, readEndpoint, err) |
| 76 | + return adapter, err |
| 77 | + } |
| 78 | + |
| 79 | + adapter = newGCAdapterFromEndpoint(inep) |
| 80 | + adapter.ctx = ctx |
| 81 | + adapter.dev = dev |
| 82 | + adapter.cfg = cfg |
| 83 | + adapter.intf = intf |
| 84 | + adapter.mutex.Lock() |
| 85 | + adapter.controllers = make(map[uint8]*rawGCInput) |
| 86 | + adapter.offsets = make(map[uint8]*Offsets) |
| 87 | + for _, PORT := range []uint8{0, 1, 2, 3} { |
| 88 | + adapter.controllers[PORT] = &rawGCInput{} |
| 89 | + adapter.offsets[PORT] = &Offsets{} |
| 90 | + } |
| 91 | + adapter.mutex.Unlock() |
| 92 | + return adapter, nil |
| 93 | +} |
| 94 | + |
| 95 | +func newGCAdapterFromEndpoint(endpoint *gousb.InEndpoint) *GCAdapter { |
| 96 | + adapter := &GCAdapter{} |
| 97 | + adapter.endpoint = endpoint |
| 98 | + return adapter |
| 99 | +} |
| 100 | + |
| 101 | +// Poll polls the Gamecube usb adapter once |
| 102 | +func (adapter *GCAdapter) Poll() error { |
| 103 | + return adapter.step() |
| 104 | +} |
| 105 | + |
| 106 | +// StartPolling starts a Gamecube adapter polling loop, where the adapter is polled at its defined polling rate |
| 107 | +// (which is 125Hz by default on the official adapter). |
| 108 | +// The function is meant to be wrapped in a goroutine. |
| 109 | +func (adapter *GCAdapter) StartPolling() { |
| 110 | + var err error |
| 111 | + for { |
| 112 | + err = adapter.step() |
| 113 | + if err != nil { |
| 114 | + log.Printf("%v", err) |
| 115 | + } |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +// Close properly closes the adapter once it's not required anymore |
| 120 | +func (adapter *GCAdapter) Close() error { |
| 121 | + var err error |
| 122 | + err = adapter.ctx.Close() |
| 123 | + if err != nil { |
| 124 | + return err |
| 125 | + } |
| 126 | + err = adapter.dev.Close() |
| 127 | + if err != nil { |
| 128 | + return err |
| 129 | + } |
| 130 | + err = adapter.cfg.Close() |
| 131 | + if err != nil { |
| 132 | + return err |
| 133 | + } |
| 134 | + adapter.intf.Close() |
| 135 | + return nil |
| 136 | +} |
| 137 | + |
| 138 | +// Buttons represents the Gamecube controller buttons |
| 139 | +type Buttons struct { |
| 140 | + UP bool |
| 141 | + DOWN bool |
| 142 | + RIGHT bool |
| 143 | + LEFT bool |
| 144 | + Y bool |
| 145 | + X bool |
| 146 | + B bool |
| 147 | + A bool |
| 148 | + L bool |
| 149 | + R bool |
| 150 | + Z bool |
| 151 | + START bool |
| 152 | +} |
| 153 | + |
| 154 | +type rawGCInput struct { |
| 155 | + Button Buttons |
| 156 | + StickX uint8 |
| 157 | + StickY uint8 |
| 158 | + CX uint8 |
| 159 | + CY uint8 |
| 160 | + LAnalog uint8 |
| 161 | + RAnalog uint8 |
| 162 | + PluggedIn bool |
| 163 | +} |
| 164 | + |
| 165 | +// GCInputs represent the state of the gamecube controller, with sticks values in [-1, 1], triggers values in [0, 1] and buttons as boolean |
| 166 | +// Sticks and triggers values are calibrated, and sticks values are clamped inside the unit circle, but deadzones are not enforced. |
| 167 | +type GCInputs struct { |
| 168 | + Button Buttons |
| 169 | + StickX float32 |
| 170 | + StickY float32 |
| 171 | + CX float32 |
| 172 | + CY float32 |
| 173 | + LAnalog float32 |
| 174 | + RAnalog float32 |
| 175 | + PluggedIn bool |
| 176 | +} |
| 177 | + |
| 178 | +// Offsets are the sticks and trigger values read right after plugging the controller or resetting it with X+Y+Start. |
| 179 | +// They are used to calibrate the values returned in GCInputs. |
| 180 | +type Offsets struct { |
| 181 | + StickX uint8 |
| 182 | + StickY uint8 |
| 183 | + CX uint8 |
| 184 | + CY uint8 |
| 185 | + LAnalog uint8 |
| 186 | + RAnalog uint8 |
| 187 | + PluggedIn bool |
| 188 | +} |
| 189 | + |
| 190 | +func (adapter *GCAdapter) step() error { |
| 191 | + controllers, err := readGCAdapter(adapter.endpoint) |
| 192 | + if err != nil { |
| 193 | + return err |
| 194 | + } |
| 195 | + |
| 196 | + for PORT, controller := range controllers { |
| 197 | + if adapter.controllers[PORT].PluggedIn != adapter.offsets[PORT].PluggedIn { |
| 198 | + adapter.offsets[PORT].PluggedIn = adapter.controllers[PORT].PluggedIn |
| 199 | + adapter.offsets[PORT].StickX = adapter.controllers[PORT].StickX |
| 200 | + adapter.offsets[PORT].StickY = adapter.controllers[PORT].StickY |
| 201 | + adapter.offsets[PORT].CX = adapter.controllers[PORT].CX |
| 202 | + adapter.offsets[PORT].CY = adapter.controllers[PORT].CY |
| 203 | + adapter.offsets[PORT].LAnalog = adapter.controllers[PORT].LAnalog |
| 204 | + adapter.offsets[PORT].RAnalog = adapter.controllers[PORT].RAnalog |
| 205 | + } |
| 206 | + adapter.mutex.Lock() |
| 207 | + adapter.controllers[PORT] = controller |
| 208 | + adapter.mutex.Unlock() |
| 209 | + } |
| 210 | + |
| 211 | + return nil |
| 212 | +} |
| 213 | + |
| 214 | +// Controller return the current state of the controller on port PORT. |
| 215 | +func (adapter *GCAdapter) Controller(PORT uint8) *GCInputs { |
| 216 | + adapter.mutex.RLock() |
| 217 | + defer adapter.mutex.RUnlock() |
| 218 | + return processRawController(adapter.controllers[PORT], adapter.offsets[PORT]) |
| 219 | +} |
| 220 | + |
| 221 | +// Controllers return the current state of the plugged in controllers. |
| 222 | +func (adapter *GCAdapter) Controllers() map[uint8]*GCInputs { |
| 223 | + adapter.mutex.RLock() |
| 224 | + defer adapter.mutex.RUnlock() |
| 225 | + gcInputs := make(map[uint8]*GCInputs) |
| 226 | + for _, PORT := range []uint8{0, 1, 2, 3} { |
| 227 | + if adapter.controllers[PORT].PluggedIn { |
| 228 | + gcInputs[PORT] = processRawController(adapter.controllers[PORT], adapter.offsets[PORT]) |
| 229 | + } |
| 230 | + } |
| 231 | + return gcInputs |
| 232 | +} |
| 233 | + |
| 234 | +// AllControllers return the current state of the 4 controllers (even unplugged ones) |
| 235 | +func (adapter *GCAdapter) AllControllers() map[uint8]*GCInputs { |
| 236 | + adapter.mutex.RLock() |
| 237 | + defer adapter.mutex.RUnlock() |
| 238 | + gcInputs := make(map[uint8]*GCInputs) |
| 239 | + for _, PORT := range []uint8{0, 1, 2, 3} { |
| 240 | + gcInputs[PORT] = processRawController(adapter.controllers[PORT], adapter.offsets[PORT]) |
| 241 | + } |
| 242 | + return gcInputs |
| 243 | +} |
| 244 | + |
| 245 | +func processRawController(rawInput *rawGCInput, offsets *Offsets) *GCInputs { |
| 246 | + gcinput := GCInputs{} |
| 247 | + gcinput.Button = rawInput.Button |
| 248 | + |
| 249 | + x, y := rawInput.StickX, rawInput.StickY |
| 250 | + dx, dy := offsets.StickX, offsets.StickY |
| 251 | + x, y = correctStickOffset(x, dx), correctStickOffset(y, dy) |
| 252 | + x, y = clampStick(x, y) |
| 253 | + gcinput.StickX = float32(0.0125 * float64(byteToInt8(x))) |
| 254 | + gcinput.StickY = float32(0.0125 * float64(byteToInt8(y))) |
| 255 | + |
| 256 | + x, y = rawInput.CX, rawInput.CY |
| 257 | + dx, dy = offsets.CX, offsets.CY |
| 258 | + x, y = correctStickOffset(x, dx), correctStickOffset(y, dy) |
| 259 | + x, y = clampStick(x, y) |
| 260 | + gcinput.CX = float32(0.0125 * float64(byteToInt8(x))) |
| 261 | + gcinput.CY = float32(0.0125 * float64(byteToInt8(y))) |
| 262 | + |
| 263 | + l := rawInput.LAnalog |
| 264 | + l = correctTriggerOffset(l, offsets.LAnalog) |
| 265 | + if l > 140 { |
| 266 | + l = 140 |
| 267 | + } |
| 268 | + gcinput.LAnalog = float32(l) / 140. |
| 269 | + |
| 270 | + r := rawInput.RAnalog |
| 271 | + r = correctTriggerOffset(r, offsets.LAnalog) |
| 272 | + if r > 140 { |
| 273 | + r = 140 |
| 274 | + } |
| 275 | + gcinput.RAnalog = float32(r) / 140. |
| 276 | + |
| 277 | + // Deadzone |
| 278 | + // if math.Abs(float64(gcinput.StickX)) < 0.28 { |
| 279 | + // gcinput.StickX = 0 |
| 280 | + // } |
| 281 | + // if math.Abs(float64(gcinput.StickY)) < 0.28 { |
| 282 | + // gcinput.StickY = 0 |
| 283 | + // } |
| 284 | + return &gcinput |
| 285 | +} |
| 286 | + |
| 287 | +func deserializeGCControllers(data []byte) map[uint8]*rawGCInput { |
| 288 | + gcInputs := make(map[uint8]*rawGCInput) |
| 289 | + for _, PORT := range []uint8{0, 1, 2, 3} { |
| 290 | + gcInput := &rawGCInput{} |
| 291 | + |
| 292 | + gcInput.PluggedIn = data[9*PORT+1] == 20 || data[9*PORT+1] == 16 |
| 293 | + |
| 294 | + gcInput.Button.UP = data[9*PORT+2]&0b10000000 != 0 |
| 295 | + gcInput.Button.DOWN = data[9*PORT+2]&0b01000000 != 0 |
| 296 | + gcInput.Button.RIGHT = data[9*PORT+2]&0b00100000 != 0 |
| 297 | + gcInput.Button.LEFT = data[9*PORT+2]&0b00010000 != 0 |
| 298 | + gcInput.Button.Y = data[9*PORT+2]&0b00001000 != 0 |
| 299 | + gcInput.Button.X = data[9*PORT+2]&0b00000100 != 0 |
| 300 | + gcInput.Button.B = data[9*PORT+2]&0b00000010 != 0 |
| 301 | + gcInput.Button.A = data[9*PORT+2]&0b00000001 != 0 |
| 302 | + gcInput.Button.L = data[9*PORT+3]&0b00001000 != 0 |
| 303 | + gcInput.Button.R = data[9*PORT+3]&0b00000100 != 0 |
| 304 | + gcInput.Button.Z = data[9*PORT+3]&0b00000010 != 0 |
| 305 | + gcInput.Button.START = data[9*PORT+3]&0b00000001 != 0 |
| 306 | + |
| 307 | + gcInput.StickX = data[9*PORT+4] |
| 308 | + gcInput.StickY = data[9*PORT+5] |
| 309 | + gcInput.CX = data[9*PORT+6] |
| 310 | + gcInput.CX = data[9*PORT+7] |
| 311 | + gcInput.LAnalog = data[9*PORT+8] |
| 312 | + gcInput.RAnalog = data[9*PORT+9] |
| 313 | + |
| 314 | + gcInputs[PORT] = gcInput |
| 315 | + } |
| 316 | + return gcInputs |
| 317 | +} |
| 318 | + |
| 319 | +func readGCAdapter(adapterEndpoint *gousb.InEndpoint) (map[uint8]*rawGCInput, error) { |
| 320 | + rawGcInputs := make(map[uint8]*rawGCInput) |
| 321 | + |
| 322 | + data := make([]byte, adapterEndpoint.Desc.MaxPacketSize) |
| 323 | + _, err := adapterEndpoint.Read(data) |
| 324 | + if err != nil { |
| 325 | + // fmt.Printf("Couldn't read adapter. Error: %v\n", err) |
| 326 | + return rawGcInputs, err |
| 327 | + } |
| 328 | + rawGcInputs = deserializeGCControllers(data) |
| 329 | + return rawGcInputs, nil |
| 330 | +} |
0 commit comments