#include "SDL_internal.h"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../../misc/SDL_libusb.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "../../hidapi/SDL_hidapi_c.h"
#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
#if 0#endif
#define MAX_CONTROLLERS 4
typedef struct
{
bool pc_mode;
SDL_JoystickID joysticks[MAX_CONTROLLERS];
Uint8 wireless[MAX_CONTROLLERS];
Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
Uint8 rumbleAllowed[MAX_CONTROLLERS];
Uint8 rumble[1 + MAX_CONTROLLERS];
bool rumbleUpdate;
bool useRumbleBrake;
} SDL_DriverGameCube_Context;
static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
}
static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
}
static bool HIDAPI_DriverGameCube_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
SDL_HIDAPI_DEFAULT));
}
static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
return true;
}
if (vendor_id == USB_VENDOR_DRAGONRISE &&
(product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 ||
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3)) {
return true;
}
return false;
}
static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
{
SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 - 88, SDL_GAMEPAD_AXIS_COUNT);
SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 + 88, SDL_GAMEPAD_AXIS_COUNT);
ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40;
ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40;
}
static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
if (hint) {
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
ctx->useRumbleBrake = SDL_GetStringBoolean(hint, false);
}
}
static bool HIDAPI_DriverGameCube_EnableAdapter(SDL_HIDAPI_Device *device)
{
#ifdef HAVE_LIBUSB
SDL_hid_close(device->dev);
SDL_LibUSBContext *libusb_ctx;
if (SDL_InitLibUSB(&libusb_ctx)) {
libusb_context *context = NULL;
libusb_device **devs = NULL;
libusb_device_handle *handle = NULL;
struct libusb_device_descriptor desc;
ssize_t i, num_devs;
bool kernel_detached = false;
if (libusb_ctx->init(&context) == 0) {
num_devs = libusb_ctx->get_device_list(context, &devs);
for (i = 0; i < num_devs; ++i) {
if (libusb_ctx->get_device_descriptor(devs[i], &desc) != 0) {
continue;
}
if (desc.idVendor != USB_VENDOR_NINTENDO ||
desc.idProduct != USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
continue;
}
if (libusb_ctx->open(devs[i], &handle) != 0) {
continue;
}
if (libusb_ctx->kernel_driver_active(handle, 0)) {
if (libusb_ctx->detach_kernel_driver(handle, 0) == 0) {
kernel_detached = true;
}
}
if (libusb_ctx->claim_interface(handle, 0) == 0) {
libusb_ctx->control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000);
libusb_ctx->release_interface(handle, 0);
}
if (kernel_detached) {
libusb_ctx->attach_kernel_driver(handle, 0);
}
libusb_ctx->close(handle);
}
libusb_ctx->free_device_list(devs, 1);
libusb_ctx->exit(context);
}
SDL_QuitLibUSB();
}
device->dev = SDL_hid_open_path(device->path);
if (!device->dev) {
return false;
}
#endif
Uint8 initMagic = 0x13;
if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
return false;
}
SDL_Delay(10);
return true;
}
static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx;
Uint8 packet[37];
Uint8 *curSlot;
Uint8 i;
int size;
Uint8 rumbleMagic = 0x11;
ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
if (!ctx) {
return false;
}
device->context = ctx;
ctx->rumble[0] = rumbleMagic;
if (device->vendor_id != USB_VENDOR_NINTENDO) {
ctx->pc_mode = true;
}
if (ctx->pc_mode) {
#ifdef SDL_PLATFORM_WIN32
ResetAxisRange(ctx, 0);
HIDAPI_JoystickConnected(device, &ctx->joysticks[0]);
#else
for (i = 0; i < MAX_CONTROLLERS; ++i) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
#endif
} else {
if (!HIDAPI_DriverGameCube_EnableAdapter(device)) {
return false;
}
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
#ifdef DEBUG_GAMECUBE_PROTOCOL
HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
#endif
if (size < 37 || packet[0] != 0x21) {
continue; }
curSlot = packet + 1;
for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
if (curSlot[0] & 0x30) { if (ctx->joysticks[i] == 0) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
} else {
if (ctx->joysticks[i] != 0) {
HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
ctx->joysticks[i] = 0;
}
continue;
}
}
}
}
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
return true;
}
static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 i;
for (i = 0; i < 4; ++i) {
if (instance_id == ctx->joysticks[i]) {
return i;
}
}
return -1;
}
static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 slot, const Uint8 *packet, bool invert_c_stick)
{
SDL_Joystick *joystick;
Uint8 i;
Uint8 v;
Sint16 axis_value;
Uint64 timestamp = SDL_GetTicksNS();
#ifdef SDL_PLATFORM_WIN32
i = 0;
#else
i = slot;
if (i >= MAX_CONTROLLERS) {
return;
}
#endif
joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
if (!joystick) {
return;
}
#define READ_BUTTON(off, flag, button) \
SDL_SendJoystickButton( \
timestamp, \
joystick, \
button, \
((packet[off] & flag) != 0));
READ_BUTTON(0, 0x02, 0) READ_BUTTON(0, 0x04, 1) READ_BUTTON(0, 0x08, 3) READ_BUTTON(0, 0x01, 2) READ_BUTTON(1, 0x80, 4) READ_BUTTON(1, 0x20, 5) READ_BUTTON(1, 0x40, 6) READ_BUTTON(1, 0x10, 7) READ_BUTTON(1, 0x02, 8) READ_BUTTON(0, 0x80, 9)
READ_BUTTON(0, 0x20, 10) READ_BUTTON(0, 0x10, 11) #undef READ_BUTTON
#define READ_AXIS(off, axis, invert) \
v = (invert) ? (0xff - packet[off]) : packet[off]; \
if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
SDL_SendJoystickAxis( \
timestamp, \
joystick, \
axis, axis_value);
READ_AXIS(2, SDL_GAMEPAD_AXIS_LEFTX, 0)
READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTY, 1)
READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX, invert_c_stick ? 1 : 0)
READ_AXIS(4, SDL_GAMEPAD_AXIS_RIGHTY, invert_c_stick ? 0 : 1)
READ_AXIS(6, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0)
READ_AXIS(7, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0)
#undef READ_AXIS
}
static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size)
{
SDL_Joystick *joystick;
Uint8 *curSlot;
Uint8 i;
Sint16 axis_value;
Uint64 timestamp = SDL_GetTicksNS();
if (size < 37 || packet[0] != 0x21) {
return; }
curSlot = packet + 1;
for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
if (curSlot[0] & 0x30) { if (ctx->joysticks[i] == 0) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
if (!joystick) {
continue;
}
} else {
if (ctx->joysticks[i] != 0) {
HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
ctx->joysticks[i] = 0;
}
continue;
}
#define READ_BUTTON(off, flag, button) \
SDL_SendJoystickButton( \
timestamp, \
joystick, \
button, \
((curSlot[off] & flag) != 0));
READ_BUTTON(1, 0x01, 0) READ_BUTTON(1, 0x02, 1) READ_BUTTON(1, 0x04, 2) READ_BUTTON(1, 0x08, 3) READ_BUTTON(1, 0x10, 4) READ_BUTTON(1, 0x20, 5) READ_BUTTON(1, 0x40, 6) READ_BUTTON(1, 0x80, 7) READ_BUTTON(2, 0x01, 8) READ_BUTTON(2, 0x02, 9)
READ_BUTTON(2, 0x04, 10) READ_BUTTON(2, 0x08, 11) #undef READ_BUTTON
#define READ_AXIS(off, axis) \
if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
axis_value = (Sint16)HIDAPI_RemapVal(curSlot[off], ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
SDL_SendJoystickAxis( \
timestamp, \
joystick, \
axis, axis_value);
READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX)
READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY)
READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX)
READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY)
READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER)
READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
#undef READ_AXIS
}
}
static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 packet[USB_PACKET_LENGTH];
int size;
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
#ifdef DEBUG_GAMECUBE_PROTOCOL
HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
#endif
if (ctx->pc_mode) {
if (size == 10) {
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet[0] - 1, &packet[1], true);
} else if (size == 9) {
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, 0, packet, false);
} else {
}
} else {
HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size);
}
}
if (ctx->rumbleUpdate) {
SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
ctx->rumbleUpdate = false;
}
return true;
}
static bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 i;
SDL_AssertJoysticksLocked();
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
if (joystick->instance_id == ctx->joysticks[i]) {
joystick->nbuttons = 12;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
if (ctx->wireless[i]) {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
} else {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
}
return true;
}
}
return false; }
static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint8 i, val;
SDL_AssertJoysticksLocked();
if (ctx->pc_mode) {
return SDL_Unsupported();
}
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
if (joystick->instance_id == ctx->joysticks[i]) {
if (ctx->wireless[i]) {
return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble");
}
if (!ctx->rumbleAllowed[i]) {
return SDL_SetError("Second USB cable for WUP-028 not connected");
}
if (ctx->useRumbleBrake) {
if (low_frequency_rumble == 0 && high_frequency_rumble > 0) {
val = 0; } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) {
val = 2; } else {
val = 1; }
} else {
val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
}
if (val != ctx->rumble[i + 1]) {
ctx->rumble[i + 1] = val;
ctx->rumbleUpdate = true;
}
return true;
}
}
return SDL_SetError("Couldn't find joystick");
}
static bool HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
Uint32 result = 0;
SDL_AssertJoysticksLocked();
if (!ctx->pc_mode) {
Uint8 i;
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
if (joystick->instance_id == ctx->joysticks[i]) {
if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) {
result |= SDL_JOYSTICK_CAP_RUMBLE;
break;
}
}
}
}
return result;
}
static bool HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
if (ctx->rumbleUpdate) {
SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
ctx->rumbleUpdate = false;
}
}
static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = {
SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
true,
HIDAPI_DriverGameCube_RegisterHints,
HIDAPI_DriverGameCube_UnregisterHints,
HIDAPI_DriverGameCube_IsEnabled,
HIDAPI_DriverGameCube_IsSupportedDevice,
HIDAPI_DriverGameCube_InitDevice,
HIDAPI_DriverGameCube_GetDevicePlayerIndex,
HIDAPI_DriverGameCube_SetDevicePlayerIndex,
HIDAPI_DriverGameCube_UpdateDevice,
HIDAPI_DriverGameCube_OpenJoystick,
HIDAPI_DriverGameCube_RumbleJoystick,
HIDAPI_DriverGameCube_RumbleJoystickTriggers,
HIDAPI_DriverGameCube_GetJoystickCapabilities,
HIDAPI_DriverGameCube_SetJoystickLED,
HIDAPI_DriverGameCube_SendJoystickEffect,
HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
HIDAPI_DriverGameCube_CloseJoystick,
HIDAPI_DriverGameCube_FreeDevice,
};
#endif
#endif