2023-08-26 15:47:52 +02:00
# include "stdafx.h"
# include "skateboard_pad_handler.h"
# include "Emu/Io/pad_config.h"
LOG_CHANNEL ( skateboard_log , " Skateboard " ) ;
2024-06-27 20:37:33 +02:00
using namespace reports ;
2023-08-26 15:47:52 +02:00
namespace
{
constexpr id_pair SKATEBOARD_ID_0 = { 0x12BA , 0x0400 } ; // Tony Hawk RIDE Skateboard
2024-09-05 17:36:57 +02:00
constexpr id_pair SKATEBOARD_ID_1 = { 0x1430 , 0x0100 } ; // Tony Hawk SHRED Skateboard
2023-08-26 15:47:52 +02:00
enum button_flags : u16
{
2025-04-05 21:50:45 +02:00
square = 0x0001 ,
cross = 0x0002 ,
circle = 0x0004 ,
2023-08-26 15:47:52 +02:00
triangle = 0x0008 ,
2025-04-05 21:50:45 +02:00
select = 0x0100 ,
start = 0x0200 ,
ps = 0x1000 ,
2023-08-26 15:47:52 +02:00
} ;
enum dpad_states : u8
{
2025-04-05 21:50:45 +02:00
up = 0x00 ,
up_right = 0x01 ,
left = 0x02 ,
2023-08-26 15:47:52 +02:00
down_right = 0x03 ,
2025-04-05 21:50:45 +02:00
down = 0x04 ,
down_left = 0x05 ,
right = 0x06 ,
up_left = 0x07 ,
none = 0x0F ,
2023-08-26 15:47:52 +02:00
} ;
// Default data if dongle is connected but the skateboard is turned off:
2025-04-05 21:50:45 +02:00
static constexpr std : : array < u8 , sizeof ( skateboard_input_report ) > not_connected_state = { 0x00 , 0x00 , 0x0F , 0x80 , 0x80 , 0x80 , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 , 0x00 , 0x02 , 0x00 , 0x02 , 0x00 , 0x02 } ;
static constexpr std : : array < u8 , sizeof ( skateboard_input_report ) > disconnected_state = { 0x00 , 0x00 , 0x0F , 0x80 , 0x80 , 0x80 , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0x00 , 0x00 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF } ;
} // namespace
2023-08-26 15:47:52 +02:00
2024-06-11 22:30:16 +02:00
skateboard_pad_handler : : skateboard_pad_handler ( )
2025-04-05 21:50:45 +02:00
: hid_pad_handler < skateboard_device > ( pad_handler : : skateboard , { SKATEBOARD_ID_0 , SKATEBOARD_ID_1 } )
2023-08-26 15:47:52 +02:00
{
// Unique names for the config files and our pad settings dialog
button_list =
2025-04-05 21:50:45 +02:00
{
{ skateboard_key_codes : : none , " " } ,
{ skateboard_key_codes : : left , " Left " } ,
{ skateboard_key_codes : : right , " Right " } ,
{ skateboard_key_codes : : up , " Up " } ,
{ skateboard_key_codes : : down , " Down " } ,
{ skateboard_key_codes : : cross , " Cross " } ,
{ skateboard_key_codes : : square , " Square " } ,
{ skateboard_key_codes : : circle , " Circle " } ,
{ skateboard_key_codes : : triangle , " Triangle " } ,
{ skateboard_key_codes : : start , " Start " } ,
{ skateboard_key_codes : : select , " Select " } ,
{ skateboard_key_codes : : ps , " PS " } ,
{ skateboard_key_codes : : ir_left , " IR Left " } ,
{ skateboard_key_codes : : ir_right , " IR Right " } ,
{ skateboard_key_codes : : ir_nose , " IR Nose " } ,
{ skateboard_key_codes : : ir_tail , " IR Tail " } ,
{ skateboard_key_codes : : tilt_left , " Tilt Left " } ,
{ skateboard_key_codes : : tilt_right , " Tilt Right " } } ;
2023-08-26 15:47:52 +02:00
init_configs ( ) ;
// Define border values
thumb_max = 255 ;
trigger_min = 0 ;
trigger_max = 255 ;
// Set capabilities
b_has_config = true ;
b_has_rumble = false ;
b_has_motion = true ;
b_has_deadzones = false ;
b_has_led = false ;
b_has_rgb = false ;
b_has_player_led = false ;
b_has_battery = false ;
2024-07-06 17:46:37 +02:00
b_has_battery_led = false ;
2023-08-26 15:47:52 +02:00
b_has_pressure_intensity_button = false ;
2025-01-08 01:22:50 +01:00
b_has_orientation = true ;
2023-08-26 15:47:52 +02:00
m_name_string = " Skateboard # " ;
m_max_devices = CELL_PAD_MAX_PORT_NUM ;
m_trigger_threshold = trigger_max / 2 ;
2025-04-05 21:50:45 +02:00
m_thumb_threshold = thumb_max / 2 ;
2023-08-26 15:47:52 +02:00
}
skateboard_pad_handler : : ~ skateboard_pad_handler ( )
{
}
void skateboard_pad_handler : : init_config ( cfg_pad * cfg )
{
2025-04-05 21:50:45 +02:00
if ( ! cfg )
return ;
2023-08-26 15:47:52 +02:00
// Set default button mapping
2025-04-05 21:50:45 +02:00
cfg - > ls_left . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > ls_down . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
2023-08-26 15:47:52 +02:00
cfg - > ls_right . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
2025-04-05 21:50:45 +02:00
cfg - > ls_up . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > rs_left . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > rs_down . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
2023-08-26 15:47:52 +02:00
cfg - > rs_right . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
2025-04-05 21:50:45 +02:00
cfg - > rs_up . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > start . def = : : at32 ( button_list , skateboard_key_codes : : start ) ;
cfg - > select . def = : : at32 ( button_list , skateboard_key_codes : : select ) ;
cfg - > ps . def = : : at32 ( button_list , skateboard_key_codes : : ps ) ;
cfg - > square . def = : : at32 ( button_list , skateboard_key_codes : : square ) ;
cfg - > cross . def = : : at32 ( button_list , skateboard_key_codes : : cross ) ;
cfg - > circle . def = : : at32 ( button_list , skateboard_key_codes : : circle ) ;
2023-08-26 15:47:52 +02:00
cfg - > triangle . def = : : at32 ( button_list , skateboard_key_codes : : triangle ) ;
2025-04-05 21:50:45 +02:00
cfg - > left . def = : : at32 ( button_list , skateboard_key_codes : : left ) ;
cfg - > down . def = : : at32 ( button_list , skateboard_key_codes : : down ) ;
cfg - > right . def = : : at32 ( button_list , skateboard_key_codes : : right ) ;
cfg - > up . def = : : at32 ( button_list , skateboard_key_codes : : up ) ;
cfg - > r1 . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > r2 . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > r3 . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > l1 . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > l2 . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > l3 . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
cfg - > ir_nose . def = : : at32 ( button_list , skateboard_key_codes : : ir_nose ) ;
cfg - > ir_tail . def = : : at32 ( button_list , skateboard_key_codes : : ir_tail ) ;
cfg - > ir_left . def = : : at32 ( button_list , skateboard_key_codes : : ir_left ) ;
cfg - > ir_right . def = : : at32 ( button_list , skateboard_key_codes : : ir_right ) ;
cfg - > tilt_left . def = : : at32 ( button_list , skateboard_key_codes : : tilt_left ) ;
2023-08-26 15:47:52 +02:00
cfg - > tilt_right . def = : : at32 ( button_list , skateboard_key_codes : : tilt_right ) ;
2025-01-08 01:22:50 +01:00
cfg - > orientation_reset_button . def = : : at32 ( button_list , skateboard_key_codes : : none ) ;
2023-08-26 15:47:52 +02:00
// Set default misc variables
2024-05-27 21:50:09 +02:00
cfg - > lstick_anti_deadzone . def = 0 ;
cfg - > rstick_anti_deadzone . def = 0 ;
2025-04-05 21:50:45 +02:00
cfg - > lstickdeadzone . def = 40 ; // between 0 and 255
cfg - > rstickdeadzone . def = 40 ; // between 0 and 255
cfg - > ltriggerthreshold . def = 0 ; // between 0 and 255
cfg - > rtriggerthreshold . def = 0 ; // between 0 and 255
2023-08-26 15:47:52 +02:00
// apply defaults
cfg - > from_default ( ) ;
}
2025-03-03 19:59:08 +01:00
void skateboard_pad_handler : : check_add_device ( hid_device * hidDevice , hid_enumerated_device_view path , std : : wstring_view wide_serial )
2023-08-26 15:47:52 +02:00
{
if ( ! hidDevice )
{
return ;
}
skateboard_device * device = nullptr ;
for ( auto & controller : m_controllers )
{
ensure ( controller . second ) ;
if ( ! controller . second - > hidDevice )
{
device = controller . second . get ( ) ;
break ;
}
}
if ( ! device )
{
return ;
}
if ( hid_set_nonblocking ( hidDevice , 1 ) = = - 1 )
{
skateboard_log . error ( " check_add_device: hid_set_nonblocking failed! Reason: %s " , hid_error ( hidDevice ) ) ;
hid_close ( hidDevice ) ;
return ;
}
device - > hidDevice = hidDevice ;
device - > path = path ;
// Activate
if ( send_output_report ( device ) = = - 1 )
{
skateboard_log . error ( " check_add_device: send_output_report failed! Reason: %s " , hid_error ( hidDevice ) ) ;
}
std : : string serial ;
for ( wchar_t ch : wide_serial )
serial + = static_cast < uchar > ( ch ) ;
skateboard_log . notice ( " Added device: serial='%s', path='%s' " , serial , device - > path ) ;
}
skateboard_pad_handler : : DataStatus skateboard_pad_handler : : get_data ( skateboard_device * device )
{
if ( ! device )
return DataStatus : : ReadError ;
std : : array < u8 , sizeof ( skateboard_input_report ) > buf { } ;
int res = hid_read ( device - > hidDevice , buf . data ( ) , buf . size ( ) ) ;
if ( res = = - 1 )
{
// looks like controller disconnected or read error
2024-06-30 11:32:44 +02:00
skateboard_log . error ( " get_data ReadError: %s " , hid_error ( device - > hidDevice ) ) ;
2023-08-26 15:47:52 +02:00
return DataStatus : : ReadError ;
}
if ( res ! = static_cast < int > ( sizeof ( skateboard_input_report ) ) )
return DataStatus : : NoNewData ;
2024-04-28 13:13:47 +02:00
if ( std : : memcmp ( & device - > report , buf . data ( ) , sizeof ( skateboard_input_report ) ) = = 0 )
2023-08-26 15:47:52 +02:00
return DataStatus : : NoNewData ;
// Get the new data
2024-04-28 13:13:47 +02:00
std : : memcpy ( & device - > report , buf . data ( ) , sizeof ( skateboard_input_report ) ) ;
2023-08-26 15:47:52 +02:00
// Check the skateboard's power state based on the input report
device - > skateboard_is_on =
( std : : memcmp ( not_connected_state . data ( ) , buf . data ( ) , not_connected_state . size ( ) ) ! = 0 & & // This usually means that the device hasn't been connected to the dongle yet.
2025-04-05 21:50:45 +02:00
std : : memcmp ( disconnected_state . data ( ) , buf . data ( ) , disconnected_state . size ( ) ) ! = 0 ) ; // This usually means that the device was disconnected from the dongle.
2023-08-26 15:47:52 +02:00
return DataStatus : : NewData ;
}
PadHandlerBase : : connection skateboard_pad_handler : : update_connection ( const std : : shared_ptr < PadDevice > & device )
{
2024-08-17 11:21:11 +02:00
skateboard_device * dev = static_cast < skateboard_device * > ( device . get ( ) ) ;
2025-03-03 19:59:08 +01:00
if ( ! dev | | dev - > path = = hid_enumerated_device_default )
2023-08-26 15:47:52 +02:00
return connection : : disconnected ;
2024-08-17 11:21:11 +02:00
if ( dev - > hidDevice = = nullptr )
2023-08-26 15:47:52 +02:00
{
// try to reconnect
2025-03-03 19:59:08 +01:00
# ifdef ANDROID
if ( hid_device * hid_dev = hid_libusb_wrap_sys_device ( dev - > path , - 1 ) )
# else
2024-08-17 11:21:11 +02:00
if ( hid_device * hid_dev = hid_open_path ( dev - > path . c_str ( ) ) )
2025-03-03 19:59:08 +01:00
# endif
2023-08-26 15:47:52 +02:00
{
2024-08-17 11:21:11 +02:00
if ( hid_set_nonblocking ( hid_dev , 1 ) = = - 1 )
2023-08-26 15:47:52 +02:00
{
2024-08-17 11:21:11 +02:00
skateboard_log . error ( " Reconnecting Device %s: hid_set_nonblocking failed with error %s " , dev - > path , hid_error ( hid_dev ) ) ;
2023-08-26 15:47:52 +02:00
}
2024-08-17 11:21:11 +02:00
dev - > hidDevice = hid_dev ;
2023-08-26 15:47:52 +02:00
}
else
{
// nope, not there
2024-08-17 11:21:11 +02:00
skateboard_log . error ( " Device %s: disconnected " , dev - > path ) ;
2023-08-26 15:47:52 +02:00
return connection : : disconnected ;
}
}
2024-08-17 11:21:11 +02:00
if ( get_data ( dev ) = = DataStatus : : ReadError )
2023-08-26 15:47:52 +02:00
{
// this also can mean disconnected, either way deal with it on next loop and reconnect
2024-08-17 11:21:11 +02:00
dev - > close ( ) ;
2023-08-26 15:47:52 +02:00
return connection : : no_data ;
}
2024-08-17 11:21:11 +02:00
if ( ! dev - > skateboard_is_on )
2023-08-26 15:47:52 +02:00
{
// This means that the dongle is still connected, but the skateboard is turned off.
// There is no need to reconnect the hid device again, we just have to check the input report for proper data.
// The game should get the proper disconnected state anyway.
return connection : : disconnected ;
}
return connection : : connected ;
}
std : : unordered_map < u64 , u16 > skateboard_pad_handler : : get_button_values ( const std : : shared_ptr < PadDevice > & device )
{
std : : unordered_map < u64 , u16 > key_buf ;
2024-04-28 13:13:47 +02:00
skateboard_device * dev = static_cast < skateboard_device * > ( device . get ( ) ) ;
if ( ! dev )
2023-08-26 15:47:52 +02:00
return key_buf ;
2024-04-28 13:13:47 +02:00
const skateboard_input_report & input = dev - > report ;
2023-08-26 15:47:52 +02:00
// D-Pad
2025-04-05 21:50:45 +02:00
key_buf [ skateboard_key_codes : : left ] = ( input . d_pad = = dpad_states : : left | | input . d_pad = = dpad_states : : up_left | | input . d_pad = = dpad_states : : down_left ) ? 255 : 0 ;
2024-04-28 13:13:47 +02:00
key_buf [ skateboard_key_codes : : right ] = ( input . d_pad = = dpad_states : : right | | input . d_pad = = dpad_states : : up_right | | input . d_pad = = dpad_states : : down_right ) ? 255 : 0 ;
2025-04-05 21:50:45 +02:00
key_buf [ skateboard_key_codes : : up ] = ( input . d_pad = = dpad_states : : up | | input . d_pad = = dpad_states : : up_left | | input . d_pad = = dpad_states : : up_right ) ? 255 : 0 ;
key_buf [ skateboard_key_codes : : down ] = ( input . d_pad = = dpad_states : : down | | input . d_pad = = dpad_states : : down_left | | input . d_pad = = dpad_states : : down_right ) ? 255 : 0 ;
2023-08-26 15:47:52 +02:00
// Face buttons
2025-04-05 21:50:45 +02:00
key_buf [ skateboard_key_codes : : cross ] = ( input . buttons & button_flags : : cross ) ? 255 : 0 ;
key_buf [ skateboard_key_codes : : square ] = ( input . buttons & button_flags : : square ) ? 255 : 0 ;
key_buf [ skateboard_key_codes : : circle ] = ( input . buttons & button_flags : : circle ) ? 255 : 0 ;
2024-04-28 13:13:47 +02:00
key_buf [ skateboard_key_codes : : triangle ] = ( input . buttons & button_flags : : triangle ) ? 255 : 0 ;
2025-04-05 21:50:45 +02:00
key_buf [ skateboard_key_codes : : start ] = ( input . buttons & button_flags : : start ) ? 255 : 0 ;
key_buf [ skateboard_key_codes : : select ] = ( input . buttons & button_flags : : select ) ? 255 : 0 ;
key_buf [ skateboard_key_codes : : ps ] = ( input . buttons & button_flags : : ps ) ? 255 : 0 ;
2023-08-26 15:47:52 +02:00
// Infrared
2025-04-05 21:50:45 +02:00
key_buf [ skateboard_key_codes : : ir_nose ] = input . pressure_triangle ;
key_buf [ skateboard_key_codes : : ir_tail ] = input . pressure_circle ;
key_buf [ skateboard_key_codes : : ir_left ] = input . pressure_cross ;
key_buf [ skateboard_key_codes : : ir_right ] = input . pressure_square ;
key_buf [ skateboard_key_codes : : tilt_left ] = input . pressure_l1 ;
2024-04-28 13:13:47 +02:00
key_buf [ skateboard_key_codes : : tilt_right ] = input . pressure_r1 ;
2023-08-26 15:47:52 +02:00
// NOTE: Axes X, Y, Z and RZ are always 128, which is the default anyway, so setting the values is omitted.
return key_buf ;
}
void skateboard_pad_handler : : get_extended_info ( const pad_ensemble & binding )
{
const auto & device = binding . device ;
const auto & pad = binding . pad ;
skateboard_device * dev = static_cast < skateboard_device * > ( device . get ( ) ) ;
if ( ! dev | | ! pad )
return ;
2024-04-28 13:13:47 +02:00
const skateboard_input_report & input = dev - > report ;
2023-08-26 15:47:52 +02:00
2024-04-28 13:13:47 +02:00
pad - > m_sensors [ 0 ] . m_value = Clamp0To1023 ( input . large_axes [ 0 ] ) ;
pad - > m_sensors [ 1 ] . m_value = Clamp0To1023 ( input . large_axes [ 1 ] ) ;
pad - > m_sensors [ 2 ] . m_value = Clamp0To1023 ( input . large_axes [ 2 ] ) ;
pad - > m_sensors [ 3 ] . m_value = Clamp0To1023 ( input . large_axes [ 3 ] ) ;
2025-01-08 01:22:50 +01:00
set_raw_orientation ( * pad ) ;
2023-08-26 15:47:52 +02:00
}
pad_preview_values skateboard_pad_handler : : get_preview_values ( const std : : unordered_map < u64 , u16 > & /*data*/ )
{
// There is no proper user interface for skateboard values yet
return { } ;
}
int skateboard_pad_handler : : send_output_report ( skateboard_device * device )
{
if ( ! device | | ! device - > hidDevice )
return - 2 ;
const cfg_pad * config = device - > config ;
if ( config = = nullptr )
return - 2 ; // hid_write returns -1 on error
// The output report contents are still unknown
skateboard_output_report report { } ;
return hid_write ( device - > hidDevice , report . data . data ( ) , report . data . size ( ) ) ;
}
void skateboard_pad_handler : : apply_pad_data ( const pad_ensemble & binding )
{
const auto & device = binding . device ;
const auto & pad = binding . pad ;
skateboard_device * dev = static_cast < skateboard_device * > ( device . get ( ) ) ;
if ( ! dev | | ! dev - > hidDevice | | ! dev - > config | | ! pad )
return ;
dev - > new_output_data = false ;
2024-08-17 11:21:11 +02:00
// Disabled until needed
2025-04-05 21:50:45 +02:00
// const auto now = steady_clock::now();
// const auto elapsed = now - dev->last_output;
2024-08-17 11:21:11 +02:00
2025-04-05 21:50:45 +02:00
// if (dev->new_output_data || elapsed > min_output_interval)
2024-08-17 11:21:11 +02:00
//{
// if (const int res = send_output_report(dev); res >= 0)
// {
// dev->new_output_data = false;
// dev->last_output = now;
// }
// else if (res == -1)
// {
// skateboard_log.error("apply_pad_data: send_output_report failed! error=%s", hid_error(dev->hidDevice));
// }
2025-04-05 21:50:45 +02:00
// }
2023-08-26 15:47:52 +02:00
}
void skateboard_pad_handler : : SetPadData ( const std : : string & padId , u8 player_id , u8 /*large_motor*/ , u8 /*small_motor*/ , s32 /*r*/ , s32 /*g*/ , s32 /*b*/ , bool /*player_led*/ , bool /*battery_led*/ , u32 /*battery_led_brightness*/ )
{
std : : shared_ptr < skateboard_device > device = get_hid_device ( padId ) ;
if ( device = = nullptr | | device - > hidDevice = = nullptr )
return ;
device - > player_id = player_id ;
device - > config = get_config ( padId ) ;
ensure ( device - > config ) ;
// Disabled until needed
2025-04-05 21:50:45 +02:00
// if (send_output_report(device.get()) == -1)
2024-08-16 23:46:31 +02:00
//{
// skateboard_log.error("SetPadData: send_output_report failed! Reason: %s", hid_error(device->hidDevice));
//}
2023-08-26 15:47:52 +02:00
}