GC Hero

^home ^projects

@Gitlab

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;
}