mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
add gamecube controller support for all 4 ports
This commit is contained in:
@@ -25,6 +25,9 @@ Code.pul
|
||||
# Build output
|
||||
/build/
|
||||
/build-*/
|
||||
/.build-output/
|
||||
/.build-test/
|
||||
/native-build/
|
||||
/dist/
|
||||
/out/
|
||||
[Bb]in/
|
||||
|
||||
@@ -205,7 +205,7 @@ function(mkw_configure_product target)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${target} PRIVATE
|
||||
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
|
||||
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp setupapi winusb)
|
||||
|
||||
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
foreach(runtime_dll libc++.dll libunwind.dll)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
struct PADStatus;
|
||||
|
||||
namespace Wup028Adapter {
|
||||
|
||||
enum class ConnectionState : uint8_t {
|
||||
Searching,
|
||||
DriverError,
|
||||
Connected,
|
||||
};
|
||||
|
||||
struct AdapterInfo {
|
||||
ConnectionState state = ConnectionState::Searching;
|
||||
std::string deviceName;
|
||||
std::string detail;
|
||||
float pollRateHz = 0.0f;
|
||||
uint8_t inputEndpoint = 0;
|
||||
uint8_t outputEndpoint = 0;
|
||||
std::array<bool, 4> ports{};
|
||||
// Raw adapter type/status byte. High nibble 1 is wired, 2 is wireless.
|
||||
std::array<uint8_t, 4> portStatus{};
|
||||
std::array<uint64_t, 4> portChangeSequence{};
|
||||
};
|
||||
|
||||
// Starts the project-owned WUP-028 worker. Safe to call more than once.
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
// Returns true while an official adapter is open. Each entry is a native
|
||||
// GameCube port; an empty port is represented by PAD_ERR_NO_CONTROLLER.
|
||||
bool Read(std::array<PADStatus, 4>& statuses);
|
||||
// Returns true when the command belongs to an active WUP-028, allowing the
|
||||
// caller to avoid also sending it to Aurora's unrelated controller backend.
|
||||
bool SetRumble(uint32_t port, bool enabled);
|
||||
AdapterInfo GetInfo();
|
||||
|
||||
} // namespace Wup028Adapter
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "hle_stubs.h"
|
||||
#include "memory.h"
|
||||
#include "hle/controller_status_contract.h"
|
||||
#include "wup028_adapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
@@ -33,6 +34,7 @@ void WritePadStatus(uint32_t base, const PADStatus& status) {
|
||||
|
||||
extern "C" uint32_t PAD__Init_HLE()
|
||||
{
|
||||
Wup028Adapter::Initialize();
|
||||
return PADInit() ? 1u : 0u;
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ());
|
||||
@@ -44,7 +46,18 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr)
|
||||
}
|
||||
|
||||
PADStatus statuses[PAD_CHANMAX]{};
|
||||
const uint32_t rumbleMask = PADRead(statuses);
|
||||
std::array<PADStatus, PAD_CHANMAX> adapterStatuses{};
|
||||
uint32_t rumbleMask = 0;
|
||||
if (Wup028Adapter::Read(adapterStatuses)) {
|
||||
std::copy(adapterStatuses.begin(), adapterStatuses.end(), statuses);
|
||||
for (uint32_t port = 0; port < PAD_CHANMAX; ++port) {
|
||||
if (statuses[port].err == PAD_ERR_NONE) {
|
||||
rumbleMask |= PAD_CHAN0_BIT >> port;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rumbleMask = PADRead(statuses);
|
||||
}
|
||||
|
||||
try {
|
||||
for (uint32_t i = 0; i < PAD_CHANMAX; ++i) {
|
||||
@@ -73,6 +86,8 @@ PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), (
|
||||
|
||||
extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command)
|
||||
{
|
||||
PADControlMotor(chan, command);
|
||||
if (!Wup028Adapter::SetRumble(static_cast<uint32_t>(chan), command == PAD_MOTOR_RUMBLE)) {
|
||||
PADControlMotor(chan, command);
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command));
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "system_bridge.h"
|
||||
#include "ppc_runtime.h"
|
||||
#include "aurora_events.h"
|
||||
#include "wup028_adapter.h"
|
||||
#include "fiber_manager.h"
|
||||
#include "hle_stubs.h"
|
||||
#include "runtime_config.h"
|
||||
@@ -1252,6 +1253,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
}
|
||||
aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait);
|
||||
GxGuestWrite::InstallAuroraHooks();
|
||||
Wup028Adapter::Initialize();
|
||||
UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width,
|
||||
auroraInfo.windowSize.native_fb_height);
|
||||
settings_overlay::InitializeRuntimeSettings();
|
||||
@@ -1293,6 +1295,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
// Shutdown fiber system
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
Wup028Adapter::Shutdown();
|
||||
aurora_shutdown();
|
||||
SetRuntimeExitCodeImpl(0);
|
||||
ShutdownProcessTranscript();
|
||||
@@ -1310,6 +1313,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
SetRuntimeExitCodeImpl(1);
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
Wup028Adapter::Shutdown();
|
||||
aurora_shutdown();
|
||||
ShutdownProcessTranscript();
|
||||
return 1;
|
||||
@@ -1321,6 +1325,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
SetRuntimeExitCodeImpl(1);
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
Wup028Adapter::Shutdown();
|
||||
aurora_shutdown();
|
||||
ShutdownProcessTranscript();
|
||||
return 1;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "settings_overlay.h"
|
||||
#include "wup028_adapter.h"
|
||||
#include "audio_backend.h"
|
||||
#include "game_graphics_options.h"
|
||||
#include "music_attenuation.h"
|
||||
@@ -319,6 +320,30 @@ void DrawControllerSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GameCube controller adapter")) {
|
||||
const auto adapter = Wup028Adapter::GetInfo();
|
||||
const char* state = adapter.state == Wup028Adapter::ConnectionState::Connected
|
||||
? "Connected"
|
||||
: adapter.state == Wup028Adapter::ConnectionState::DriverError ? "Driver error"
|
||||
: "Searching";
|
||||
ImGui::Text("Status: %s", state);
|
||||
if (!adapter.deviceName.empty()) ImGui::Text("Device: %s", adapter.deviceName.c_str());
|
||||
ImGui::TextWrapped("%s", adapter.detail.c_str());
|
||||
if (adapter.state == Wup028Adapter::ConnectionState::Connected) {
|
||||
ImGui::Text("Poll rate: %.1f reports/s", adapter.pollRateHz);
|
||||
ImGui::Text("Endpoints: IN 0x%02X, OUT 0x%02X", adapter.inputEndpoint, adapter.outputEndpoint);
|
||||
for (size_t port = 0; port < adapter.ports.size(); ++port) {
|
||||
const uint8_t type = adapter.portStatus[port] & 0x30;
|
||||
const char* typeName = type == 0x10 ? "wired" : type == 0x20 ? "wireless" : "none";
|
||||
ImGui::Text("Adapter port %u: %s (type %s, raw 0x%02X)",
|
||||
static_cast<unsigned>(port + 1),
|
||||
adapter.ports[port] ? "Controller connected" : "Empty", typeName,
|
||||
adapter.portStatus[port]);
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
const uint32_t controllerCount = PADCount();
|
||||
if (controllerCount == 0) {
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
#include "wup028_adapter.h"
|
||||
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <dolphin/pad.h>
|
||||
#include <windows.h>
|
||||
#include <initguid.h>
|
||||
#include <setupapi.h>
|
||||
#include <usb.h>
|
||||
#include <usbiodef.h>
|
||||
#include <winusb.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cwctype>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace Wup028Adapter {
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kNintendoVendor = 0x057e;
|
||||
constexpr uint16_t kAdapterProduct = 0x0337;
|
||||
constexpr size_t kReportSize = 37;
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::array<PADStatus, PAD_CHANMAX> g_statuses{};
|
||||
std::array<uint8_t, PAD_CHANMAX> g_rumble{};
|
||||
std::thread g_worker;
|
||||
std::atomic_bool g_stop{false};
|
||||
std::atomic_bool g_running{false};
|
||||
std::atomic_bool g_connected{false};
|
||||
AdapterInfo g_info;
|
||||
|
||||
struct Device {
|
||||
HANDLE file = INVALID_HANDLE_VALUE;
|
||||
HANDLE event = nullptr;
|
||||
WINUSB_INTERFACE_HANDLE usb = nullptr;
|
||||
UCHAR inputPipe = 0;
|
||||
UCHAR outputPipe = 0;
|
||||
|
||||
~Device() { Close(); }
|
||||
void Close() {
|
||||
if (usb != nullptr) WinUsb_Free(usb);
|
||||
if (event != nullptr) CloseHandle(event);
|
||||
if (file != INVALID_HANDLE_VALUE) CloseHandle(file);
|
||||
usb = nullptr;
|
||||
event = nullptr;
|
||||
file = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
};
|
||||
|
||||
bool Transfer(Device& device, bool input, UCHAR pipe, UCHAR* data, ULONG size, ULONG& transferred,
|
||||
DWORD timeoutMs, bool* timedOut = nullptr) {
|
||||
if (timedOut != nullptr) *timedOut = false;
|
||||
ResetEvent(device.event);
|
||||
OVERLAPPED operation{};
|
||||
operation.hEvent = device.event;
|
||||
const BOOL started = input ? WinUsb_ReadPipe(device.usb, pipe, data, size, &transferred, &operation)
|
||||
: WinUsb_WritePipe(device.usb, pipe, data, size, &transferred, &operation);
|
||||
if (started) return true;
|
||||
if (GetLastError() != ERROR_IO_PENDING) return false;
|
||||
const DWORD wait = WaitForSingleObject(device.event, timeoutMs);
|
||||
if (wait == WAIT_OBJECT_0) {
|
||||
return WinUsb_GetOverlappedResult(device.usb, &operation, &transferred, FALSE);
|
||||
}
|
||||
CancelIoEx(device.file, &operation);
|
||||
WaitForSingleObject(device.event, INFINITE);
|
||||
WinUsb_GetOverlappedResult(device.usb, &operation, &transferred, FALSE);
|
||||
if (timedOut != nullptr && wait == WAIT_TIMEOUT) *timedOut = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct DeviceMatch {
|
||||
std::wstring path;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::string WideToUtf8(const wchar_t* value) {
|
||||
if (value == nullptr || *value == L'\0') return {};
|
||||
const int size = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr);
|
||||
if (size <= 1) return {};
|
||||
std::string result(static_cast<size_t>(size), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, value, -1, result.data(), size, nullptr, nullptr);
|
||||
result.resize(static_cast<size_t>(size - 1));
|
||||
return result;
|
||||
}
|
||||
|
||||
DeviceMatch FindAdapter() {
|
||||
HDEVINFO devices = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr,
|
||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (devices == INVALID_HANDLE_VALUE) return {};
|
||||
|
||||
DeviceMatch result;
|
||||
for (DWORD index = 0;; ++index) {
|
||||
SP_DEVICE_INTERFACE_DATA iface{sizeof(iface)};
|
||||
if (!SetupDiEnumDeviceInterfaces(devices, nullptr, &GUID_DEVINTERFACE_USB_DEVICE, index, &iface)) break;
|
||||
|
||||
DWORD required = 0;
|
||||
SetupDiGetDeviceInterfaceDetailW(devices, &iface, nullptr, 0, &required, nullptr);
|
||||
if (required < sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W)) continue;
|
||||
std::vector<uint8_t> storage(required);
|
||||
auto* detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(storage.data());
|
||||
detail->cbSize = sizeof(*detail);
|
||||
SP_DEVINFO_DATA deviceInfo{sizeof(deviceInfo)};
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(devices, &iface, detail, required, nullptr, &deviceInfo)) continue;
|
||||
|
||||
std::wstring lower(detail->DevicePath);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
|
||||
if (lower.find(L"vid_057e") != std::wstring::npos && lower.find(L"pid_0337") != std::wstring::npos) {
|
||||
result.path = detail->DevicePath;
|
||||
std::array<wchar_t, 256> description{};
|
||||
if (SetupDiGetDeviceRegistryPropertyW(devices, &deviceInfo, SPDRP_FRIENDLYNAME, nullptr,
|
||||
reinterpret_cast<BYTE*>(description.data()),
|
||||
static_cast<DWORD>(description.size() * sizeof(wchar_t)), nullptr) ||
|
||||
SetupDiGetDeviceRegistryPropertyW(devices, &deviceInfo, SPDRP_DEVICEDESC, nullptr,
|
||||
reinterpret_cast<BYTE*>(description.data()),
|
||||
static_cast<DWORD>(description.size() * sizeof(wchar_t)), nullptr)) {
|
||||
result.name = WideToUtf8(description.data());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
SetupDiDestroyDeviceInfoList(devices);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Open(Device& device, std::string& name, std::string& error) {
|
||||
const auto match = FindAdapter();
|
||||
if (match.path.empty()) {
|
||||
error = "No VID 057E / PID 0337 adapter is present";
|
||||
return false;
|
||||
}
|
||||
name = match.name.empty() ? "WUP-028-compatible adapter" : match.name;
|
||||
device.file = CreateFileW(match.path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
|
||||
if (device.file == INVALID_HANDLE_VALUE || !WinUsb_Initialize(device.file, &device.usb)) {
|
||||
error = "WinUSB could not open the adapter (Windows error " + std::to_string(GetLastError()) + ")";
|
||||
device.Close();
|
||||
return false;
|
||||
}
|
||||
device.event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (device.event == nullptr) {
|
||||
error = "Could not create the WinUSB transfer event";
|
||||
device.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
USB_INTERFACE_DESCRIPTOR descriptor{};
|
||||
if (!WinUsb_QueryInterfaceSettings(device.usb, 0, &descriptor)) {
|
||||
error = "WinUSB could not query the adapter interface";
|
||||
device.Close();
|
||||
return false;
|
||||
}
|
||||
for (UCHAR index = 0; index < descriptor.bNumEndpoints; ++index) {
|
||||
WINUSB_PIPE_INFORMATION pipe{};
|
||||
if (!WinUsb_QueryPipe(device.usb, 0, index, &pipe)) continue;
|
||||
if ((pipe.PipeType == UsbdPipeTypeInterrupt || pipe.PipeType == UsbdPipeTypeBulk) &&
|
||||
USB_ENDPOINT_DIRECTION_IN(pipe.PipeId) && device.inputPipe == 0) {
|
||||
device.inputPipe = pipe.PipeId;
|
||||
} else if ((pipe.PipeType == UsbdPipeTypeInterrupt || pipe.PipeType == UsbdPipeTypeBulk) &&
|
||||
USB_ENDPOINT_DIRECTION_OUT(pipe.PipeId) && device.outputPipe == 0) {
|
||||
device.outputPipe = pipe.PipeId;
|
||||
}
|
||||
}
|
||||
if (device.inputPipe == 0 || device.outputPipe == 0) {
|
||||
error = "The adapter has no usable input/output endpoint";
|
||||
device.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
UCHAR command = 0x13; // Enable the adapter's 37-byte input stream.
|
||||
ULONG written = 0;
|
||||
if (!Transfer(device, false, device.outputPipe, &command, 1, written, 1000) || written != 1) {
|
||||
error = "The adapter rejected input initialization on endpoint 0x";
|
||||
const char hex[] = "0123456789ABCDEF";
|
||||
error += hex[device.outputPipe >> 4];
|
||||
error += hex[device.outputPipe & 15];
|
||||
device.Close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int8_t Axis(uint8_t raw) {
|
||||
return static_cast<int8_t>(std::clamp(static_cast<int>(raw) - 128, -128, 127));
|
||||
}
|
||||
|
||||
PADStatus DecodePort(const uint8_t* p) {
|
||||
PADStatus out{};
|
||||
// The WUP-028 protocol uses type 1 for wired pads and type 2 for
|
||||
// WaveBird/wireless receivers. Testing only bit 0x10 drops every type-2
|
||||
// controller and makes otherwise valid ports appear empty.
|
||||
if ((p[0] & 0x30) == 0) {
|
||||
out.err = PAD_ERR_NO_CONTROLLER;
|
||||
return out;
|
||||
}
|
||||
if (p[1] & 0x01) out.button |= PAD_BUTTON_A;
|
||||
if (p[1] & 0x02) out.button |= PAD_BUTTON_B;
|
||||
if (p[1] & 0x04) out.button |= PAD_BUTTON_X;
|
||||
if (p[1] & 0x08) out.button |= PAD_BUTTON_Y;
|
||||
if (p[1] & 0x10) out.button |= PAD_BUTTON_LEFT;
|
||||
if (p[1] & 0x20) out.button |= PAD_BUTTON_RIGHT;
|
||||
if (p[1] & 0x40) out.button |= PAD_BUTTON_DOWN;
|
||||
if (p[1] & 0x80) out.button |= PAD_BUTTON_UP;
|
||||
if (p[2] & 0x01) out.button |= PAD_BUTTON_START;
|
||||
if (p[2] & 0x02) out.button |= PAD_TRIGGER_Z;
|
||||
if (p[2] & 0x04) out.button |= PAD_TRIGGER_R;
|
||||
if (p[2] & 0x08) out.button |= PAD_TRIGGER_L;
|
||||
out.stickX = Axis(p[3]);
|
||||
out.stickY = Axis(p[4]);
|
||||
out.substickX = Axis(p[5]);
|
||||
out.substickY = Axis(p[6]);
|
||||
out.triggerL = p[7];
|
||||
out.triggerR = p[8];
|
||||
out.err = PAD_ERR_NONE;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool SendRumble(Device& device, const std::array<uint8_t, PAD_CHANMAX>& motors) {
|
||||
std::array<UCHAR, 5> report{{0x11, motors[0], motors[1], motors[2], motors[3]}};
|
||||
ULONG written = 0;
|
||||
return Transfer(device, false, device.outputPipe, report.data(), report.size(), written, 1000) &&
|
||||
written == report.size();
|
||||
}
|
||||
|
||||
bool RefreshInputStream(Device& device) {
|
||||
UCHAR command = 0x13;
|
||||
ULONG written = 0;
|
||||
return Transfer(device, false, device.outputPipe, &command, 1, written, 1000) && written == 1;
|
||||
}
|
||||
|
||||
void ClearConnectedPorts(const char* reason) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
for (size_t port = 0; port < g_info.ports.size(); ++port) {
|
||||
if (g_info.ports[port]) {
|
||||
g_info.ports[port] = false;
|
||||
++g_info.portChangeSequence[port];
|
||||
RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter port " << (port + 1)
|
||||
<< " controller disconnected (" << reason << ")" << std::endl;
|
||||
}
|
||||
g_statuses[port] = {};
|
||||
g_statuses[port].err = PAD_ERR_NO_CONTROLLER;
|
||||
g_info.portStatus[port] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Worker() {
|
||||
std::string lastError;
|
||||
while (!g_stop.load(std::memory_order_acquire)) {
|
||||
Device device;
|
||||
std::string name;
|
||||
std::string error;
|
||||
if (!Open(device, name, error)) {
|
||||
g_connected.store(false, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_info.state = error.starts_with("No VID") ? ConnectionState::Searching : ConnectionState::DriverError;
|
||||
g_info.deviceName = name;
|
||||
g_info.detail = error;
|
||||
g_info.pollRateHz = 0.0f;
|
||||
g_info.inputEndpoint = 0;
|
||||
g_info.outputEndpoint = 0;
|
||||
g_info.ports.fill(false);
|
||||
g_info.portStatus.fill(0);
|
||||
}
|
||||
if (error != lastError && !error.starts_with("No VID")) {
|
||||
RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter: " << error << std::endl;
|
||||
}
|
||||
lastError = error;
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
RT_LOG(RT_TAG_RUNTIME) << name << " connected (input endpoint 0x" << std::hex
|
||||
<< static_cast<unsigned>(device.inputPipe) << ", output endpoint 0x"
|
||||
<< static_cast<unsigned>(device.outputPipe) << std::dec << ")" << std::endl;
|
||||
lastError.clear();
|
||||
g_connected.store(true, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_info.state = ConnectionState::Connected;
|
||||
g_info.deviceName = name;
|
||||
g_info.detail = "Receiving native GameCube reports";
|
||||
g_info.inputEndpoint = device.inputPipe;
|
||||
g_info.outputEndpoint = device.outputPipe;
|
||||
}
|
||||
std::array<uint8_t, PAD_CHANMAX> sentRumble{};
|
||||
auto rateStart = std::chrono::steady_clock::now();
|
||||
uint32_t rateReports = 0;
|
||||
std::array<bool, PAD_CHANMAX> reportedPorts{};
|
||||
|
||||
while (!g_stop.load(std::memory_order_acquire)) {
|
||||
std::array<UCHAR, kReportSize> report{};
|
||||
ULONG read = 0;
|
||||
bool timedOut = false;
|
||||
if (!Transfer(device, true, device.inputPipe, report.data(), report.size(), read, 100, &timedOut)) {
|
||||
if (timedOut) continue;
|
||||
break;
|
||||
}
|
||||
if (read != report.size() || report[0] != 0x21) continue;
|
||||
++rateReports;
|
||||
std::array<PADStatus, PAD_CHANMAX> decoded{};
|
||||
for (size_t port = 0; port < decoded.size(); ++port) decoded[port] = DecodePort(report.data() + 1 + port * 9);
|
||||
std::array<uint8_t, PAD_CHANMAX> desired{};
|
||||
std::array<int8_t, PAD_CHANMAX> transitions{};
|
||||
bool refreshStream = false;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_statuses = decoded;
|
||||
desired = g_rumble;
|
||||
for (size_t port = 0; port < decoded.size(); ++port) {
|
||||
g_info.portStatus[port] = report[1 + port * 9];
|
||||
const bool present = decoded[port].err == PAD_ERR_NONE;
|
||||
if (present != reportedPorts[port]) {
|
||||
reportedPorts[port] = present;
|
||||
g_info.ports[port] = present;
|
||||
++g_info.portChangeSequence[port];
|
||||
transitions[port] = present ? 1 : -1;
|
||||
}
|
||||
}
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const float seconds = std::chrono::duration<float>(now - rateStart).count();
|
||||
if (seconds >= 1.0f) {
|
||||
g_info.pollRateHz = static_cast<float>(rateReports) / seconds;
|
||||
rateReports = 0;
|
||||
rateStart = now;
|
||||
refreshStream = true;
|
||||
}
|
||||
}
|
||||
for (size_t port = 0; port < transitions.size(); ++port) {
|
||||
if (transitions[port] != 0) {
|
||||
RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter port " << (port + 1) << " controller "
|
||||
<< (transitions[port] > 0 ? "connected" : "disconnected") << std::endl;
|
||||
}
|
||||
}
|
||||
if (desired != sentRumble) {
|
||||
if (!SendRumble(device, desired)) break;
|
||||
sentRumble = desired;
|
||||
}
|
||||
if (refreshStream && !RefreshInputStream(device)) break;
|
||||
}
|
||||
g_connected.store(false, std::memory_order_release);
|
||||
ClearConnectedPorts("adapter unavailable");
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_info.state = ConnectionState::Searching;
|
||||
g_info.detail = "Adapter disconnected; waiting for reconnect";
|
||||
g_info.pollRateHz = 0.0f;
|
||||
}
|
||||
if (!g_stop.load(std::memory_order_acquire)) {
|
||||
RT_LOG(RT_TAG_RUNTIME) << "WUP-028 GameCube adapter disconnected; waiting for reconnect" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Initialize() {
|
||||
bool expected = false;
|
||||
if (!g_running.compare_exchange_strong(expected, true)) return;
|
||||
for (auto& status : g_statuses) status.err = PAD_ERR_NO_CONTROLLER;
|
||||
g_stop.store(false, std::memory_order_release);
|
||||
g_worker = std::thread(Worker);
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!g_running.exchange(false)) return;
|
||||
g_stop.store(true, std::memory_order_release);
|
||||
if (g_worker.joinable()) g_worker.join();
|
||||
g_connected.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool Read(std::array<PADStatus, 4>& statuses) {
|
||||
if (!g_connected.load(std::memory_order_acquire)) return false;
|
||||
std::lock_guard lock(g_mutex);
|
||||
statuses = g_statuses;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetRumble(uint32_t port, bool enabled) {
|
||||
if (port >= g_rumble.size() || !g_connected.load(std::memory_order_acquire)) return false;
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_rumble[port] = enabled ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
AdapterInfo GetInfo() {
|
||||
std::lock_guard lock(g_mutex);
|
||||
return g_info;
|
||||
}
|
||||
|
||||
} // namespace Wup028Adapter
|
||||
Reference in New Issue
Block a user