#include "libretro.h" #include "libretro_settings.h" #include "mednafen/mednafen-types.h" #include "mednafen/ss/ss.h" #include "mednafen/ss/smpc.h" #include "mednafen/state.h" #include #include //------------------------------------------------------------------------------ // Locals //------------------------------------------------------------------------------ static retro_environment_t environ_cb; /* cached during input_init_env */ #define MAX_CONTROLLERS 12 /* 2x 6 player adaptors */ static unsigned players = 2; static int astick_deadzone = 0; static int trigger_deadzone = 0; static float mouse_sensitivity = 1.0f; static unsigned geometry_width = 0; static unsigned geometry_height = 0; typedef union { uint8_t u8[ 32 ]; uint16_t gun_pos[ 2 ]; uint16_t buttons; } INPUT_DATA; // Controller state buffer (per player) static INPUT_DATA input_data[ MAX_CONTROLLERS ] = {0}; // Controller type (per player) static uint32_t input_type[ MAX_CONTROLLERS ] = {0}; #define INPUT_MODE_3D_PAD_ANALOG ( 1 << 0 ) // Set means analog mode. #define INPUT_MODE_3D_PAD_PREVIOUS_MASK ( 1 << 1 ) // Edge trigger helper. #define INPUT_MODE_MISSION_THROTTLE_LATCH ( 1 << 2 ) // Latch throttle enabled? #define INPUT_MODE_MISSION_THROTTLE_PREV ( 1 << 3 ) // Edge trigger helper. #define INPUT_MODE_DEFAULT 0 #define INPUT_MODE_DEFAULT_3D_PAD INPUT_MODE_3D_PAD_ANALOG // Mode switch for 3D Control Pad (per player) static uint16_t input_mode[ MAX_CONTROLLERS ] = {0}; static int16_t input_throttle_latch[ MAX_CONTROLLERS ] = {0}; //------------------------------------------------------------------------------ // Supported Devices //------------------------------------------------------------------------------ #define RETRO_DEVICE_SS_PAD RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_JOYPAD, 0 ) #define RETRO_DEVICE_SS_3D_PAD RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_ANALOG, 0 ) #define RETRO_DEVICE_SS_WHEEL RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_ANALOG, 1 ) #define RETRO_DEVICE_SS_MISSION RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_ANALOG, 2 ) #define RETRO_DEVICE_SS_MISSION2 RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_ANALOG, 3 ) #define RETRO_DEVICE_SS_MOUSE RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_MOUSE, 0 ) #define RETRO_DEVICE_SS_TWINSTICK RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_ANALOG, 4 ) #define RETRO_DEVICE_SS_GUN_JP RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_LIGHTGUN, 0 ) #define RETRO_DEVICE_SS_GUN_US RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_LIGHTGUN, 1 ) enum { INPUT_DEVICE_TYPES_COUNT = 1 /*none*/ + 9 }; /* <-- update me! */ static const struct retro_controller_description input_device_types[ INPUT_DEVICE_TYPES_COUNT ] = { { "Control Pad", RETRO_DEVICE_JOYPAD }, { "3D Control Pad", RETRO_DEVICE_SS_3D_PAD }, { "Arcade Racer", RETRO_DEVICE_SS_WHEEL }, { "Mission Stick", RETRO_DEVICE_SS_MISSION }, { "Mouse", RETRO_DEVICE_SS_MOUSE }, { "Stunner", RETRO_DEVICE_SS_GUN_US }, { "Twin-Stick", RETRO_DEVICE_SS_TWINSTICK }, { "Virtua Gun", RETRO_DEVICE_SS_GUN_JP }, { "Dual Mission Sticks", RETRO_DEVICE_SS_MISSION2 }, /*"Panzer Dragoon Zwei" only!*/ { NULL, 0 }, }; static const struct retro_controller_info ports_no_6player[ 1 + 1 + 1 ] = { /* port one */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, /* port two */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { 0 }, }; static const struct retro_controller_info ports_left_6player[ 6 + 1 + 1 ] = { /* port one */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, /* port two: 6player adaptor */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { 0 }, }; static const struct retro_controller_info ports_right_6player[ 1 + 6 + 1 ] = { /* port one */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, /* port two: 6player adaptor */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { 0 }, }; static const struct retro_controller_info ports_two_6player[ 6 + 6 + 1 ] = { /* port one: 6player adaptor */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, /* port two: 6player adaptor */ { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { input_device_types, INPUT_DEVICE_TYPES_COUNT }, { 0 }, }; /* Lookup table to select ports info for all combinations of 6player adaptor connection. */ static const struct retro_controller_info* ports_lut[ 4 ] = { ports_no_6player, ports_left_6player, ports_right_6player, ports_two_6player }; //------------------------------------------------------------------------------ // Mapping Helpers //------------------------------------------------------------------------------ /* Control Pad (default) */ enum { INPUT_MAP_PAD_SIZE = 12 }; static const unsigned input_map_pad[ INPUT_MAP_PAD_SIZE ] = { // libretro input at position || maps to Saturn on bit //----------------------------------------------------------------------------- RETRO_DEVICE_ID_JOYPAD_L, // L1 -> Z 0 RETRO_DEVICE_ID_JOYPAD_X, // X(top) -> Y 1 RETRO_DEVICE_ID_JOYPAD_Y, // Y(left) -> X 2 RETRO_DEVICE_ID_JOYPAD_R2, // R2 -> R 3 RETRO_DEVICE_ID_JOYPAD_UP, // Pad-Up -> Pad-Up 4 RETRO_DEVICE_ID_JOYPAD_DOWN, // Pad-Down -> Pad-Down 5 RETRO_DEVICE_ID_JOYPAD_LEFT, // Pad-Left -> Pad-Left 6 RETRO_DEVICE_ID_JOYPAD_RIGHT, // Pad-Right -> Pad-Right 7 RETRO_DEVICE_ID_JOYPAD_A, // A(right) -> B 8 RETRO_DEVICE_ID_JOYPAD_R, // R1 -> C 9 RETRO_DEVICE_ID_JOYPAD_B, // B(down) -> A 10 RETRO_DEVICE_ID_JOYPAD_START, // Start -> Start 11 }; static const unsigned input_map_pad_left_shoulder = RETRO_DEVICE_ID_JOYPAD_L2; // L2 -> L 15 /* 3D Control Pad */ enum { INPUT_MAP_3D_PAD_SIZE = 11 }; static const unsigned input_map_3d_pad[ INPUT_MAP_3D_PAD_SIZE ] = { // libretro input at position || maps to Saturn on bit //----------------------------------------------------------------------------- RETRO_DEVICE_ID_JOYPAD_UP, // Pad-Up -> Pad-Up 0 RETRO_DEVICE_ID_JOYPAD_DOWN, // Pad-Down -> Pad-Down 1 RETRO_DEVICE_ID_JOYPAD_LEFT, // Pad-Left -> Pad-Left 2 RETRO_DEVICE_ID_JOYPAD_RIGHT, // Pad-Right -> Pad-Right 3 RETRO_DEVICE_ID_JOYPAD_A, // A(right) -> B 4 RETRO_DEVICE_ID_JOYPAD_R, // R1 -> C 5 RETRO_DEVICE_ID_JOYPAD_B, // B(down) -> A 6 RETRO_DEVICE_ID_JOYPAD_START, // Start -> Start 7 RETRO_DEVICE_ID_JOYPAD_L, // L1 -> Z 8 RETRO_DEVICE_ID_JOYPAD_X, // X(top) -> Y 9 RETRO_DEVICE_ID_JOYPAD_Y, // Y(left) -> X 10 }; static const unsigned input_map_3d_pad_mode_switch = RETRO_DEVICE_ID_JOYPAD_SELECT; /* Arcade Racer (wheel) */ enum { INPUT_MAP_WHEEL_BITSHIFT = 4 }; enum { INPUT_MAP_WHEEL_SIZE = 7 }; static const unsigned input_map_wheel[ INPUT_MAP_WHEEL_SIZE ] = { // libretro input at position || maps to Saturn on bit //----------------------------------------------------------------------------- RETRO_DEVICE_ID_JOYPAD_A, // A(right) -> B 4 RETRO_DEVICE_ID_JOYPAD_R, // R1 -> C 5 RETRO_DEVICE_ID_JOYPAD_B, // B(down) -> A 6 RETRO_DEVICE_ID_JOYPAD_START, // Start -> Start 7 RETRO_DEVICE_ID_JOYPAD_L, // L1 -> Z 8 RETRO_DEVICE_ID_JOYPAD_X, // X(top) -> Y 9 RETRO_DEVICE_ID_JOYPAD_Y, // Y(left) -> X 10 }; static const unsigned input_map_wheel_shift_left = RETRO_DEVICE_ID_JOYPAD_L2; static const unsigned input_map_wheel_shift_right = RETRO_DEVICE_ID_JOYPAD_R2; /* Mission Stick */ enum { INPUT_MAP_MISSION_SIZE = 8 }; static const unsigned input_map_mission[ INPUT_MAP_MISSION_SIZE ] = { // libretro input at position || maps to Saturn on bit //----------------------------------------------------------------------------- RETRO_DEVICE_ID_JOYPAD_A, // A(right) -> B 0 RETRO_DEVICE_ID_JOYPAD_R, // R1 -> C 1 RETRO_DEVICE_ID_JOYPAD_B, // B(down) -> A 2 RETRO_DEVICE_ID_JOYPAD_START, // Start -> Start 3 RETRO_DEVICE_ID_JOYPAD_L, // L1 -> Z 4 RETRO_DEVICE_ID_JOYPAD_X, // X(top) -> Y 5 RETRO_DEVICE_ID_JOYPAD_Y, // Y(left) -> X 6 RETRO_DEVICE_ID_JOYPAD_R2, // R2 -> R 7 }; static const unsigned input_map_mission_left_shoulder = RETRO_DEVICE_ID_JOYPAD_L2; // L2 -> L 15 static const unsigned input_map_mission_throttle_latch = RETRO_DEVICE_ID_JOYPAD_R3; /* Twin-Stick */ static const unsigned input_map_twinstick_left_trigger = RETRO_DEVICE_ID_JOYPAD_L2; static const unsigned input_map_twinstick_left_button = RETRO_DEVICE_ID_JOYPAD_L; static const unsigned input_map_twinstick_right_trigger = RETRO_DEVICE_ID_JOYPAD_R2; static const unsigned input_map_twinstick_right_button = RETRO_DEVICE_ID_JOYPAD_R; //------------------------------------------------------------------------------ // Local Functions //------------------------------------------------------------------------------ static void get_analog_axis( retro_input_state_t input_state_cb, int player_index, int stick, int axis, int* p_analog ) { int analog; analog = input_state_cb( player_index, RETRO_DEVICE_ANALOG, stick, axis ); // Analog stick deadzone if ( astick_deadzone > 0 ) { static const int ASTICK_MAX = 0x8000; const float scale = ((float)ASTICK_MAX/(float)(ASTICK_MAX - astick_deadzone)); if ( analog < -astick_deadzone ) { // Re-scale analog stick range float scaled = (-analog - astick_deadzone)*scale; analog = (int)round(-scaled); if (analog < -32767) { analog = -32767; } } else if ( analog > astick_deadzone ) { // Re-scale analog stick range float scaled = (analog - astick_deadzone)*scale; analog = (int)round(scaled); if (analog > +32767) { analog = +32767; } } else { analog = 0; } } // output *p_analog = analog; } static void get_analog_stick( retro_input_state_t input_state_cb, int player_index, int stick, int* p_analog_x, int* p_analog_y ) { int analog_x, analog_y; analog_x = input_state_cb( player_index, RETRO_DEVICE_ANALOG, stick, RETRO_DEVICE_ID_ANALOG_X ); analog_y = input_state_cb( player_index, RETRO_DEVICE_ANALOG, stick, RETRO_DEVICE_ID_ANALOG_Y ); // Analog stick deadzone (borrowed code from parallel-n64 core) if ( astick_deadzone > 0 ) { static const int ASTICK_MAX = 0x8000; // Convert cartesian coordinate analog stick to polar coordinates double radius = sqrt(analog_x * analog_x + analog_y * analog_y); double angle = atan2(analog_y, analog_x); if (radius > astick_deadzone) { // Re-scale analog stick range to negate deadzone (makes slow movements possible) radius = (radius - astick_deadzone)*((float)ASTICK_MAX/(ASTICK_MAX - astick_deadzone)); // Convert back to cartesian coordinates analog_x = (int)round(radius * cos(angle)); analog_y = (int)round(radius * sin(angle)); // Clamp to correct range if (analog_x > +32767) analog_x = +32767; if (analog_x < -32767) analog_x = -32767; if (analog_y > +32767) analog_y = +32767; if (analog_y < -32767) analog_y = -32767; } else { analog_x = 0; analog_y = 0; } } // output *p_analog_x = analog_x; *p_analog_y = analog_y; } static uint16_t apply_trigger_deadzone( uint16_t input ) { if ( trigger_deadzone > 0 ) { static const int TRIGGER_MAX = 0x8000; const float scale = ((float)TRIGGER_MAX/(float)(TRIGGER_MAX - trigger_deadzone)); if ( input > trigger_deadzone ) { // Re-scale analog range float scaled = (input - trigger_deadzone)*scale; input = (int)round(scaled); if (input > +32767) { input = +32767; } } else { input = 0; } } return input; } static uint16_t get_analog_trigger( retro_input_state_t input_state_cb, int player_index, int id ) { uint16_t trigger; // NOTE: Analog triggers were added Nov 2017. Not all front-ends support this // feature (or pre-date it) so we need to handle this in a graceful way. // First, try and get an analog value using the new libretro API constant trigger = input_state_cb( player_index, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_BUTTON, id ); if ( trigger == 0 ) { // If we got exactly zero, we're either not pressing the button, or the front-end // is not reporting analog values. We need to do a second check using the classic // digital API method, to at least get some response - better than nothing. // NOTE: If we're really just not holding the trigger, we're still going to get zero. trigger = input_state_cb( player_index, RETRO_DEVICE_JOYPAD, 0, id ) ? 0x7FFF : 0; } else { // We got something, which means the front-end can handle analog buttons. // So we apply a deadzone to the input and use it. trigger = apply_trigger_deadzone( trigger ); } return trigger; } //------------------------------------------------------------------------------ // Global Functions //------------------------------------------------------------------------------ void input_init_env( retro_environment_t _environ_cb ) { // Cache this environ_cb = _environ_cb; #define RETRO_DESCRIPTOR_BLOCK( _user ) \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "A Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "B Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "C Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "X Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Y Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "Z Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "L Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "R Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start Button" }, \ { _user, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Mode Switch" }, \ { _user, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Analog X" }, \ { _user, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Analog Y" }, \ { _user, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Analog X (Right)" }, \ { _user, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Analog Y (Right)" }, \ { _user, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER, "Gun Trigger" }, \ { _user, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START, "Gun Start" }, \ { _user, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD, "Gun Reload" } struct retro_input_descriptor desc[] = { RETRO_DESCRIPTOR_BLOCK( 0 ), RETRO_DESCRIPTOR_BLOCK( 1 ), RETRO_DESCRIPTOR_BLOCK( 2 ), RETRO_DESCRIPTOR_BLOCK( 3 ), RETRO_DESCRIPTOR_BLOCK( 4 ), RETRO_DESCRIPTOR_BLOCK( 5 ), RETRO_DESCRIPTOR_BLOCK( 6 ), RETRO_DESCRIPTOR_BLOCK( 7 ), RETRO_DESCRIPTOR_BLOCK( 8 ), RETRO_DESCRIPTOR_BLOCK( 9 ), RETRO_DESCRIPTOR_BLOCK( 10 ), RETRO_DESCRIPTOR_BLOCK( 11 ), { 0 }, }; #undef RETRO_DESCRIPTOR_BLOCK /* Send to front-end */ environ_cb( RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc ); } void input_set_env( retro_environment_t environ_cb ) { /* Pick ports selection */ const struct retro_controller_info* ports = ports_lut[ setting_multitap_port1 + setting_multitap_port2 * 2 ]; /* Send to front-end */ environ_cb( RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports ); } void input_init() { // Initialise to default pad type and bind input buffers to SMPC emulation. for ( unsigned i = 0; i < MAX_CONTROLLERS; ++i ) { input_type[ i ] = RETRO_DEVICE_JOYPAD; input_mode[ i ] = INPUT_MODE_DEFAULT; input_throttle_latch[ i ] = 0; SMPC_SetInput( i, "gamepad", (uint8*)&input_data[ i ] ); } } void input_set_geometry( unsigned width, unsigned height ) { log_cb( RETRO_LOG_INFO, "input_set_geometry: %dx%d\n", width, height ); geometry_width = width; geometry_height = height; } void input_set_deadzone_stick( int percent ) { if ( percent >= 0 && percent <= 100 ) astick_deadzone = (int)( percent * 0.01f * 0x8000); } void input_set_deadzone_trigger( int percent ) { if ( percent >= 0 && percent <= 100 ) trigger_deadzone = (int)( percent * 0.01f * 0x8000); } void input_set_mouse_sensitivity( int percent ) { if ( percent > 0 && percent <= 200 ) { mouse_sensitivity = (float)percent / 100.0f; } } void input_update( retro_input_state_t input_state_cb ) { // For each player (logical controller) for ( unsigned iplayer = 0; iplayer < players; ++iplayer ) { INPUT_DATA* p_input = &(input_data[ iplayer ]); // reset input p_input->buttons = 0; // What kind of controller is connected? switch ( input_type[ iplayer ] ) { case RETRO_DEVICE_JOYPAD: case RETRO_DEVICE_SS_PAD: { // // -- standard control pad buttons + d-pad // input_map_pad is configured to quickly map libretro buttons to the correct bits for the Saturn. for ( int i = 0; i < INPUT_MAP_PAD_SIZE; ++i ) { p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_pad[ i ] ) ? ( 1 << i ) : 0; } // .. the left trigger on the Saturn is a special case since there's a gap in the bits. p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_pad_left_shoulder ) ? ( 1 << 15 ) : 0; } break; case RETRO_DEVICE_SS_TWINSTICK: { int analog_lx, analog_ly; get_analog_stick( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_LEFT, &analog_lx, &analog_ly ); int analog_rx, analog_ry; get_analog_stick( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_RIGHT, &analog_rx, &analog_ry ); const int thresh = 16000; // left-stick if ( analog_ly <= -thresh ) p_input->buttons |= ( 1 << 4 ); // Up if ( analog_lx >= thresh ) p_input->buttons |= ( 1 << 7 ); // Right if ( analog_ly >= thresh ) p_input->buttons |= ( 1 << 5 ); // Down if ( analog_lx <= -thresh ) p_input->buttons |= ( 1 << 6 ); // Left // right-stick if ( analog_ry <= -thresh ) p_input->buttons |= ( 1 << 1 ); // Up <-(Y) if ( analog_rx >= thresh ) p_input->buttons |= ( 1 << 0 ); // Right <-(Z) if ( analog_ry >= thresh ) p_input->buttons |= ( 1 << 8 ); // Down <-(B) if ( analog_rx <= -thresh ) p_input->buttons |= ( 1 << 2 ); // Left <-(X) // left trigger p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_twinstick_left_trigger ) ? ( 1 << 15 ) : 0; // left button p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_twinstick_left_button ) ? ( 1 << 3 ) : 0; // right trigger p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_twinstick_right_trigger ) ? ( 1 << 10 ) : 0; // right button p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_twinstick_right_button ) ? ( 1 << 9 ) : 0; // start p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START ) ? ( 1 << 11 ) : 0; } break; case RETRO_DEVICE_SS_3D_PAD: { // // -- 3d control pad buttons // input_map_3d_pad is configured to quickly map libretro buttons to the correct bits for the Saturn. for ( int i = 0; i < INPUT_MAP_3D_PAD_SIZE; ++i ) { p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_3d_pad[ i ] ) ? ( 1 << i ) : 0; } // // -- analog stick int analog_x, analog_y; get_analog_stick( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_LEFT, &analog_x, &analog_y ); // // -- triggers uint16_t l_trigger, r_trigger; l_trigger = get_analog_trigger( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_L2 ); r_trigger = get_analog_trigger( input_state_cb, iplayer, RETRO_DEVICE_ID_JOYPAD_R2 ); // // -- mode switch { // Handle MODE button as a switch uint16_t prev = ( input_mode[iplayer] & INPUT_MODE_3D_PAD_PREVIOUS_MASK ); uint16_t held = input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_3d_pad_mode_switch ) ? INPUT_MODE_3D_PAD_PREVIOUS_MASK : 0; // Rising edge trigger if ( !prev && held ) { // Toggle 'state' bit: analog/digital mode input_mode[ iplayer ] ^= INPUT_MODE_3D_PAD_ANALOG; // Tell user char text[ 256 ]; if ( input_mode[iplayer] & INPUT_MODE_3D_PAD_ANALOG ) { sprintf( text, "Controller %u: Analog Mode", (iplayer+1) ); } else { sprintf( text, "Controller %u: Digital Mode", (iplayer+1) ); } struct retro_message msg = { text, 180 }; environ_cb( RETRO_ENVIRONMENT_SET_MESSAGE, &msg ); } // Store held state in 'previous' bit. input_mode[ iplayer ] = ( input_mode[ iplayer ] & ~INPUT_MODE_3D_PAD_PREVIOUS_MASK ) | held; } // // -- format input data // Convert analog values into direction values. uint16_t right = analog_x > 0 ? analog_x : 0; uint16_t left = analog_x < 0 ? -analog_x : 0; uint16_t down = analog_y > 0 ? analog_y : 0; uint16_t up = analog_y < 0 ? -analog_y : 0; // Apply analog/digital mode switch bit. if ( input_mode[iplayer] & INPUT_MODE_3D_PAD_ANALOG ) { p_input->buttons |= 0x1000; // set bit 12 } p_input->u8[0x2] = ((left >> 0) & 0xff); p_input->u8[0x3] = ((left >> 8) & 0xff); p_input->u8[0x4] = ((right >> 0) & 0xff); p_input->u8[0x5] = ((right >> 8) & 0xff); p_input->u8[0x6] = ((up >> 0) & 0xff); p_input->u8[0x7] = ((up >> 8) & 0xff); p_input->u8[0x8] = ((down >> 0) & 0xff); p_input->u8[0x9] = ((down >> 8) & 0xff); p_input->u8[0xa] = ((r_trigger >> 0) & 0xff); p_input->u8[0xb] = ((r_trigger >> 8) & 0xff); p_input->u8[0xc] = ((l_trigger >> 0) & 0xff); p_input->u8[0xd] = ((l_trigger >> 8) & 0xff); } break; case RETRO_DEVICE_SS_WHEEL: { // // -- Wheel buttons // input_map_wheel is configured to quickly map libretro buttons to the correct bits for the Saturn. for ( int i = 0; i < INPUT_MAP_WHEEL_SIZE; ++i ) { const uint16_t bit = ( 1 << ( i + INPUT_MAP_WHEEL_BITSHIFT ) ); p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_wheel[ i ] ) ? bit : 0; } // shift-paddles p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_wheel_shift_left ) ? ( 1 << 0 ) : 0; p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_wheel_shift_right ) ? ( 1 << 1 ) : 0; // // -- analog wheel int analog_x; get_analog_axis( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, &analog_x ); // // -- format input data // Convert analog values into direction values. uint16_t right = analog_x > 0 ? analog_x : 0; uint16_t left = analog_x < 0 ? -analog_x : 0; p_input->u8[0x2] = ((left >> 0) & 0xff); p_input->u8[0x3] = ((left >> 8) & 0xff); p_input->u8[0x4] = ((right >> 0) & 0xff); p_input->u8[0x5] = ((right >> 8) & 0xff); } break; case RETRO_DEVICE_SS_MOUSE: { // mouse buttons p_input->u8[0x8] = 0; if ( input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT ) ) { p_input->u8[0x8] |= ( 1 << 0 ); // A } if ( input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_RIGHT ) ) { p_input->u8[0x8] |= ( 1 << 1 ); // B } if ( input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_MIDDLE ) ) { p_input->u8[0x8] |= ( 1 << 2 ); // C } if ( input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START ) || input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_BUTTON_4 ) || input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_BUTTON_5 ) ) { p_input->u8[0x8] |= ( 1 << 3 ); // Start } // mouse input int dx_raw, dy_raw; dx_raw = input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_X ); dy_raw = input_state_cb( iplayer, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_Y ); int *delta; delta = (int*)p_input; delta[ 0 ] = (int)roundf( dx_raw * mouse_sensitivity ); delta[ 1 ] = (int)roundf( dy_raw * mouse_sensitivity ); } break; case RETRO_DEVICE_SS_MISSION: { // // -- mission stick buttons // input_map_mission is configured to quickly map libretro buttons to the correct bits for the Saturn. for ( int i = 0; i < INPUT_MAP_MISSION_SIZE; ++i ) { p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_mission[ i ] ) ? ( 1 << i ) : 0; } // .. the left trigger is a special case, there's a gap in the bits. p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_mission_left_shoulder ) ? ( 1 << 11 ) : 0; // // -- analog stick int analog_x, analog_y; get_analog_stick( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_LEFT, &analog_x, &analog_y ); // // -- throttle int throttle_real; get_analog_axis( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, &throttle_real ); int16_t throttle; if ( input_mode[iplayer] & INPUT_MODE_MISSION_THROTTLE_LATCH ) { // Use latched value throttle = input_throttle_latch[iplayer]; } else { // Direct read throttle = throttle_real; } // // -- throttle latch switch { // Handle MODE button as a switch uint16_t prev = ( input_mode[iplayer] & INPUT_MODE_MISSION_THROTTLE_PREV ); uint16_t held = input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_mission_throttle_latch ) ? INPUT_MODE_MISSION_THROTTLE_PREV : 0; // Rising edge trigger? if ( !prev && held ) { // Toggle 'state' bit: throttle latch enable/disable. input_mode[ iplayer ] ^= INPUT_MODE_MISSION_THROTTLE_LATCH; // Tell user if ( input_mode[iplayer] & INPUT_MODE_MISSION_THROTTLE_LATCH ) { log_cb( RETRO_LOG_INFO, "Controller %u: Latched Throttle at %d\n", (iplayer+1), throttle_real ); input_throttle_latch[iplayer] = (int16_t)throttle_real; } else { log_cb( RETRO_LOG_INFO, "Controller %u: Remove Throttle Latch\n", (iplayer+1) ); } } // Store held state in 'previous' bit. input_mode[ iplayer ] = ( input_mode[ iplayer ] & ~INPUT_MODE_MISSION_THROTTLE_PREV ) | held; } // // -- format input data // Convert analog values into direction values. uint16_t right = analog_x > 0 ? analog_x : 0; uint16_t left = analog_x < 0 ? -analog_x : 0; uint16_t down = analog_y > 0 ? analog_y : 0; uint16_t up = analog_y < 0 ? -analog_y : 0; uint16_t th_up = throttle > 0 ? throttle : 0; uint16_t th_dn = throttle < 0 ? -throttle : 0; p_input->u8[0x2] = 0; // todo: auto-fire controls. p_input->u8[0x3] = ((left >> 0) & 0xff); p_input->u8[0x4] = ((left >> 8) & 0xff); p_input->u8[0x5] = ((right >> 0) & 0xff); p_input->u8[0x6] = ((right >> 8) & 0xff); p_input->u8[0x7] = ((up >> 0) & 0xff); p_input->u8[0x8] = ((up >> 8) & 0xff); p_input->u8[0x9] = ((down >> 0) & 0xff); p_input->u8[0xa] = ((down >> 8) & 0xff); p_input->u8[0xb] = ((th_up >> 0) & 0xff); p_input->u8[0xc] = ((th_up >> 8) & 0xff); p_input->u8[0xd] = ((th_dn >> 0) & 0xff); p_input->u8[0xe] = ((th_dn >> 8) & 0xff); } break; case RETRO_DEVICE_SS_MISSION2: { // // -- mission stick buttons // input_map_mission is configured to quickly map libretro buttons to the correct bits for the Saturn. for ( int i = 0; i < INPUT_MAP_MISSION_SIZE; ++i ) { p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_mission[ i ] ) ? ( 1 << i ) : 0; } // .. the left trigger is a special case, there's a gap in the bits. p_input->buttons |= input_state_cb( iplayer, RETRO_DEVICE_JOYPAD, 0, input_map_mission_left_shoulder ) ? ( 1 << 11 ) : 0; // // -- analog sticks int analog1_x, analog1_y; int analog2_x, analog2_y; // Default - patent shows first stick on right side, second added on left // see: https://segaretro.org/images/a/a1/Patent_EP0745928A2.pdf get_analog_stick( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_RIGHT, &analog1_x, &analog1_y ); get_analog_stick( input_state_cb, iplayer, RETRO_DEVICE_INDEX_ANALOG_LEFT, &analog2_x, &analog2_y ); // // -- format input data // Convert analog values into direction values. uint16_t right1 = analog1_x > 0 ? analog1_x : 0; uint16_t left1 = analog1_x < 0 ? -analog1_x : 0; uint16_t down1 = analog1_y > 0 ? analog1_y : 0; uint16_t up1 = analog1_y < 0 ? -analog1_y : 0; uint16_t right2 = analog2_x > 0 ? analog2_x : 0; uint16_t left2 = analog2_x < 0 ? -analog2_x : 0; uint16_t down2 = analog2_y > 0 ? analog2_y : 0; uint16_t up2 = analog2_y < 0 ? -analog2_y : 0; p_input->u8[ 0x2] = 0; // todo: auto-fire controls. p_input->u8[ 0x3] = ((left1 >> 0) & 0xff); p_input->u8[ 0x4] = ((left1 >> 8) & 0xff); p_input->u8[ 0x5] = ((right1 >> 0) & 0xff); p_input->u8[ 0x6] = ((right1 >> 8) & 0xff); p_input->u8[ 0x7] = ((up1 >> 0) & 0xff); p_input->u8[ 0x8] = ((up1 >> 8) & 0xff); p_input->u8[ 0x9] = ((down1 >> 0) & 0xff); p_input->u8[ 0xa] = ((down1 >> 8) & 0xff); p_input->u8[ 0xb] = 0; // todo: throttle1 p_input->u8[ 0xc] = 0; p_input->u8[ 0xd] = 0; p_input->u8[ 0xe] = 0; p_input->u8[ 0xf] = ((left2 >> 0) & 0xff); p_input->u8[0x10] = ((left2 >> 8) & 0xff); p_input->u8[0x11] = ((right2 >> 0) & 0xff); p_input->u8[0x12] = ((right2 >> 8) & 0xff); p_input->u8[0x13] = ((up2 >> 0) & 0xff); p_input->u8[0x14] = ((up2 >> 8) & 0xff); p_input->u8[0x15] = ((down2 >> 0) & 0xff); p_input->u8[0x16] = ((down2 >> 8) & 0xff); p_input->u8[0x17] = 0; // todo: throttle2 p_input->u8[0x18] = 0; p_input->u8[0x19] = 0; p_input->u8[0x1a] = 0; } break; case RETRO_DEVICE_SS_GUN_JP: case RETRO_DEVICE_SS_GUN_US: { uint8_t shot_type; int gun_x, gun_y; int forced_reload; forced_reload = input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD ); // off-screen? if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN ) || forced_reload || geometry_height == 0 ) { shot_type = 0x4; // off-screen shot gun_x = -16384; // magic position to disable cross-hair drawing. gun_y = -16384; } else { shot_type = 0x1; // on-screen shot int gun_x_raw, gun_y_raw; gun_x_raw = input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X ); gun_y_raw = input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y ); // .. scale into screen space: // NOTE: the scaling here is empirical-guesswork. // Tested at 352x240 (ntsc) and 352x256 (pal) const int scale_x = 21472; const int scale_y = geometry_height; const int offset_y = geometry_height - 240; gun_x = ( ( gun_x_raw + 0x7fff ) * scale_x ) / (0x7fff << 1); gun_y = ( ( gun_y_raw + 0x7fff ) * scale_y ) / (0x7fff << 1) + offset_y; } // position p_input->gun_pos[ 0 ] = gun_x; p_input->gun_pos[ 1 ] = gun_y; // buttons p_input->u8[ 4 ] = 0; // trigger if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER ) || forced_reload ) { p_input->u8[ 4 ] |= shot_type; } // start if ( input_state_cb( iplayer, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START ) ) { p_input->u8[ 4 ] |= 0x2; } } break; }; // switch ( input_type[ iplayer ] ) }; // for each player } // save state function for input int input_StateAction( StateMem* sm, const unsigned load, const bool data_only ) { int success; SFORMAT StateRegs[] = { SFPTR16N( input_mode, MAX_CONTROLLERS, "pad-mode" ), SFPTR16N( input_throttle_latch, MAX_CONTROLLERS, "throttle-latch" ), SFEND }; success = MDFNSS_StateAction( sm, load, data_only, StateRegs, "LIBRETRO-INPUT", false); // ok? return success; } //------------------------------------------------------------------------------ // Libretro Interface //------------------------------------------------------------------------------ void retro_set_controller_port_device( unsigned in_port, unsigned device ) { if ( in_port < MAX_CONTROLLERS ) { // Store input type input_type[ in_port ] = device; input_mode[ in_port ] = INPUT_MODE_DEFAULT; switch ( device ) { case RETRO_DEVICE_NONE: log_cb( RETRO_LOG_INFO, "Controller %u: Unplugged\n", (in_port+1) ); SMPC_SetInput( in_port, "none", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_JOYPAD: case RETRO_DEVICE_SS_PAD: log_cb( RETRO_LOG_INFO, "Controller %u: Control Pad\n", (in_port+1) ); SMPC_SetInput( in_port, "gamepad", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_3D_PAD: log_cb( RETRO_LOG_INFO, "Controller %u: 3D Control Pad\n", (in_port+1) ); SMPC_SetInput( in_port, "3dpad", (uint8*)&input_data[ in_port ] ); input_mode[ in_port ] = INPUT_MODE_DEFAULT_3D_PAD; break; case RETRO_DEVICE_SS_WHEEL: log_cb( RETRO_LOG_INFO, "Controller %u: Arcade Racer\n", (in_port+1) ); SMPC_SetInput( in_port, "wheel", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_MISSION: log_cb( RETRO_LOG_INFO, "Controller %u: Mission Stick\n", (in_port+1) ); SMPC_SetInput( in_port, "mission", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_MISSION2: log_cb( RETRO_LOG_INFO, "Controller %u: Dual Mission Sticks\n", (in_port+1) ); SMPC_SetInput( in_port, "dmission", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_MOUSE: log_cb( RETRO_LOG_INFO, "Controller %u: Mouse\n", (in_port+1) ); SMPC_SetInput( in_port, "mouse", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_GUN_US: log_cb( RETRO_LOG_INFO, "Controller %u: Stunner\n", (in_port+1) ); SMPC_SetInput( in_port, "gun", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_GUN_JP: log_cb( RETRO_LOG_INFO, "Controller %u: Virtua Gun\n", (in_port+1) ); SMPC_SetInput( in_port, "gun", (uint8*)&input_data[ in_port ] ); break; case RETRO_DEVICE_SS_TWINSTICK: log_cb( RETRO_LOG_INFO, "Controller %u: Twin-Stick\n", (in_port+1) ); SMPC_SetInput( in_port, "gamepad", (uint8*)&input_data[ in_port ] ); break; default: log_cb( RETRO_LOG_WARN, "Controller %u: Unsupported Device (%u)\n", (in_port+1), device ); SMPC_SetInput( in_port, "none", (uint8*)&input_data[ in_port ] ); break; }; // switch ( device ) }; // valid port? } void input_multitap( int port, bool enabled ) { switch ( port ) { case 1: // PORT 1 if ( enabled != setting_multitap_port1 ) { setting_multitap_port1 = enabled; if ( setting_multitap_port1 ) { log_cb( RETRO_LOG_INFO, "Connected 6Player Adaptor to Port 1\n" ); } else { log_cb( RETRO_LOG_INFO, "Removed 6Player Adaptor from Port 1\n" ); } SMPC_SetMultitap( 0, setting_multitap_port1 ); } break; case 2: // PORT 2 if ( enabled != setting_multitap_port2 ) { setting_multitap_port2 = enabled; if ( setting_multitap_port2 ) { log_cb( RETRO_LOG_INFO, "Connected 6Player Adaptor to Port 2\n" ); } else { log_cb( RETRO_LOG_INFO, "Removed 6Player Adaptor from Port 2\n" ); } SMPC_SetMultitap( 1, setting_multitap_port2 ); } break; }; // switch ( port ) // Update players count players = 2; if ( setting_multitap_port1 ) { players += 5; } if ( setting_multitap_port2 ) { players += 5; } /*Tell front-end*/ input_set_env( environ_cb ); } //==============================================================================