Skip to content

Commit db36559

Browse files
committed
initial commit
0 parents  commit db36559

File tree

8 files changed

+1156
-0
lines changed

8 files changed

+1156
-0
lines changed

.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, built with `go test -c`
9+
*.test
10+
11+
# Output of the go coverage tool, specifically when used with LiteIDE
12+
*.out
13+
14+
# Dependency directories (remove the comment below to include it)
15+
# vendor/
16+
17+
# Custom
18+
junk/

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# go-gc-adapter
2+
3+
A Go library for interfacing with the Gamecube controller adapter.
4+
5+
## Example
6+
7+
```go
8+
package main
9+
10+
import (
11+
"fmt"
12+
"log"
13+
"time"
14+
15+
gcadapter "github.com/Gurvan/go-gc-adapter"
16+
)
17+
18+
func main() {
19+
adapter, err := gcadapter.NewGCAdapter()
20+
if err != nil {
21+
log.Fatalf("%v", err)
22+
}
23+
defer adapter.Close()
24+
go adapter.StartPolling()
25+
26+
go func() {
27+
for range time.Tick(time.Second / time.Duration(60.)) {
28+
inputs := adapter.Controllers()
29+
fmt.Println(inputs[0])
30+
}
31+
}()
32+
time.Sleep(10 * time.Second)
33+
}
34+
```

gcadapter.go

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
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

Comments
 (0)