mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	gc_adapter: Port code cleanup and feature updates from yuzu
Streamlines the code and introduces fixes for the origin status of the controller along with adapter hotplug support Co-Authored-By: Narr the Reg <5944268+german77@users.noreply.github.com> Co-Authored-By: LC <712067+lioncash@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									05e28a53e8
								
							
						
					
					
						commit
						700fa6b96a
					
				
					 3 changed files with 390 additions and 320 deletions
				
			
		|  | @ -4,201 +4,239 @@ | |||
| 
 | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
 | ||||
| #endif | ||||
| #include <libusb.h> | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
| 
 | ||||
| #include "common/logging/log.h" | ||||
| #include "common/param_package.h" | ||||
| #include "input_common/gcadapter/gc_adapter.h" | ||||
| 
 | ||||
| namespace GCAdapter { | ||||
| 
 | ||||
| /// Used to loop through and assign button in poller
 | ||||
| constexpr std::array<PadButton, 12> PadButtonArray{ | ||||
|     PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, | ||||
|     PadButton::PAD_BUTTON_UP,   PadButton::PAD_TRIGGER_Z,    PadButton::PAD_TRIGGER_R, | ||||
|     PadButton::PAD_TRIGGER_L,   PadButton::PAD_BUTTON_A,     PadButton::PAD_BUTTON_B, | ||||
|     PadButton::PAD_BUTTON_X,    PadButton::PAD_BUTTON_Y,     PadButton::PAD_BUTTON_START, | ||||
| }; | ||||
| 
 | ||||
| Adapter::Adapter() { | ||||
|     if (usb_adapter_handle != nullptr) { | ||||
|         return; | ||||
|     } | ||||
|     LOG_INFO(Input, "GC Adapter Initialization started"); | ||||
| 
 | ||||
|     const int init_res = libusb_init(&libusb_ctx); | ||||
|     if (init_res == LIBUSB_SUCCESS) { | ||||
|         Setup(); | ||||
|         adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); | ||||
|     } else { | ||||
|         LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { | ||||
|     GCPadStatus pad = {}; | ||||
|     const std::size_t offset = 1 + (9 * port); | ||||
| Adapter::~Adapter() { | ||||
|     JoinThreads(); | ||||
|     ClearLibusbHandle(); | ||||
|     ResetDevices(); | ||||
| 
 | ||||
|     adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); | ||||
|     if (libusb_ctx) { | ||||
|         libusb_exit(libusb_ctx); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Adapter::AdapterInputThread() { | ||||
|     LOG_DEBUG(Input, "GC Adapter input thread started"); | ||||
|     s32 payload_size{}; | ||||
|     AdapterPayload adapter_payload{}; | ||||
| 
 | ||||
|     if (adapter_scan_thread.joinable()) { | ||||
|         adapter_scan_thread.join(); | ||||
|     } | ||||
| 
 | ||||
|     while (adapter_input_thread_running) { | ||||
|         libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), | ||||
|                                   static_cast<s32>(adapter_payload.size()), &payload_size, 16); | ||||
|         if (IsPayloadCorrect(adapter_payload, payload_size)) { | ||||
|             UpdateControllers(adapter_payload); | ||||
|         } | ||||
|         std::this_thread::yield(); | ||||
|     } | ||||
| 
 | ||||
|     if (restart_scan_thread) { | ||||
|         adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); | ||||
|         restart_scan_thread = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { | ||||
|     if (payload_size != static_cast<s32>(adapter_payload.size()) || | ||||
|         adapter_payload[0] != LIBUSB_DT_HID) { | ||||
|         LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, | ||||
|                   adapter_payload[0]); | ||||
|         if (++input_error_counter > 20) { | ||||
|             LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); | ||||
|             adapter_input_thread_running = false; | ||||
|             restart_scan_thread = true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     input_error_counter = 0; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { | ||||
|     for (std::size_t port = 0; port < pads.size(); ++port) { | ||||
|         const std::size_t offset = 1 + (9 * port); | ||||
|         const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); | ||||
|         UpdatePadType(port, type); | ||||
|         if (DeviceConnected(port)) { | ||||
|             const u8 b1 = adapter_payload[offset + 1]; | ||||
|             const u8 b2 = adapter_payload[offset + 2]; | ||||
|             UpdateStateButtons(port, b1, b2); | ||||
|             UpdateStateAxes(port, adapter_payload); | ||||
|             if (configuring) { | ||||
|                 UpdateSettings(port); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { | ||||
|     if (pads[port].type == pad_type) { | ||||
|         return; | ||||
|     } | ||||
|     // Device changed reset device and set new type
 | ||||
|     ResetDevice(port); | ||||
|     pads[port].type = pad_type; | ||||
| } | ||||
| 
 | ||||
| void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { | ||||
|     if (port >= pads.size()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     static constexpr std::array<PadButton, 8> b1_buttons{ | ||||
|         PadButton::PAD_BUTTON_A,    PadButton::PAD_BUTTON_B,    PadButton::PAD_BUTTON_X, | ||||
|         PadButton::PAD_BUTTON_Y,    PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, | ||||
|         PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, | ||||
|         PadButton::ButtonA,    PadButton::ButtonB,     PadButton::ButtonX,    PadButton::ButtonY, | ||||
|         PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, | ||||
|     }; | ||||
| 
 | ||||
|     static constexpr std::array<PadButton, 4> b2_buttons{ | ||||
|         PadButton::PAD_BUTTON_START, | ||||
|         PadButton::PAD_TRIGGER_Z, | ||||
|         PadButton::PAD_TRIGGER_R, | ||||
|         PadButton::PAD_TRIGGER_L, | ||||
|         PadButton::ButtonStart, | ||||
|         PadButton::TriggerZ, | ||||
|         PadButton::TriggerR, | ||||
|         PadButton::TriggerL, | ||||
|     }; | ||||
|     pads[port].buttons = 0; | ||||
|     for (std::size_t i = 0; i < b1_buttons.size(); ++i) { | ||||
|         if ((b1 & (1U << i)) != 0) { | ||||
|             pads[port].buttons = | ||||
|                 static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i])); | ||||
|             pads[port].last_button = b1_buttons[i]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (std::size_t j = 0; j < b2_buttons.size(); ++j) { | ||||
|         if ((b2 & (1U << j)) != 0) { | ||||
|             pads[port].buttons = | ||||
|                 static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j])); | ||||
|             pads[port].last_button = b2_buttons[j]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { | ||||
|     if (port >= pads.size()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const std::size_t offset = 1 + (9 * port); | ||||
|     static constexpr std::array<PadAxes, 6> axes{ | ||||
|         PadAxes::StickX,    PadAxes::StickY,      PadAxes::SubstickX, | ||||
|         PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, | ||||
|     }; | ||||
| 
 | ||||
|     if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) { | ||||
|         // Controller may have been disconnected, recalibrate if reconnected.
 | ||||
|         get_origin[port] = true; | ||||
|     } | ||||
| 
 | ||||
|     if (adapter_controllers_status[port] != ControllerTypes::None) { | ||||
|         const u8 b1 = adapter_payload[offset + 1]; | ||||
|         const u8 b2 = adapter_payload[offset + 2]; | ||||
| 
 | ||||
|         for (std::size_t i = 0; i < b1_buttons.size(); ++i) { | ||||
|             if ((b1 & (1U << i)) != 0) { | ||||
|                 pad.button |= static_cast<u16>(b1_buttons[i]); | ||||
|             } | ||||
|     for (const PadAxes axis : axes) { | ||||
|         const auto index = static_cast<std::size_t>(axis); | ||||
|         const u8 axis_value = adapter_payload[offset + 3 + index]; | ||||
|         if (pads[port].axis_origin[index] == 255) { | ||||
|             pads[port].axis_origin[index] = axis_value; | ||||
|         } | ||||
| 
 | ||||
|         for (std::size_t j = 0; j < b2_buttons.size(); ++j) { | ||||
|             if ((b2 & (1U << j)) != 0) { | ||||
|                 pad.button |= static_cast<u16>(b2_buttons[j]); | ||||
|             } | ||||
|         } | ||||
|         for (PadAxes axis : axes) { | ||||
|             const std::size_t index = static_cast<std::size_t>(axis); | ||||
|             pad.axis_values[index] = adapter_payload[offset + 3 + index]; | ||||
|         } | ||||
| 
 | ||||
|         if (get_origin[port]) { | ||||
|             origin_status[port].axis_values = pad.axis_values; | ||||
|             get_origin[port] = false; | ||||
|         } | ||||
|     } | ||||
|     return pad; | ||||
| } | ||||
| 
 | ||||
| void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { | ||||
|     for (const auto& button : PadButtonArray) { | ||||
|         const u16 button_value = static_cast<u16>(button); | ||||
|         state.buttons.insert_or_assign(button_value, pad.button & button_value); | ||||
|     } | ||||
| 
 | ||||
|     for (size_t i = 0; i < pad.axis_values.size(); ++i) { | ||||
|         state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]); | ||||
|         pads[port].axis_values[index] = | ||||
|             static_cast<s16>(axis_value - pads[port].axis_origin[index]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Adapter::Read() { | ||||
|     LOG_DEBUG(Input, "GC Adapter Read() thread started"); | ||||
| void Adapter::UpdateSettings(std::size_t port) { | ||||
|     if (port >= pads.size()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     int payload_size; | ||||
|     std::array<u8, 37> adapter_payload; | ||||
|     std::array<GCPadStatus, 4> pads; | ||||
|     constexpr u8 axis_threshold = 50; | ||||
|     GCPadStatus pad_status = {port}; | ||||
| 
 | ||||
|     while (adapter_thread_running) { | ||||
|         libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), | ||||
|                                   sizeof(adapter_payload), &payload_size, 16); | ||||
|     if (pads[port].buttons != 0) { | ||||
|         pad_status.button = pads[port].last_button; | ||||
|         pad_queue.Push(pad_status); | ||||
|     } | ||||
| 
 | ||||
|         if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) { | ||||
|             LOG_ERROR(Input, | ||||
|                       "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?", | ||||
|                       payload_size, adapter_payload[0]); | ||||
|             adapter_thread_running = false; // error reading from adapter, stop reading.
 | ||||
|             break; | ||||
|     // Accounting for a threshold here to ensure an intentional press
 | ||||
|     for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { | ||||
|         const s16 value = pads[port].axis_values[i]; | ||||
| 
 | ||||
|         if (value > axis_threshold || value < -axis_threshold) { | ||||
|             pad_status.axis = static_cast<PadAxes>(i); | ||||
|             pad_status.axis_value = value; | ||||
|             pad_status.axis_threshold = axis_threshold; | ||||
|             pad_queue.Push(pad_status); | ||||
|         } | ||||
|         for (std::size_t port = 0; port < pads.size(); ++port) { | ||||
|             pads[port] = GetPadStatus(port, adapter_payload); | ||||
|             if (DeviceConnected(port) && configuring) { | ||||
|                 if (pads[port].button != 0) { | ||||
|                     pad_queue[port].Push(pads[port]); | ||||
|                 } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|                 // Accounting for a threshold here to ensure an intentional press
 | ||||
|                 for (size_t i = 0; i < pads[port].axis_values.size(); ++i) { | ||||
|                     const u8 value = pads[port].axis_values[i]; | ||||
|                     const u8 origin = origin_status[port].axis_values[i]; | ||||
| 
 | ||||
|                     if (value > origin + pads[port].THRESHOLD || | ||||
|                         value < origin - pads[port].THRESHOLD) { | ||||
|                         pads[port].axis = static_cast<PadAxes>(i); | ||||
|                         pads[port].axis_value = pads[port].axis_values[i]; | ||||
|                         pad_queue[port].Push(pads[port]); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             PadToState(pads[port], state[port]); | ||||
|         } | ||||
|         std::this_thread::yield(); | ||||
| void Adapter::AdapterScanThread() { | ||||
|     adapter_scan_thread_running = true; | ||||
|     adapter_input_thread_running = false; | ||||
|     if (adapter_input_thread.joinable()) { | ||||
|         adapter_input_thread.join(); | ||||
|     } | ||||
|     ClearLibusbHandle(); | ||||
|     ResetDevices(); | ||||
|     while (adapter_scan_thread_running && !adapter_input_thread_running) { | ||||
|         Setup(); | ||||
|         std::this_thread::sleep_for(std::chrono::seconds(1)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Adapter::Setup() { | ||||
|     // Initialize all controllers as unplugged
 | ||||
|     adapter_controllers_status.fill(ControllerTypes::None); | ||||
|     // Initialize all ports to store axis origin values
 | ||||
|     get_origin.fill(true); | ||||
|     usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); | ||||
| 
 | ||||
|     // pointer to list of connected usb devices
 | ||||
|     libusb_device** devices{}; | ||||
| 
 | ||||
|     // populate the list of devices, get the count
 | ||||
|     const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices); | ||||
|     if (device_count < 0) { | ||||
|         LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count); | ||||
|     if (usb_adapter_handle == NULL) { | ||||
|         return; | ||||
|     } | ||||
|     if (!CheckDeviceAccess()) { | ||||
|         ClearLibusbHandle(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (devices != nullptr) { | ||||
|         for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) { | ||||
|             if (CheckDeviceAccess(devices[index])) { | ||||
|                 // GC Adapter found and accessible, registering it
 | ||||
|                 GetGCEndpoint(devices[index]); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         libusb_free_device_list(devices, 1); | ||||
|     libusb_device* device = libusb_get_device(usb_adapter_handle); | ||||
| 
 | ||||
|     LOG_INFO(Input, "GC adapter is now connected"); | ||||
|     // GC Adapter found and accessible, registering it
 | ||||
|     if (GetGCEndpoint(device)) { | ||||
|         adapter_scan_thread_running = false; | ||||
|         adapter_input_thread_running = true; | ||||
|         input_error_counter = 0; | ||||
|         adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Adapter::CheckDeviceAccess(libusb_device* device) { | ||||
|     libusb_device_descriptor desc; | ||||
|     const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); | ||||
|     if (get_descriptor_error) { | ||||
|         // could not acquire the descriptor, no point in trying to use it.
 | ||||
|         LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", | ||||
|                   get_descriptor_error); | ||||
|         return false; | ||||
| bool Adapter::CheckDeviceAccess() { | ||||
|     // This fixes payload problems from offbrand GCAdapters
 | ||||
|     const s32 control_transfer_error = | ||||
|         libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); | ||||
|     if (control_transfer_error < 0) { | ||||
|         LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); | ||||
|     } | ||||
| 
 | ||||
|     if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { | ||||
|         // This isn't the device we are looking for.
 | ||||
|         return false; | ||||
|     } | ||||
|     const int open_error = libusb_open(device, &usb_adapter_handle); | ||||
| 
 | ||||
|     if (open_error == LIBUSB_ERROR_ACCESS) { | ||||
|         LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", | ||||
|                   desc.idVendor, desc.idProduct); | ||||
|         return false; | ||||
|     } | ||||
|     if (open_error) { | ||||
|         LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); | ||||
|     s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); | ||||
|     if (kernel_driver_error == 1) { | ||||
|         kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); | ||||
|         if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { | ||||
|  | @ -224,13 +262,13 @@ bool Adapter::CheckDeviceAccess(libusb_device* device) { | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void Adapter::GetGCEndpoint(libusb_device* device) { | ||||
| bool Adapter::GetGCEndpoint(libusb_device* device) { | ||||
|     libusb_config_descriptor* config = nullptr; | ||||
|     const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); | ||||
|     if (config_descriptor_return != LIBUSB_SUCCESS) { | ||||
|         LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", | ||||
|                   config_descriptor_return); | ||||
|         return; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { | ||||
|  | @ -239,7 +277,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) { | |||
|             const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; | ||||
|             for (u8 e = 0; e < interface->bNumEndpoints; e++) { | ||||
|                 const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; | ||||
|                 if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { | ||||
|                 if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { | ||||
|                     input_endpoint = endpoint->bEndpointAddress; | ||||
|                 } else { | ||||
|                     output_endpoint = endpoint->bEndpointAddress; | ||||
|  | @ -252,78 +290,89 @@ void Adapter::GetGCEndpoint(libusb_device* device) { | |||
|     unsigned char clear_payload = 0x13; | ||||
|     libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, | ||||
|                               sizeof(clear_payload), nullptr, 16); | ||||
| 
 | ||||
|     adapter_thread_running = true; | ||||
|     adapter_input_thread = std::thread(&Adapter::Read, this); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| Adapter::~Adapter() { | ||||
|     Reset(); | ||||
| } | ||||
| void Adapter::JoinThreads() { | ||||
|     restart_scan_thread = false; | ||||
|     adapter_input_thread_running = false; | ||||
|     adapter_scan_thread_running = false; | ||||
| 
 | ||||
| void Adapter::Reset() { | ||||
|     if (adapter_thread_running) { | ||||
|         adapter_thread_running = false; | ||||
|     if (adapter_scan_thread.joinable()) { | ||||
|         adapter_scan_thread.join(); | ||||
|     } | ||||
| 
 | ||||
|     if (adapter_input_thread.joinable()) { | ||||
|         adapter_input_thread.join(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     adapter_controllers_status.fill(ControllerTypes::None); | ||||
|     get_origin.fill(true); | ||||
| 
 | ||||
| void Adapter::ClearLibusbHandle() { | ||||
|     if (usb_adapter_handle) { | ||||
|         libusb_release_interface(usb_adapter_handle, 1); | ||||
|         libusb_close(usb_adapter_handle); | ||||
|         usb_adapter_handle = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     if (libusb_ctx) { | ||||
|         libusb_exit(libusb_ctx); | ||||
| void Adapter::ResetDevices() { | ||||
|     for (std::size_t i = 0; i < pads.size(); ++i) { | ||||
|         ResetDevice(i); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Adapter::DeviceConnected(std::size_t port) { | ||||
|     return adapter_controllers_status[port] != ControllerTypes::None; | ||||
| void Adapter::ResetDevice(std::size_t port) { | ||||
|     pads[port].type = ControllerTypes::None; | ||||
|     pads[port].buttons = 0; | ||||
|     pads[port].last_button = PadButton::Undefined; | ||||
|     pads[port].axis_values.fill(0); | ||||
|     pads[port].axis_origin.fill(255); | ||||
| } | ||||
| 
 | ||||
| void Adapter::ResetDeviceType(std::size_t port) { | ||||
|     adapter_controllers_status[port] = ControllerTypes::None; | ||||
| std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { | ||||
|     std::vector<Common::ParamPackage> devices; | ||||
|     for (std::size_t port = 0; port < pads.size(); ++port) { | ||||
|         if (!DeviceConnected(port)) { | ||||
|             continue; | ||||
|         } | ||||
|         std::string name = fmt::format("Gamecube Controller {}", port + 1); | ||||
|         devices.emplace_back(Common::ParamPackage{ | ||||
|             {"class", "gcpad"}, | ||||
|             {"display", std::move(name)}, | ||||
|             {"port", std::to_string(port)}, | ||||
|         }); | ||||
|     } | ||||
|     return devices; | ||||
| } | ||||
| 
 | ||||
| bool Adapter::DeviceConnected(std::size_t port) const { | ||||
|     return pads[port].type != ControllerTypes::None; | ||||
| } | ||||
| 
 | ||||
| void Adapter::BeginConfiguration() { | ||||
|     get_origin.fill(true); | ||||
|     for (auto& pq : pad_queue) { | ||||
|         pq.Clear(); | ||||
|     } | ||||
|     pad_queue.Clear(); | ||||
|     configuring = true; | ||||
| } | ||||
| 
 | ||||
| void Adapter::EndConfiguration() { | ||||
|     for (auto& pq : pad_queue) { | ||||
|         pq.Clear(); | ||||
|     } | ||||
|     pad_queue.Clear(); | ||||
|     configuring = false; | ||||
| } | ||||
| 
 | ||||
| std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() { | ||||
| Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() { | ||||
|     return pad_queue; | ||||
| } | ||||
| 
 | ||||
| const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const { | ||||
| const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const { | ||||
|     return pad_queue; | ||||
| } | ||||
| 
 | ||||
| std::array<GCState, 4>& Adapter::GetPadState() { | ||||
|     return state; | ||||
| GCController& Adapter::GetPadState(std::size_t port) { | ||||
|     return pads.at(port); | ||||
| } | ||||
| 
 | ||||
| const std::array<GCState, 4>& Adapter::GetPadState() const { | ||||
|     return state; | ||||
| } | ||||
| 
 | ||||
| int Adapter::GetOriginValue(int port, int axis) const { | ||||
|     return origin_status[port].axis_values[axis]; | ||||
| const GCController& Adapter::GetPadState(std::size_t port) const { | ||||
|     return pads.at(port); | ||||
| } | ||||
| 
 | ||||
| } // namespace GCAdapter
 | ||||
|  |  | |||
|  | @ -17,27 +17,30 @@ struct libusb_context; | |||
| struct libusb_device; | ||||
| struct libusb_device_handle; | ||||
| 
 | ||||
| namespace Common { | ||||
| class ParamPackage; | ||||
| } | ||||
| 
 | ||||
| namespace GCAdapter { | ||||
| 
 | ||||
| enum class PadButton { | ||||
|     PAD_BUTTON_LEFT = 0x0001, | ||||
|     PAD_BUTTON_RIGHT = 0x0002, | ||||
|     PAD_BUTTON_DOWN = 0x0004, | ||||
|     PAD_BUTTON_UP = 0x0008, | ||||
|     PAD_TRIGGER_Z = 0x0010, | ||||
|     PAD_TRIGGER_R = 0x0020, | ||||
|     PAD_TRIGGER_L = 0x0040, | ||||
|     PAD_BUTTON_A = 0x0100, | ||||
|     PAD_BUTTON_B = 0x0200, | ||||
|     PAD_BUTTON_X = 0x0400, | ||||
|     PAD_BUTTON_Y = 0x0800, | ||||
|     PAD_BUTTON_START = 0x1000, | ||||
|     Undefined = 0x0000, | ||||
|     ButtonLeft = 0x0001, | ||||
|     ButtonRight = 0x0002, | ||||
|     ButtonDown = 0x0004, | ||||
|     ButtonUp = 0x0008, | ||||
|     TriggerZ = 0x0010, | ||||
|     TriggerR = 0x0020, | ||||
|     TriggerL = 0x0040, | ||||
|     ButtonA = 0x0100, | ||||
|     ButtonB = 0x0200, | ||||
|     ButtonX = 0x0400, | ||||
|     ButtonY = 0x0800, | ||||
|     ButtonStart = 0x1000, | ||||
|     // Below is for compatibility with "AxisButton" type
 | ||||
|     PAD_STICK = 0x2000, | ||||
|     Stick = 0x2000, | ||||
| }; | ||||
| 
 | ||||
| extern const std::array<PadButton, 12> PadButtonArray; | ||||
| 
 | ||||
| enum class PadAxes : u8 { | ||||
|     StickX, | ||||
|     StickY, | ||||
|  | @ -48,85 +51,103 @@ enum class PadAxes : u8 { | |||
|     Undefined, | ||||
| }; | ||||
| 
 | ||||
| enum class ControllerTypes { | ||||
|     None, | ||||
|     Wired, | ||||
|     Wireless, | ||||
| }; | ||||
| 
 | ||||
| struct GCPadStatus { | ||||
|     u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
 | ||||
|     std::size_t port{}; | ||||
| 
 | ||||
|     std::array<u8, 6> axis_values{};    // Triggers and sticks, following indices defined in PadAxes
 | ||||
|     static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling
 | ||||
|     PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
 | ||||
| 
 | ||||
|     u8 port{}; | ||||
|     PadAxes axis{PadAxes::Undefined}; | ||||
|     u8 axis_value{255}; | ||||
|     s16 axis_value{}; | ||||
|     u8 axis_threshold{50}; | ||||
| }; | ||||
| 
 | ||||
| struct GCState { | ||||
|     std::unordered_map<int, bool> buttons; | ||||
|     std::unordered_map<int, u16> axes; | ||||
| struct GCController { | ||||
|     ControllerTypes type{}; | ||||
|     u16 buttons{}; | ||||
|     PadButton last_button{}; | ||||
|     std::array<s16, 6> axis_values{}; | ||||
|     std::array<u8, 6> axis_origin{}; | ||||
| }; | ||||
| 
 | ||||
| enum class ControllerTypes { None, Wired, Wireless }; | ||||
| 
 | ||||
| class Adapter { | ||||
| public: | ||||
|     /// Initialize the GC Adapter capture and read sequence
 | ||||
|     Adapter(); | ||||
| 
 | ||||
|     /// Close the adapter read thread and release the adapter
 | ||||
|     ~Adapter(); | ||||
| 
 | ||||
|     /// Used for polling
 | ||||
|     void BeginConfiguration(); | ||||
|     void EndConfiguration(); | ||||
| 
 | ||||
|     Common::SPSCQueue<GCPadStatus>& GetPadQueue(); | ||||
|     const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const; | ||||
| 
 | ||||
|     GCController& GetPadState(std::size_t port); | ||||
|     const GCController& GetPadState(std::size_t port) const; | ||||
| 
 | ||||
|     /// Returns true if there is a device connected to port
 | ||||
|     bool DeviceConnected(std::size_t port); | ||||
|     bool DeviceConnected(std::size_t port) const; | ||||
| 
 | ||||
|     std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); | ||||
|     const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; | ||||
| 
 | ||||
|     std::array<GCState, 4>& GetPadState(); | ||||
|     const std::array<GCState, 4>& GetPadState() const; | ||||
| 
 | ||||
|     int GetOriginValue(int port, int axis) const; | ||||
|     std::vector<Common::ParamPackage> GetInputDevices() const; | ||||
| 
 | ||||
| private: | ||||
|     GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload); | ||||
|     using AdapterPayload = std::array<u8, 37>; | ||||
| 
 | ||||
|     void PadToState(const GCPadStatus& pad, GCState& state); | ||||
|     void UpdatePadType(std::size_t port, ControllerTypes pad_type); | ||||
|     void UpdateControllers(const AdapterPayload& adapter_payload); | ||||
|     void UpdateSettings(std::size_t port); | ||||
|     void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); | ||||
|     void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); | ||||
| 
 | ||||
|     void Read(); | ||||
|     void AdapterInputThread(); | ||||
| 
 | ||||
|     /// Resets status of device connected to port
 | ||||
|     void ResetDeviceType(std::size_t port); | ||||
|     void AdapterScanThread(); | ||||
| 
 | ||||
|     /// Returns true if we successfully gain access to GC Adapter
 | ||||
|     bool CheckDeviceAccess(libusb_device* device); | ||||
| 
 | ||||
|     /// Captures GC Adapter endpoint address,
 | ||||
|     void GetGCEndpoint(libusb_device* device); | ||||
| 
 | ||||
|     /// For shutting down, clear all data, join all threads, release usb
 | ||||
|     void Reset(); | ||||
|     bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); | ||||
| 
 | ||||
|     /// For use in initialization, querying devices to find the adapter
 | ||||
|     void Setup(); | ||||
| 
 | ||||
|     /// Resets status of all GC controller devices to a disconnected state
 | ||||
|     void ResetDevices(); | ||||
| 
 | ||||
|     /// Resets status of device connected to a disconnected state
 | ||||
|     void ResetDevice(std::size_t port); | ||||
| 
 | ||||
|     /// Returns true if we successfully gain access to GC Adapter
 | ||||
|     bool CheckDeviceAccess(); | ||||
| 
 | ||||
|     /// Captures GC Adapter endpoint address
 | ||||
|     /// Returns true if the endpoint was set correctly
 | ||||
|     bool GetGCEndpoint(libusb_device* device); | ||||
| 
 | ||||
|     // Join all threads
 | ||||
|     void JoinThreads(); | ||||
| 
 | ||||
|     // Release usb handles
 | ||||
|     void ClearLibusbHandle(); | ||||
| 
 | ||||
|     libusb_device_handle* usb_adapter_handle = nullptr; | ||||
|     std::array<GCController, 4> pads; | ||||
|     Common::SPSCQueue<GCPadStatus> pad_queue; | ||||
| 
 | ||||
|     std::thread adapter_input_thread; | ||||
|     bool adapter_thread_running; | ||||
|     std::thread adapter_scan_thread; | ||||
|     bool adapter_input_thread_running; | ||||
|     bool adapter_scan_thread_running; | ||||
|     bool restart_scan_thread; | ||||
| 
 | ||||
|     libusb_context* libusb_ctx; | ||||
| 
 | ||||
|     u8 input_endpoint = 0; | ||||
|     u8 output_endpoint = 0; | ||||
|     u8 input_endpoint{0}; | ||||
|     u8 output_endpoint{0}; | ||||
|     u8 input_error_counter{0}; | ||||
| 
 | ||||
|     bool configuring = false; | ||||
| 
 | ||||
|     std::array<GCState, 4> state; | ||||
|     std::array<bool, 4> get_origin; | ||||
|     std::array<GCPadStatus, 4> origin_status; | ||||
|     std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; | ||||
|     std::array<ControllerTypes, 4> adapter_controllers_status{}; | ||||
|     bool configuring{false}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace GCAdapter
 | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ public: | |||
| 
 | ||||
|     bool GetStatus() const override { | ||||
|         if (gcadapter->DeviceConnected(port)) { | ||||
|             return gcadapter->GetPadState()[port].buttons.at(button); | ||||
|             return (gcadapter->GetPadState(port).buttons & button) != 0; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | @ -38,16 +38,13 @@ public: | |||
|     explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, | ||||
|                           GCAdapter::Adapter* adapter) | ||||
|         : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), | ||||
|           gcadapter(adapter), | ||||
|           origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {} | ||||
|           gcadapter(adapter) {} | ||||
| 
 | ||||
|     bool GetStatus() const override { | ||||
|         if (gcadapter->DeviceConnected(port)) { | ||||
|             const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis); | ||||
|             const float axis_value = (current_axis_value - origin_value) / 128.0f; | ||||
|             const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); | ||||
|             const float axis_value = current_axis_value / 128.0f; | ||||
|             if (trigger_if_greater) { | ||||
|                 // TODO: Might be worthwile to set a slider for the trigger threshold. It is
 | ||||
|                 // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
 | ||||
|                 return axis_value > threshold; | ||||
|             } | ||||
|             return axis_value < -threshold; | ||||
|  | @ -56,12 +53,11 @@ public: | |||
|     } | ||||
| 
 | ||||
| private: | ||||
|     const int port; | ||||
|     const int axis; | ||||
|     const u32 port; | ||||
|     const u32 axis; | ||||
|     float threshold; | ||||
|     bool trigger_if_greater; | ||||
|     GCAdapter::Adapter* gcadapter; | ||||
|     const float origin_value; | ||||
|     const GCAdapter::Adapter* gcadapter; | ||||
| }; | ||||
| 
 | ||||
| GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) | ||||
|  | @ -73,7 +69,7 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param | |||
|     const int button_id = params.Get("button", 0); | ||||
|     const int port = params.Get("port", 0); | ||||
| 
 | ||||
|     constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK); | ||||
|     constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick); | ||||
| 
 | ||||
|     // button is not an axis/stick button
 | ||||
|     if (button_id != PAD_STICK_ID) { | ||||
|  | @ -106,32 +102,25 @@ Common::ParamPackage GCButtonFactory::GetNextInput() { | |||
|     Common::ParamPackage params; | ||||
|     GCAdapter::GCPadStatus pad; | ||||
|     auto& queue = adapter->GetPadQueue(); | ||||
|     for (std::size_t port = 0; port < queue.size(); ++port) { | ||||
|         while (queue[port].Pop(pad)) { | ||||
|             // This while loop will break on the earliest detected button
 | ||||
|             params.Set("engine", "gcpad"); | ||||
|             params.Set("port", static_cast<int>(port)); | ||||
|             for (const auto& button : GCAdapter::PadButtonArray) { | ||||
|                 const u16 button_value = static_cast<u16>(button); | ||||
|                 if (pad.button & button_value) { | ||||
|                     params.Set("button", button_value); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|     while (queue.Pop(pad)) { | ||||
|         // This while loop will break on the earliest detected button
 | ||||
|         params.Set("engine", "gcpad"); | ||||
|         params.Set("port", static_cast<s32>(pad.port)); | ||||
|         if (pad.button != GCAdapter::PadButton::Undefined) { | ||||
|             params.Set("button", static_cast<u16>(pad.button)); | ||||
|         } | ||||
| 
 | ||||
|             // For Axis button implementation
 | ||||
|             if (pad.axis != GCAdapter::PadAxes::Undefined) { | ||||
|                 params.Set("axis", static_cast<u8>(pad.axis)); | ||||
|                 params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK)); | ||||
|                 if (pad.axis_value > 128) { | ||||
|                     params.Set("direction", "+"); | ||||
|                     params.Set("threshold", "0.25"); | ||||
|                 } else { | ||||
|                     params.Set("direction", "-"); | ||||
|                     params.Set("threshold", "-0.25"); | ||||
|                 } | ||||
|                 break; | ||||
|         // For Axis button implementation
 | ||||
|         if (pad.axis != GCAdapter::PadAxes::Undefined) { | ||||
|             params.Set("axis", static_cast<u8>(pad.axis)); | ||||
|             params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick)); | ||||
|             params.Set("threshold", "0.25"); | ||||
|             if (pad.axis_value > 0) { | ||||
|                 params.Set("direction", "+"); | ||||
|             } else { | ||||
|                 params.Set("direction", "-"); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return params; | ||||
|  | @ -149,26 +138,23 @@ void GCButtonFactory::Stop() { | |||
| 
 | ||||
| class GCAnalog final : public Input::AnalogDevice { | ||||
| public: | ||||
|     GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter, | ||||
|              float range_) | ||||
|         : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), | ||||
|           origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))), | ||||
|           origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))), | ||||
|           range(range_) {} | ||||
|     explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, float deadzone_, | ||||
|                       const GCAdapter::Adapter* adapter) | ||||
|         : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {} | ||||
| 
 | ||||
|     float GetAxis(int axis) const { | ||||
|     float GetAxis(u32 axis) const { | ||||
|         if (gcadapter->DeviceConnected(port)) { | ||||
|             std::lock_guard lock{mutex}; | ||||
|             const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; | ||||
|             return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); | ||||
|             const auto axis_value = | ||||
|                 static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis)); | ||||
|             return (axis_value) / 50.0f; | ||||
|         } | ||||
|         return 0.0f; | ||||
|     } | ||||
| 
 | ||||
|     std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { | ||||
|         float x = GetAxis(axis_x); | ||||
|         float y = GetAxis(axis_y); | ||||
| 
 | ||||
|     std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { | ||||
|         float x = GetAxis(analog_axis_x); | ||||
|         float y = GetAxis(analog_axis_y); | ||||
|         // Make sure the coordinates are in the unit circle,
 | ||||
|         // otherwise normalize it.
 | ||||
|         float r = x * x + y * y; | ||||
|  | @ -192,14 +178,11 @@ public: | |||
|     } | ||||
| 
 | ||||
| private: | ||||
|     const int port; | ||||
|     const int axis_x; | ||||
|     const int axis_y; | ||||
|     const u32 port; | ||||
|     const u32 axis_x; | ||||
|     const u32 axis_y; | ||||
|     const float deadzone; | ||||
|     GCAdapter::Adapter* gcadapter; | ||||
|     const float origin_value_x; | ||||
|     const float origin_value_y; | ||||
|     const float range; | ||||
|     const GCAdapter::Adapter* gcadapter; | ||||
|     mutable std::mutex mutex; | ||||
| }; | ||||
| 
 | ||||
|  | @ -215,13 +198,12 @@ GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) | |||
|  *     - "axis_y": the index of the axis to be bind as y-axis | ||||
|  */ | ||||
| std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { | ||||
|     const int port = params.Get("port", 0); | ||||
|     const int axis_x = params.Get("axis_x", 0); | ||||
|     const int axis_y = params.Get("axis_y", 1); | ||||
|     const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); | ||||
|     const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); | ||||
|     const auto port = static_cast<u32>(params.Get("port", 0)); | ||||
|     const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); | ||||
|     const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); | ||||
|     const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); | ||||
| 
 | ||||
|     return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range); | ||||
|     return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get()); | ||||
| } | ||||
| 
 | ||||
| void GCAnalogFactory::Start() { | ||||
|  | @ -236,26 +218,44 @@ void GCAnalogFactory::Stop() { | |||
| 
 | ||||
| Common::ParamPackage GCAnalogFactory::GetNextInput() { | ||||
|     GCAdapter::GCPadStatus pad; | ||||
|     Common::ParamPackage params; | ||||
|     auto& queue = adapter->GetPadQueue(); | ||||
|     for (std::size_t port = 0; port < queue.size(); ++port) { | ||||
|         while (queue[port].Pop(pad)) { | ||||
|             if (pad.axis == GCAdapter::PadAxes::Undefined || | ||||
|                 std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.05) { | ||||
|                 continue; | ||||
|             } | ||||
|             // An analog device needs two axes, so we need to store the axis for later and wait for
 | ||||
|             // a second input event. The axes also must be from the same joystick.
 | ||||
|             const u8 axis = static_cast<u8>(pad.axis); | ||||
|             if (analog_x_axis == -1) { | ||||
|                 analog_x_axis = axis; | ||||
|                 controller_number = static_cast<int>(port); | ||||
|             } else if (analog_y_axis == -1 && analog_x_axis != axis && | ||||
|                        controller_number == static_cast<int>(port)) { | ||||
|                 analog_y_axis = axis; | ||||
|             } | ||||
|     while (queue.Pop(pad)) { | ||||
|         if (pad.button != GCAdapter::PadButton::Undefined) { | ||||
|             params.Set("engine", "gcpad"); | ||||
|             params.Set("port", static_cast<s32>(pad.port)); | ||||
|             params.Set("button", static_cast<u16>(pad.button)); | ||||
|             return params; | ||||
|         } | ||||
|         if (pad.axis == GCAdapter::PadAxes::Undefined || | ||||
|             std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) { | ||||
|             continue; | ||||
|         } | ||||
|         // An analog device needs two axes, so we need to store the axis for later and wait for
 | ||||
|         // a second input event. The axes also must be from the same joystick.
 | ||||
|         const u8 axis = static_cast<u8>(pad.axis); | ||||
|         if (axis == 0 || axis == 1) { | ||||
|             analog_x_axis = 0; | ||||
|             analog_y_axis = 1; | ||||
|             controller_number = static_cast<s32>(pad.port); | ||||
|             break; | ||||
|         } | ||||
|         if (axis == 2 || axis == 3) { | ||||
|             analog_x_axis = 2; | ||||
|             analog_y_axis = 3; | ||||
|             controller_number = static_cast<s32>(pad.port); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if (analog_x_axis == -1) { | ||||
|             analog_x_axis = axis; | ||||
|             controller_number = static_cast<s32>(pad.port); | ||||
|         } else if (analog_y_axis == -1 && analog_x_axis != axis && | ||||
|                    controller_number == static_cast<s32>(pad.port)) { | ||||
|             analog_y_axis = axis; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     Common::ParamPackage params; | ||||
|     if (analog_x_axis != -1 && analog_y_axis != -1) { | ||||
|         params.Set("engine", "gcpad"); | ||||
|         params.Set("port", controller_number); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue