mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-04-13 11:51:28 -06:00
389 lines
11 KiB
C++
389 lines
11 KiB
C++
#include "input/api/GameCube/GameCubeControllerProvider.h"
|
|
#include "input/api/GameCube/GameCubeController.h"
|
|
#include "util/libusbWrapper/libusbWrapper.h"
|
|
|
|
#if HAS_GAMECUBE
|
|
|
|
constexpr uint16_t kVendorId = 0x57e, kProductId = 0x337;
|
|
|
|
GameCubeControllerProvider::GameCubeControllerProvider()
|
|
{
|
|
m_libusb = libusbWrapper::getInstance();
|
|
m_libusb->init();
|
|
if(!m_libusb->isAvailable())
|
|
throw std::runtime_error("libusbWrapper not available");
|
|
m_libusb->p_libusb_init(&m_context);
|
|
|
|
for(auto i = 0; i < kMaxAdapters; ++i)
|
|
{
|
|
auto device = fetch_usb_device(i);
|
|
if(std::get<0>(device))
|
|
{
|
|
m_adapters[i].m_device_handle = std::get<0>(device);
|
|
m_adapters[i].m_endpoint_reader = std::get<1>(device);
|
|
m_adapters[i].m_endpoint_writer = std::get<2>(device);
|
|
}
|
|
}
|
|
|
|
if (m_libusb->p_libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
|
|
{
|
|
m_libusb->p_libusb_hotplug_register_callback(m_context, static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
|
|
LIBUSB_HOTPLUG_NO_FLAGS, kVendorId, kProductId, LIBUSB_HOTPLUG_MATCH_ANY, &GameCubeControllerProvider::hotplug_event, this, &m_callback_handle);
|
|
}
|
|
|
|
m_running = true;
|
|
m_reader_thread = std::thread(&GameCubeControllerProvider::reader_thread, this);
|
|
m_writer_thread = std::thread(&GameCubeControllerProvider::writer_thread, this);
|
|
}
|
|
|
|
GameCubeControllerProvider::~GameCubeControllerProvider()
|
|
{
|
|
if (m_running)
|
|
{
|
|
m_running = false;
|
|
m_writer_thread.join();
|
|
m_reader_thread.join();
|
|
}
|
|
|
|
if (m_callback_handle)
|
|
{
|
|
m_libusb->p_libusb_hotplug_deregister_callback(m_context, m_callback_handle);
|
|
m_callback_handle = 0;
|
|
}
|
|
|
|
for (auto& a : m_adapters)
|
|
{
|
|
m_libusb->p_libusb_close(a.m_device_handle);
|
|
}
|
|
|
|
if (m_context)
|
|
{
|
|
m_libusb->p_libusb_exit(m_context);
|
|
m_context = nullptr;
|
|
}
|
|
}
|
|
|
|
std::vector<ControllerPtr> GameCubeControllerProvider::get_controllers()
|
|
{
|
|
std::vector<ControllerPtr> result;
|
|
|
|
const auto adapter_count = get_adapter_count();
|
|
for (uint32 adapter_index = 0; adapter_index < adapter_count && adapter_index < kMaxAdapters; ++adapter_index)
|
|
{
|
|
// adapter doesn't tell us which one is actually connected, so we return all of them
|
|
for (int index = 0; index < 4; ++index)
|
|
result.emplace_back(std::make_shared<GameCubeController>(adapter_index, index));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
uint32 GameCubeControllerProvider::get_adapter_count() const
|
|
{
|
|
uint32 adapter_count = 0;
|
|
|
|
libusb_device** devices;
|
|
const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices);
|
|
if (count < 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_list: {}", static_cast<int>(count), m_libusb->p_libusb_error_name(static_cast<int>(count)));
|
|
return adapter_count;
|
|
}
|
|
|
|
for (ssize_t i = 0; i < count; ++i)
|
|
{
|
|
if (!devices[i])
|
|
continue;
|
|
|
|
libusb_device_descriptor desc;
|
|
int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc);
|
|
if (ret != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret));
|
|
continue;
|
|
}
|
|
|
|
if (desc.idVendor != kVendorId || desc.idProduct != kProductId)
|
|
continue;
|
|
|
|
++adapter_count;
|
|
}
|
|
|
|
m_libusb->p_libusb_free_device_list(devices, 1);
|
|
return adapter_count;
|
|
}
|
|
|
|
bool GameCubeControllerProvider::has_rumble_connected(uint32 adapter_index) const
|
|
{
|
|
if (adapter_index >= m_adapters.size())
|
|
return false;
|
|
|
|
std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex);
|
|
return m_adapters[adapter_index].m_rumble_connected;
|
|
}
|
|
|
|
bool GameCubeControllerProvider::is_connected(uint32 adapter_index) const
|
|
{
|
|
if (adapter_index >= m_adapters.size())
|
|
return false;
|
|
|
|
return m_adapters[adapter_index].m_device_handle != nullptr;
|
|
}
|
|
|
|
void GameCubeControllerProvider::set_rumble_state(uint32 adapter_index, uint32 index, bool state)
|
|
{
|
|
if (adapter_index >= m_adapters.size())
|
|
return;
|
|
|
|
if (index >= kMaxIndex)
|
|
return;
|
|
|
|
std::scoped_lock lock(m_writer_mutex);
|
|
m_adapters[adapter_index].rumble_states[index] = state;
|
|
m_rumble_changed = true;
|
|
m_writer_cond.notify_all();
|
|
}
|
|
|
|
GameCubeControllerProvider::GCState GameCubeControllerProvider::get_state(uint32 adapter_index, uint32 index)
|
|
{
|
|
if (adapter_index >= m_adapters.size())
|
|
return {};
|
|
|
|
if (index >= kMaxIndex)
|
|
return {};
|
|
|
|
std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex);
|
|
return m_adapters[adapter_index].m_states[index];
|
|
}
|
|
|
|
#ifdef interface
|
|
#undef interface
|
|
#endif
|
|
|
|
std::tuple<libusb_device_handle*, uint8, uint8> GameCubeControllerProvider::fetch_usb_device(uint32 adapter) const
|
|
{
|
|
std::tuple<libusb_device_handle*, uint8, uint8> result{nullptr, 0xFF, 0xFF};
|
|
|
|
libusb_device** devices;
|
|
const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices);
|
|
if (count < 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_list: {}", static_cast<int>(count), m_libusb->p_libusb_error_name(static_cast<int>(count)));
|
|
return result;
|
|
}
|
|
|
|
int adapter_index = 0;
|
|
for (ssize_t i = 0; i < count; ++i)
|
|
{
|
|
if (!devices[i])
|
|
continue;
|
|
|
|
libusb_device_descriptor desc;
|
|
int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc);
|
|
if (ret != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret));
|
|
continue;
|
|
}
|
|
|
|
if (desc.idVendor != kVendorId || desc.idProduct != kProductId)
|
|
continue;
|
|
|
|
if (adapter != adapter_index++)
|
|
continue;
|
|
|
|
libusb_device_handle* device_handle;
|
|
ret = m_libusb->p_libusb_open(devices[i], &device_handle);
|
|
if (ret != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_open: {}", ret, m_libusb->p_libusb_error_name(ret));
|
|
continue;
|
|
}
|
|
|
|
if (m_libusb->p_libusb_kernel_driver_active(device_handle, 0) == 1)
|
|
{
|
|
ret = m_libusb->p_libusb_detach_kernel_driver(device_handle, 0);
|
|
if (ret != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_detach_kernel_driver: {}", ret, m_libusb->p_libusb_error_name(ret));
|
|
m_libusb->p_libusb_close(device_handle);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ret = m_libusb->p_libusb_claim_interface(device_handle, 0);
|
|
if (ret != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_claim_interface: {}", ret, m_libusb->p_libusb_error_name(ret));
|
|
m_libusb->p_libusb_close(device_handle);
|
|
continue;
|
|
}
|
|
|
|
libusb_config_descriptor* config = nullptr;
|
|
m_libusb->p_libusb_get_config_descriptor(devices[i], 0, &config);
|
|
for (auto ic = 0; ic < config->bNumInterfaces; ic++)
|
|
{
|
|
const auto& interface = config->interface[ic];
|
|
for (auto j = 0; j < interface.num_altsetting; j++)
|
|
{
|
|
const auto& interface_desc = interface.altsetting[j];
|
|
for (auto k = 0; k < interface_desc.bNumEndpoints; k++)
|
|
{
|
|
const auto& endpoint = interface_desc.endpoint[k];
|
|
if (endpoint.bEndpointAddress & LIBUSB_ENDPOINT_IN)
|
|
std::get<1>(result) = endpoint.bEndpointAddress;
|
|
else
|
|
std::get<2>(result) = endpoint.bEndpointAddress;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_libusb->p_libusb_free_config_descriptor(config);
|
|
|
|
// start polling
|
|
int size = 0;
|
|
uint8_t payload = 0x13;
|
|
m_libusb->p_libusb_interrupt_transfer(device_handle, std::get<2>(result), &payload, sizeof(payload), &size, 25);
|
|
|
|
std::get<0>(result) = device_handle;
|
|
|
|
break;
|
|
}
|
|
|
|
m_libusb->p_libusb_free_device_list(devices, 1);
|
|
return result;
|
|
}
|
|
|
|
void GameCubeControllerProvider::reader_thread()
|
|
{
|
|
SetThreadName("GCControllerAdapter::reader_thread");
|
|
while (m_running.load(std::memory_order_relaxed))
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
std::this_thread::yield();
|
|
|
|
for(auto& adapter : m_adapters)
|
|
{
|
|
if (!adapter.m_device_handle)
|
|
continue;
|
|
|
|
std::array<uint8_t, 37> data{};
|
|
int read;
|
|
const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_reader, data.data(), static_cast<int>(data.size()), &read, 25);
|
|
if (result == 0)
|
|
{
|
|
/*
|
|
byte 1
|
|
0x10 NORMAL STATE
|
|
0x20 WAVEBIRD STATE
|
|
0x04 RUMBLE POWER
|
|
*/
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
GCState state;
|
|
state.valid = true;
|
|
state.button = *(uint16*)&data[1 + (i * 9) + 1];
|
|
state.lstick_x = data[1 + (i * 9) + 3];
|
|
state.lstick_y = data[1 + (i * 9) + 4];
|
|
state.rstick_x = data[1 + (i * 9) + 5];
|
|
state.rstick_y = data[1 + (i * 9) + 6];
|
|
state.lstick = data[1 + (i * 9) + 7];
|
|
state.rstick = data[1 + (i * 9) + 8];
|
|
|
|
std::scoped_lock lock(adapter.m_state_mutex);
|
|
adapter.m_rumble_connected = HAS_FLAG(data[1], 4);
|
|
adapter.m_states[i] = state;
|
|
}
|
|
}
|
|
else if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_IO)
|
|
{
|
|
cemuLog_log(LogType::Force, "libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result));
|
|
if (const auto handle = adapter.m_device_handle.exchange(nullptr))
|
|
m_libusb->p_libusb_close(handle);
|
|
}
|
|
else { cemuLog_log(LogType::Force, "libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)); }
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameCubeControllerProvider::writer_thread()
|
|
{
|
|
SetThreadName("GCControllerAdapter::writer_thread");
|
|
|
|
std::array<std::array<bool, 4>, kMaxAdapters> rumble_states{};
|
|
|
|
while (m_running.load(std::memory_order_relaxed))
|
|
{
|
|
std::unique_lock lock(m_writer_mutex);
|
|
if (!m_rumble_changed && m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout)
|
|
{
|
|
if (!m_running)
|
|
return;
|
|
|
|
continue;
|
|
}
|
|
|
|
bool cmd_sent = false;
|
|
for (size_t i = 0; i < kMaxAdapters; ++i)
|
|
{
|
|
auto& adapter = m_adapters[i];
|
|
if (!adapter.m_device_handle)
|
|
continue;
|
|
|
|
if (adapter.rumble_states == rumble_states[i])
|
|
continue;
|
|
|
|
rumble_states[i] = adapter.rumble_states;
|
|
m_rumble_changed = false;
|
|
lock.unlock();
|
|
|
|
std::array<uint8, 5> rumble{ 0x11, rumble_states[i][0],rumble_states[i][1],rumble_states[i][2], rumble_states[i][3] };
|
|
|
|
int written;
|
|
const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_writer, rumble.data(), static_cast<int>(rumble.size()), &written, 25);
|
|
if (result != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)); }
|
|
cmd_sent = true;
|
|
|
|
lock.lock();
|
|
}
|
|
|
|
if(cmd_sent)
|
|
{
|
|
lock.unlock();
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|
|
}
|
|
|
|
int GameCubeControllerProvider::hotplug_event(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data)
|
|
{
|
|
auto* thisptr = static_cast<GameCubeControllerProvider*>(user_data);
|
|
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event)
|
|
{
|
|
for (auto i = 0; i < kMaxAdapters; ++i)
|
|
{
|
|
if (thisptr->m_adapters[i].m_device_handle)
|
|
continue;
|
|
|
|
auto device = thisptr->fetch_usb_device(i);
|
|
if (std::get<0>(device))
|
|
{
|
|
thisptr->m_adapters[i].m_endpoint_reader = std::get<1>(device);
|
|
thisptr->m_adapters[i].m_endpoint_writer = std::get<2>(device);
|
|
|
|
thisptr->m_adapters[i].m_device_handle = std::get<0>(device);
|
|
}
|
|
}
|
|
}
|
|
/*else
|
|
{
|
|
const auto device_handle = thisptr->m_device_handle.exchange(nullptr);
|
|
if (device_handle)
|
|
thisptr->m_libusb->p_libusb_close(device_handle);
|
|
}*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|