@ -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 : : Trigger Z,
PadButton : : Trigger R,
PadButton : : Trigger L,
} ;
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