GC Hero
GC Hero
is a small Gamecube controller Wii U Adapter driver I wrote
to learn about libusb. The main C source file is only 283 lines, so
here it is in its entirety:
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <pthread.h> #include <libusb.h> #include <libevdev/libevdev.h> #include <libevdev/libevdev-uinput.h> #define ENDPOINT_OUT 0x02 #define ENDPOINT_IN 0x81 #define MAX_CONTROLLERS_NB 4 #define CONTROLLER_INPUT_SIZE 9 const int BUTTON_EVENTS[8] = { BTN_SOUTH, BTN_WEST, BTN_EAST, BTN_NORTH, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, BTN_DPAD_DOWN, BTN_DPAD_UP }; const int TRIGGER_EVENTS[4] = { BTN_START, BTN_TR, BTN_TR2, BTN_TL2 }; struct controller_handler_datas { unsigned int controller_id; struct libusb_device_handle* adapter_handle; struct libevdev_uinput* device; }; struct controller_handler_datas threads_datas[MAX_CONTROLLERS_NB]; void libevdev_log(const struct libevdev* device, enum libevdev_log_priority priority, void* data, const char* file, int line, const char* func, const char* format, va_list args){ printf("gc-hero : an unknown error has occured\n"); return; } void* handle_controller(void* datas){ struct controller_handler_datas* local_datas = (struct controller_handler_datas*) datas; while (1) { unsigned char payload[37]; int size = 0; libusb_interrupt_transfer(local_datas->adapter_handle, ENDPOINT_IN, payload, sizeof(payload), &size, 300); if (size == 0) { printf("controller %i : N/A\n", local_datas->controller_id); continue; } printf("controller %i : ", local_datas->controller_id); for (int i = 0; i < size; i++) { printf("%i ", payload[i]); } printf("\n"); /* * adapter data form : [33 controller1 controller2 controller3 controller4] (37 bytes) * controller data (9 bytes) : * 0 : is controller plugged ? * not yet sure between : * - 20 if controller is plugged in, 4 otherwise * - 16 if controller is plugged in, 0 otherwise * 1 : sum of action-pad and D-pad. * BTN_SOUTH : 1, BTN_WEST : 2, BTN_EAST : 4, BTN_NORTH : 8, * BTN_DPAD_LEFT : 16, BTN_DPAD_RIGHT : 32, BTN_DPAD_DOWN : 64, BTN_DPAD_UP : 128 * 2 : sum of start button + trigger truth (start, ZR, R, L) * BTN_START : 1, BTN_TR : 2, BTN_TR2 : 4, BTN_TL2 : 8 * 3 : left analog stick X * ABS_X : [0 - 255] ([30 - 230] in practice), from left to right * 4 : left analog stick Y * ABS_Y : [0 - 255] ([20 - 230] in practice), from down to up * 5 : C-stick X * ABS_RX : [0 - 255] ([41 - 227] in practice), from left to right * 6 : C-stick Y * ABS_RY : [0 - 255] ([25 - 220] in practice), from down to up * 7 : left trigger analog value (L) * ABS_HAT2Y : [0 - 255] ([30 - 242] in practice), from free to pressed * 8 : right trigger analog value (R) * ABS_HAT2X : [0 - 255] ([30 - 242] in practice), from free to pressed */ unsigned int controller_index = 1 + local_datas->controller_id * CONTROLLER_INPUT_SIZE; // actions buttons and d-pad unsigned char button_mask = 0b00000001; for(int j = 0; j < 8; j++){ if (button_mask & payload[controller_index + 1]){ libevdev_uinput_write_event(local_datas->device, EV_KEY, BUTTON_EVENTS[j], 1); } else { libevdev_uinput_write_event(local_datas->device, EV_KEY, BUTTON_EVENTS[j], 0); } button_mask <<= 1; } // trigger and start truth unsigned char trigger_mask = 0b00000001; for (int j = 0; j < 4; j++){ if (trigger_mask & payload[controller_index + 2]){ libevdev_uinput_write_event(local_datas->device, EV_KEY, TRIGGER_EVENTS[j], 1); } else { libevdev_uinput_write_event(local_datas->device, EV_KEY, TRIGGER_EVENTS[j], 0); } trigger_mask <<= 1; } // left analog stick int lstick_x_value = payload[controller_index + 3] - 128; int lstick_y_value = (payload[controller_index + 4] ^= 0xFF) - 128; // [0-255] to [255-0] - 128 libevdev_uinput_write_event(local_datas->device, EV_ABS, ABS_X, lstick_x_value); libevdev_uinput_write_event(local_datas->device, EV_ABS, ABS_Y, lstick_y_value); // c-stick int cstick_x_value = payload[controller_index + 5] - 128; int cstick_y_value = (payload[controller_index + 6] ^= 0xFF) - 128; // [0-255] to [255-0] - 128 libevdev_uinput_write_event(local_datas->device, EV_ABS, ABS_RX, cstick_x_value); libevdev_uinput_write_event(local_datas->device, EV_ABS, ABS_RY, cstick_y_value); // left and right trigger analog value int left_trigger_value = payload[controller_index + 7] - 128; int right_trigger_value = payload[controller_index + 8] - 128; libevdev_uinput_write_event(local_datas->device, EV_ABS, ABS_HAT2Y, left_trigger_value); libevdev_uinput_write_event(local_datas->device, EV_ABS, ABS_HAT2X, right_trigger_value); libevdev_uinput_write_event(local_datas->device, EV_SYN, SYN_REPORT, 0); } pthread_exit(NULL); } int main(){ bool err = false; libusb_init(NULL); libusb_set_option(NULL, LIBUSB_LOG_LEVEL_ERROR, 3); libusb_device** usb_devices; uint8_t usb_device_nbr = libusb_get_device_list(NULL, &usb_devices); libusb_device* adapter = NULL; for (int i = 0; i < usb_device_nbr; i++){ libusb_device* usb_device = usb_devices[i]; struct libusb_device_descriptor descriptor; libusb_get_device_descriptor(usb_device, &descriptor); if (descriptor.idVendor == 0x057e && descriptor.idProduct == 0x0337){ adapter = usb_device; break; } } if ((err = (adapter == NULL))){ printf("Error : No adapter found\n"); goto libusb_frees; } libusb_device_handle* adapter_handle; if ((err = libusb_open(adapter, &adapter_handle))){ printf("Error : could not access device\n"); goto libusb_frees; } int kernelActive = libusb_kernel_driver_active(adapter_handle, 0); if (kernelActive == 1){ if ((err = libusb_detach_kernel_driver(adapter_handle, 0))){ printf("Error : could not detach kernel driver 0\n"); goto libusb_frees; } } else if (kernelActive != 0){ printf("Unknown error\n"); goto libusb_frees; } libusb_claim_interface(adapter_handle, 0); // reset the device int size = 0; unsigned char payload[1] = {0x13}; libusb_interrupt_transfer(adapter_handle, ENDPOINT_OUT, payload, sizeof(payload), &size, 0); //uinput devices creation struct libevdev* devices[MAX_CONTROLLERS_NB]; struct libevdev_uinput* uidevices[MAX_CONTROLLERS_NB]; for (int i = 0; i < MAX_CONTROLLERS_NB; i++){ devices[i] = libevdev_new(); char device_name[22]; sprintf(device_name, "gamecube-controller-%i", i); libevdev_set_name(devices[i], device_name); libevdev_set_device_log_function(devices[i], libevdev_log, LIBEVDEV_LOG_DEBUG, NULL); libevdev_enable_event_type(devices[i], EV_KEY); libevdev_enable_event_code(devices[i], EV_KEY, BTN_START, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_NORTH, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_EAST, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_SOUTH, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_WEST, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_DPAD_UP, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_DPAD_RIGHT, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_DPAD_DOWN, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_DPAD_LEFT, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_TR, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_TL2, NULL); libevdev_enable_event_code(devices[i], EV_KEY, BTN_TR2, NULL); libevdev_enable_event_type(devices[i], EV_ABS); struct input_absinfo analog_input_infos; analog_input_infos.value = 0; analog_input_infos.minimum = -128; analog_input_infos.maximum = 128; libevdev_enable_event_code(devices[i], EV_ABS, ABS_X, &analog_input_infos); libevdev_enable_event_code(devices[i], EV_ABS, ABS_Y, &analog_input_infos); libevdev_enable_event_code(devices[i], EV_ABS, ABS_RX, &analog_input_infos); libevdev_enable_event_code(devices[i], EV_ABS, ABS_RY, &analog_input_infos); libevdev_enable_event_code(devices[i], EV_ABS, ABS_HAT2X, &analog_input_infos); libevdev_enable_event_code(devices[i], EV_ABS, ABS_HAT2Y, &analog_input_infos); libevdev_enable_event_type(devices[i], EV_FF); libevdev_enable_event_code(devices[i], EV_FF, FF_RUMBLE, NULL); libevdev_enable_event_code(devices[i], EV_SYN, SYN_REPORT, NULL); libevdev_enable_event_code(devices[i], EV_SYN, SYN_DROPPED, NULL); err = libevdev_uinput_create_from_device(devices[i], LIBEVDEV_UINPUT_OPEN_MANAGED, &uidevices[i]) != 0; if (err){ printf("error creating uinput device\n"); goto libevdev_frees; } } pthread_t threads[MAX_CONTROLLERS_NB]; for(int i = 0; i < MAX_CONTROLLERS_NB; i++){ threads_datas[i].controller_id = i; threads_datas[i].adapter_handle = adapter_handle; threads_datas[i].device = uidevices[i]; err = pthread_create(&threads[i], NULL, handle_controller, (void*) &threads_datas[i]); if(err){ printf("couldn't create thread for controller %i\n", i); goto libevdev_frees; } } for(int i = 0; i < MAX_CONTROLLERS_NB; i++){ pthread_join(threads[i], NULL); } libevdev_frees: for(int i = 0; i < MAX_CONTROLLERS_NB; i++){ libevdev_uinput_destroy(uidevices[i]); libevdev_free(devices[i]); } libusb_frees: libusb_free_device_list(usb_devices, true); libusb_exit(NULL); return err ? 1 : 0; }