diff --git a/audio/audio_driver.c b/audio/audio_driver.c index be9f9279fe..0e0c77cb3d 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -45,6 +45,10 @@ #include "../menu/menu_driver.h" #endif +#ifdef HAVE_NETWORKING +#include "../network/netplay/netplay.h" +#endif + #include "../configuration.h" #include "../driver.h" #include "../frontend/frontend_driver.h" @@ -1604,7 +1608,14 @@ bool audio_driver_callback(void) settings_t *settings = config_get_ptr(); bool runloop_paused = runloop_state_get_ptr()->paused; #ifdef HAVE_MENU - bool core_paused = runloop_paused || (settings->bools.menu_pause_libretro && menu_state_get_ptr()->alive); +#ifdef HAVE_NETWORKING + bool core_paused = runloop_paused || + (settings->bools.menu_pause_libretro && menu_state_get_ptr()->alive && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)); +#else + bool core_paused = runloop_paused || + (settings->bools.menu_pause_libretro && menu_state_get_ptr()->alive); +#endif #else bool core_paused = runloop_paused; #endif diff --git a/command.h b/command.h index 395e9d0748..0bb46d32dc 100644 --- a/command.h +++ b/command.h @@ -119,6 +119,8 @@ enum event_command CMD_EVENT_AUDIO_MUTE_TOGGLE, /* Toggles FPS counter. */ CMD_EVENT_FPS_TOGGLE, + /* Toggle ping counter. */ + CMD_EVENT_NETPLAY_PING_TOGGLE, /* Gathers diagnostic info about the system and RetroArch configuration, then sends it to our servers. */ CMD_EVENT_SEND_DEBUG_INFO, /* Toggles netplay hosting. */ @@ -202,6 +204,10 @@ enum event_command CMD_EVENT_NETPLAY_DEINIT, /* Switch between netplay gaming and watching. */ CMD_EVENT_NETPLAY_GAME_WATCH, + /* Open a netplay chat input menu. */ + CMD_EVENT_NETPLAY_PLAYER_CHAT, + /* Toggle chat fading. */ + CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE, /* Start hosting netplay. */ CMD_EVENT_NETPLAY_ENABLE_HOST, /* Disconnect from the netplay host. */ diff --git a/config.def.h b/config.def.h index 7f72c1f7a9..d1090ad0e4 100644 --- a/config.def.h +++ b/config.def.h @@ -1110,6 +1110,9 @@ static const bool audio_enable_menu_bgm = false; /* Enables displaying various timing statistics. */ #define DEFAULT_STATISTICS_SHOW false +/* Enables displaying the current netplay room ping. */ +#define DEFAULT_NETPLAY_PING_SHOW false + /* Enables use of rewind. This will incur some memory footprint * depending on the save state buffer. */ #define DEFAULT_REWIND_ENABLE false @@ -1160,7 +1163,10 @@ static const bool audio_enable_menu_bgm = false; /* Start netplay in spectator mode */ static const bool netplay_start_as_spectator = false; -/* Allow players (other than the host) to pause */ +/* Netplay chat fading toggle */ +static const bool netplay_fade_chat = true; + +/* Allow players to pause */ static const bool netplay_allow_pausing = false; /* Allow connections in slave mode */ @@ -1188,9 +1194,10 @@ static const bool netplay_use_mitm_server = false; #ifdef HAVE_NETWORKING static const unsigned netplay_max_connections = 3; -static const unsigned netplay_share_digital = RARCH_NETPLAY_SHARE_DIGITAL_NO_PREFERENCE; +static const unsigned netplay_max_ping = 0; -static const unsigned netplay_share_analog = RARCH_NETPLAY_SHARE_ANALOG_NO_PREFERENCE; +static const unsigned netplay_share_digital = RARCH_NETPLAY_SHARE_DIGITAL_NO_PREFERENCE; +static const unsigned netplay_share_analog = RARCH_NETPLAY_SHARE_ANALOG_NO_PREFERENCE; #endif /* On save state load, block SRAM from being overwritten. diff --git a/config.def.keybinds.h b/config.def.keybinds.h index 92609a9aae..cd7de7a8cc 100644 --- a/config.def.keybinds.h +++ b/config.def.keybinds.h @@ -451,6 +451,13 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_FPS_TOGGLE, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PING_TOGGLE, RETROK_UNKNOWN, + RARCH_NETPLAY_PING_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -472,6 +479,20 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_NETPLAY_GAME_WATCH, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PLAYER_CHAT, RETROK_UNKNOWN, + RARCH_NETPLAY_PLAYER_CHAT, NO_BTN, NO_BTN, 0, + true + }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, RETROK_UNKNOWN, + RARCH_NETPLAY_FADE_CHAT_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -1005,6 +1026,13 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_FPS_TOGGLE, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PING_TOGGLE, RETROK_UNKNOWN, + RARCH_NETPLAY_PING_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -1026,6 +1054,20 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_NETPLAY_GAME_WATCH, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PLAYER_CHAT, RETROK_UNKNOWN, + RARCH_NETPLAY_PLAYER_CHAT, NO_BTN, NO_BTN, 0, + true + }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, RETROK_UNKNOWN, + RARCH_NETPLAY_FADE_CHAT_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -1569,6 +1611,13 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_FPS_TOGGLE, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PING_TOGGLE, RETROK_UNKNOWN, + RARCH_NETPLAY_PING_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -1590,6 +1639,20 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_NETPLAY_GAME_WATCH, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PLAYER_CHAT, RETROK_BACKQUOTE, + RARCH_NETPLAY_PLAYER_CHAT, NO_BTN, NO_BTN, 0, + true + }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, RETROK_UNKNOWN, + RARCH_NETPLAY_FADE_CHAT_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, diff --git a/configuration.c b/configuration.c index b42607b78a..83d8a02c18 100644 --- a/configuration.c +++ b/configuration.c @@ -333,9 +333,12 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = { DECLARE_META_BIND(2, audio_mute, RARCH_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE), DECLARE_META_BIND(2, osk_toggle, RARCH_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK), DECLARE_META_BIND(2, fps_toggle, RARCH_FPS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE), + DECLARE_META_BIND(2, netplay_ping_toggle, RARCH_NETPLAY_PING_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PING_TOGGLE), DECLARE_META_BIND(2, send_debug_info, RARCH_SEND_DEBUG_INFO, MENU_ENUM_LABEL_VALUE_INPUT_META_SEND_DEBUG_INFO), DECLARE_META_BIND(2, netplay_host_toggle, RARCH_NETPLAY_HOST_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_HOST_TOGGLE), DECLARE_META_BIND(2, netplay_game_watch, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH), + DECLARE_META_BIND(2, netplay_player_chat, RARCH_NETPLAY_PLAYER_CHAT, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PLAYER_CHAT), + DECLARE_META_BIND(2, netplay_fade_chat_toggle, RARCH_NETPLAY_FADE_CHAT_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE), DECLARE_META_BIND(2, enable_hotkey, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY), DECLARE_META_BIND(2, volume_up, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP), DECLARE_META_BIND(2, volume_down, RARCH_VOLUME_DOWN, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_DOWN), @@ -1600,6 +1603,7 @@ static struct config_bool_setting *populate_settings_bool( #ifdef HAVE_NETWORKING SETTING_BOOL("netplay_public_announce", &settings->bools.netplay_public_announce, true, DEFAULT_NETPLAY_PUBLIC_ANNOUNCE, false); SETTING_BOOL("netplay_start_as_spectator", &settings->bools.netplay_start_as_spectator, false, netplay_start_as_spectator, false); + SETTING_BOOL("netplay_fade_chat", &settings->bools.netplay_fade_chat, true, netplay_fade_chat, false); SETTING_BOOL("netplay_allow_pausing", &settings->bools.netplay_allow_pausing, true, netplay_allow_pausing, false); SETTING_BOOL("netplay_allow_slaves", &settings->bools.netplay_allow_slaves, true, netplay_allow_slaves, false); SETTING_BOOL("netplay_require_slaves", &settings->bools.netplay_require_slaves, true, netplay_require_slaves, false); @@ -1622,6 +1626,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("netplay_request_device_p14", &settings->bools.netplay_request_devices[13], true, false, false); SETTING_BOOL("netplay_request_device_p15", &settings->bools.netplay_request_devices[14], true, false, false); SETTING_BOOL("netplay_request_device_p16", &settings->bools.netplay_request_devices[15], true, false, false); + SETTING_BOOL("netplay_ping_show", &settings->bools.netplay_ping_show, true, DEFAULT_NETPLAY_PING_SHOW, false); SETTING_BOOL("network_on_demand_thumbnails", &settings->bools.network_on_demand_thumbnails, true, DEFAULT_NETWORK_ON_DEMAND_THUMBNAILS, false); #endif SETTING_BOOL("input_descriptor_label_show", &settings->bools.input_descriptor_label_show, true, input_descriptor_label_show, false); @@ -2215,6 +2220,7 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("netplay_ip_port", &settings->uints.netplay_port, true, RARCH_DEFAULT_PORT, false); SETTING_OVERRIDE(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT); SETTING_UINT("netplay_max_connections", &settings->uints.netplay_max_connections, true, netplay_max_connections, false); + SETTING_UINT("netplay_max_ping", &settings->uints.netplay_max_ping, true, netplay_max_ping, false); SETTING_UINT("netplay_input_latency_frames_min",&settings->uints.netplay_input_latency_frames_min, true, 0, false); SETTING_UINT("netplay_input_latency_frames_range",&settings->uints.netplay_input_latency_frames_range, true, 0, false); SETTING_UINT("netplay_share_digital", &settings->uints.netplay_share_digital, true, netplay_share_digital, false); diff --git a/configuration.h b/configuration.h index a82bde57c4..dd357d3e24 100644 --- a/configuration.h +++ b/configuration.h @@ -201,6 +201,7 @@ typedef struct settings unsigned netplay_port; unsigned netplay_max_connections; + unsigned netplay_max_ping; unsigned netplay_input_latency_frames_min; unsigned netplay_input_latency_frames_range; unsigned netplay_share_digital; @@ -760,6 +761,7 @@ typedef struct settings /* Netplay */ bool netplay_public_announce; bool netplay_start_as_spectator; + bool netplay_fade_chat; bool netplay_allow_pausing; bool netplay_allow_slaves; bool netplay_require_slaves; @@ -767,6 +769,7 @@ typedef struct settings bool netplay_nat_traversal; bool netplay_use_mitm_server; bool netplay_request_devices[MAX_USERS]; + bool netplay_ping_show; /* Network */ bool network_buildbot_auto_extract_archive; diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index 6cb43406e5..81ce08950b 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -79,6 +79,10 @@ static void INLINE gfx_widgets_font_free(gfx_widget_font_data_t *font_data) /* Widgets list */ const static gfx_widget_t* const widgets[] = { +#ifdef HAVE_NETWORKING + &gfx_widget_netplay_chat, + &gfx_widget_netplay_ping, +#endif #ifdef HAVE_SCREENSHOTS &gfx_widget_screenshot, #endif diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index 1d0d92bf3b..5f5616b2f6 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -378,6 +378,11 @@ extern const gfx_widget_t gfx_widget_libretro_message; extern const gfx_widget_t gfx_widget_progress_message; extern const gfx_widget_t gfx_widget_load_content_animation; +#ifdef HAVE_NETWORKING +extern const gfx_widget_t gfx_widget_netplay_chat; +extern const gfx_widget_t gfx_widget_netplay_ping; +#endif + #ifdef HAVE_CHEEVOS extern const gfx_widget_t gfx_widget_achievement_popup; extern const gfx_widget_t gfx_widget_leaderboard_display; diff --git a/input/input_defines.h b/input/input_defines.h index adf78e5c53..e31c049a0a 100644 --- a/input/input_defines.h +++ b/input/input_defines.h @@ -99,9 +99,12 @@ enum RARCH_MUTE, RARCH_OSK, RARCH_FPS_TOGGLE, + RARCH_NETPLAY_PING_TOGGLE, RARCH_SEND_DEBUG_INFO, RARCH_NETPLAY_HOST_TOGGLE, RARCH_NETPLAY_GAME_WATCH, + RARCH_NETPLAY_PLAYER_CHAT, + RARCH_NETPLAY_FADE_CHAT_TOGGLE, RARCH_ENABLE_HOTKEY, RARCH_VOLUME_UP, RARCH_VOLUME_DOWN, diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 9a377e88cb..81372de215 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -1282,6 +1282,10 @@ MSG_HASH( MENU_ENUM_LABEL_STATISTICS_SHOW, "statistics_show" ) +MSG_HASH( + MENU_ENUM_LABEL_NETPLAY_PING_SHOW, + "netplay_ping_show" + ) MSG_HASH( MENU_ENUM_LABEL_FRAME_THROTTLE_ENABLE, "fastforward_ratio_throttle_enable" @@ -1972,6 +1976,10 @@ MSG_HASH( MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR, "netplay_start_as_spectator" ) +MSG_HASH( + MENU_ENUM_LABEL_NETPLAY_FADE_CHAT, + "netplay_fade_chat" + ) MSG_HASH( MENU_ENUM_LABEL_NETPLAY_ALLOW_PAUSING, "netplay_allow_pausing" @@ -1988,6 +1996,10 @@ MSG_HASH( MENU_ENUM_LABEL_NETPLAY_MAX_CONNECTIONS, "netplay_max_connections" ) +MSG_HASH( + MENU_ENUM_LABEL_NETPLAY_MAX_PING, + "netplay_max_ping" + ) MSG_HASH( MENU_ENUM_LABEL_NETPLAY_LAN_SCAN_SETTINGS, "Search for and connect to netplay hosts on the local network." @@ -2836,6 +2848,10 @@ MSG_HASH( MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS, "refresh_rooms" ) +MSG_HASH( + MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN, + "refresh_lan" + ) MSG_HASH( MENU_ENUM_LABEL_SCAN_THIS_DIRECTORY, "scan_this_directory" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index f6b63d35a3..9b9b83ad70 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -2666,6 +2666,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE, "Switches 'frames per second' status indicator on/off." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PING_TOGGLE, + "Show Netplay Ping (Toggle)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_PING_TOGGLE, + "Switches the ping counter for the current netplay room on/off." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_INPUT_META_SEND_DEBUG_INFO, "Send Debug Info" @@ -2690,6 +2698,22 @@ MSG_HASH( MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_GAME_WATCH, "Switches current netplay session between 'play' and 'spectate' modes." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PLAYER_CHAT, + "Netplay Player Chat" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_PLAYER_CHAT, + "Sends a chat message to the current netplay session." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, + "Netplay Fade Chat Toggle" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, + "Toggle between fading and static netplay chat messages." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, "Hotkey Enable" @@ -3907,6 +3931,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_MEMORY_UPDATE_INTERVAL, "Memory usage display will be updated at the set interval in frames." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_PING_SHOW, + "Display Netplay Ping" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_NETPLAY_PING_SHOW, + "Display the ping for the current netplay room." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_MENU_SHOW_LOAD_CONTENT_ANIMATION, "\"Load Content\" Startup Notification" @@ -5258,6 +5290,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_NETPLAY_MAX_CONNECTIONS, "The maximum number of active connections that the host will accept before refusing new ones." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_MAX_PING, + "Ping Limiter" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_NETPLAY_MAX_PING, + "The maximum connection latency (ping) that the host will accept. Set it to 0 for no limit." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD, "Server Password" @@ -5282,13 +5322,21 @@ MSG_HASH( MENU_ENUM_SUBLABEL_NETPLAY_START_AS_SPECTATOR, "Start netplay in spectator mode." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_FADE_CHAT, + "Fade Chat" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_NETPLAY_FADE_CHAT, + "Fade chat messages over time." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_NETPLAY_ALLOW_PAUSING, "Allow Pausing" ) MSG_HASH( MENU_ENUM_SUBLABEL_NETPLAY_ALLOW_PAUSING, - "Allow players to pause during netplay. The host can always pause regardless of this setting." + "Allow players to pause during netplay." ) MSG_HASH( MENU_ENUM_LABEL_VALUE_NETPLAY_ALLOW_SLAVES, @@ -6103,6 +6151,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_NETPLAY_REFRESH_ROOMS, "Scan for netplay hosts." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_REFRESH_LAN, + "Refresh Netplay LAN List" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_NETPLAY_REFRESH_LAN, + "Scan for netplay hosts on LAN." + ) /* Netplay > Host */ @@ -10861,6 +10917,10 @@ MSG_HASH( MSG_NETPLAY_ENTER_PASSWORD, "Enter netplay server password:" ) +MSG_HASH( + MSG_NETPLAY_ENTER_CHAT, + "Enter netplay chat message:" + ) MSG_HASH( MSG_DISCORD_CONNECTION_REQUEST, "Do you want to allow connection from user:" @@ -11137,6 +11197,10 @@ MSG_HASH( MSG_CONTENT_CRC32S_DIFFER, "Content CRC32s differ. Cannot use different games." ) +MSG_HASH( + MSG_PING_TOO_HIGH, + "Your ping is too high for this host." + ) MSG_HASH( MSG_CONTENT_LOADING_SKIPPED_IMPLEMENTATION_WILL_DO_IT, "Content loading skipped. Implementation will load it on its own." diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index aefdd33b83..a8b44c00d5 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -5756,9 +5756,10 @@ static int action_ok_netplay_connect_room(const char *path, if (net_st->room_list[room_index].host_method == NETPLAY_HOST_METHOD_MITM) snprintf(tmp_hostname, sizeof(tmp_hostname), - "%s|%d", + "%s|%d|%s", net_st->room_list[room_index].mitm_address, - net_st->room_list[room_index].mitm_port); + net_st->room_list[room_index].mitm_port, + net_st->room_list[room_index].mitm_session); else snprintf(tmp_hostname, sizeof(tmp_hostname), @@ -5783,224 +5784,168 @@ static int action_ok_netplay_connect_room(const char *path, return 0; } -#ifdef HAVE_NETPLAYDISCOVERY -static int action_ok_netplay_lan_scan(const char *path, - const char *label, unsigned type, size_t idx, size_t entry_idx) -{ - struct netplay_host_list *hosts = NULL; - struct netplay_host *host = NULL; - - /* Figure out what host we're connecting to */ - if (!netplay_discovery_driver_ctl(RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, &hosts)) - return -1; - if (entry_idx >= hosts->size) - return -1; - host = &hosts->hosts[entry_idx]; - - /* Enable Netplay client mode */ - if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) - generic_action_ok_command(CMD_EVENT_NETPLAY_DEINIT); - netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); - - /* Enable Netplay */ - if (command_event(CMD_EVENT_NETPLAY_INIT_DIRECT, (void *) host)) - return generic_action_ok_command(CMD_EVENT_RESUME); - return -1; -} - -static void netplay_lan_scan_callback(retro_task_t *task, - void *task_data, - void *user_data, const char *error) -{ - struct netplay_host_list *netplay_hosts = NULL; - enum msg_hash_enums enum_idx = MSG_UNKNOWN; - unsigned menu_type = 0; - const char *label = NULL; - const char *path = NULL; - - /* TODO/FIXME: I have no idea what this is supposed to be - * doing... - * As it stands, this function will never get past the - * following sanity check (i.e. netplay_lan_scan_callback() - * will never be called when we are viewing the 'lan scan - * settings' list, since this list doesn't even exist...). - * Moreover, any menu entries that get added here - * (menu_entries_append_enum()) will be erased by the - * subsequent netplay_refresh_rooms_cb() callback - and - * menu entries should never be added outside of - * menu_displaylist.c anyway. - * This is some legacy garbage, and someone who understands - * netplay needs to rip it all out. */ - - menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL); - - /* Don't push the results if we left the LAN scan menu */ - if (!string_is_equal(label, - msg_hash_to_str( - MENU_ENUM_LABEL_DEFERRED_NETPLAY_LAN_SCAN_SETTINGS_LIST))) - return; - - if (!netplay_discovery_driver_ctl( - RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, - (void *) &netplay_hosts)) - return; - - if (netplay_hosts->size > 0) - { - unsigned i; - file_list_t *file_list = menu_entries_get_selection_buf_ptr(0); - - menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, file_list); - - for (i = 0; i < netplay_hosts->size; i++) - { - struct netplay_host *host = &netplay_hosts->hosts[i]; - menu_entries_append_enum(file_list, - host->nick, - msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_CONNECT_TO), - MENU_ENUM_LABEL_NETPLAY_CONNECT_TO, - MENU_NETPLAY_LAN_SCAN, 0, 0); - } - } -} -#endif - static void netplay_refresh_rooms_cb(retro_task_t *task, - void *task_data, void *user_data, const char *err) + void *task_data, void *user_data, const char *error) { - char *new_data = NULL; - const char *path = NULL; - const char *label = NULL; - unsigned menu_type = 0; - enum msg_hash_enums enum_idx = MSG_UNKNOWN; - net_driver_state_t *net_st = networking_state_get_ptr(); - http_transfer_data_t *data = (http_transfer_data_t*)task_data; + char *new_data = NULL; + const char *path = NULL; + const char *label = NULL; + unsigned menu_type = 0; + enum msg_hash_enums enum_idx = MSG_UNKNOWN; + net_driver_state_t *net_st = networking_state_get_ptr(); + http_transfer_data_t *data = task_data; + bool refresh = false; menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL); /* Don't push the results if we left the netplay menu */ - if (!string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) - && !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY))) + if (!string_is_equal(label, + msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) && + !string_is_equal(label, + msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY))) return; - if (!data || err) - goto finish; - - new_data = (char*)realloc(data->data, data->len + 1); + if (error) + { + RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), + error); + return; + } + if (!data || !data->data || !data->len || data->status != 200) + { + RARCH_ERR("%s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED)); + return; + } + new_data = realloc(data->data, data->len + 1); if (!new_data) - goto finish; - + return; data->data = new_data; data->data[data->len] = '\0'; - if (!string_ends_with_size(data->data, "registry.lpl", - strlen(data->data), - STRLEN_CONST("registry.lpl"))) + if (net_st->room_list) + free(net_st->room_list); + + net_st->room_list = NULL; + net_st->room_count = 0; + + if (!string_is_empty(data->data)) { - if (string_is_empty(data->data)) - net_st->room_count = 0; - else - { - char s[PATH_MAX_LENGTH]; - unsigned i = 0; - unsigned j = 0; - struct netplay_host_list *lan_hosts = NULL; - int lan_room_count = 0; - bool refresh = false; + int i; -#ifdef HAVE_NETPLAYDISCOVERY - netplay_discovery_driver_ctl(RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, &lan_hosts); - if (lan_hosts) - lan_room_count = (int)lan_hosts->size; -#endif + netplay_rooms_parse(data->data); - netplay_rooms_parse(data->data); + net_st->room_count = netplay_rooms_get_count(); + net_st->room_list = calloc(net_st->room_count, + sizeof(*net_st->room_list)); - if (net_st->room_list) - free(net_st->room_list); - - /* TODO/FIXME - right now, a LAN and non-LAN netplay session might appear - * in the same list. If both entries are available, we want to show only - * the LAN one. */ - - net_st->room_count = netplay_rooms_get_count(); - net_st->room_list = (struct netplay_room*) - calloc(net_st->room_count + lan_room_count, - sizeof(struct netplay_room)); - - for (i = 0; i < (unsigned)net_st->room_count; i++) - memcpy(&net_st->room_list[i], netplay_room_get(i), sizeof(net_st->room_list[i])); - - if (lan_room_count != 0) - { - for (i = net_st->room_count; i < (unsigned)(net_st->room_count + lan_room_count); i++) - { - struct netplay_host *host = &lan_hosts->hosts[j++]; - - strlcpy(net_st->room_list[i].nickname, - host->nick, - sizeof(net_st->room_list[i].nickname)); - - strlcpy(net_st->room_list[i].address, - host->address, - INET6_ADDRSTRLEN); - strlcpy(net_st->room_list[i].corename, - host->core, - sizeof(net_st->room_list[i].corename)); - strlcpy(net_st->room_list[i].retroarch_version, - host->retroarch_version, - sizeof(net_st->room_list[i].retroarch_version)); - strlcpy(net_st->room_list[i].coreversion, - host->core_version, - sizeof(net_st->room_list[i].coreversion)); - strlcpy(net_st->room_list[i].gamename, - host->content, - sizeof(net_st->room_list[i].gamename)); - strlcpy(net_st->room_list[i].frontend, - host->frontend, - sizeof(net_st->room_list[i].frontend)); - strlcpy(net_st->room_list[i].subsystem_name, - host->subsystem_name, - sizeof(net_st->room_list[i].subsystem_name)); - - net_st->room_list[i].port = host->port; - net_st->room_list[i].gamecrc = host->content_crc; - net_st->room_list[i].timestamp = 0; - net_st->room_list[i].lan = true; - - snprintf(s, sizeof(s), - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_ROOM_NICKNAME), - net_st->room_list[i].nickname); - } - net_st->room_count += lan_room_count; - } - - menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); - menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); - } + for (i = 0; i < net_st->room_count; i++) + memcpy(&net_st->room_list[i], netplay_room_get(i), + sizeof(*net_st->room_list)); } -finish: - - if (err) - RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), err); - - if (user_data) - free(user_data); + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); } - static int action_ok_push_netplay_refresh_rooms(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { - char url [2048] = "http://lobby.libretro.com/list/"; -#ifdef HAVE_NETPLAYDISCOVERY - task_push_netplay_lan_scan(netplay_lan_scan_callback); +#ifndef NETPLAY_TEST_BUILD + const char *url = "http://lobby.libretro.com/list"; +#else + const char *url = "http://lobbytest.libretro.com/list"; #endif + task_push_http_transfer(url, true, NULL, netplay_refresh_rooms_cb, NULL); + return 0; } + +#ifdef HAVE_NETPLAYDISCOVERY +static void netplay_refresh_lan_cb(retro_task_t *task, + void *task_data, void *user_data, const char *error) +{ + const char *path = NULL; + const char *label = NULL; + unsigned menu_type = 0; + enum msg_hash_enums enum_idx = MSG_UNKNOWN; + net_driver_state_t *net_st = networking_state_get_ptr(); + struct netplay_host_list *hosts = NULL; + bool refresh = false; + + menu_entries_get_last_stack(&path, &label, &menu_type, &enum_idx, NULL); + + /* Don't push the results if we left the netplay menu */ + if (!string_is_equal(label, + msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)) && + !string_is_equal(label, + msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY))) + goto finished; + + if (!netplay_discovery_driver_ctl( + RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES, &hosts) || + !hosts) + goto finished; + + if (net_st->room_list) + free(net_st->room_list); + + net_st->room_list = NULL; + net_st->room_count = 0; + + if (hosts->size) + { + int i; + + net_st->room_count = hosts->size; + net_st->room_list = calloc(net_st->room_count, + sizeof(*net_st->room_list)); + + for (i = 0; i < net_st->room_count; i++) + { + struct netplay_host *host = &hosts->hosts[i]; + struct netplay_room *room = &net_st->room_list[i]; + + room->port = host->port; + room->gamecrc = host->content_crc; + strlcpy(room->retroarch_version, host->retroarch_version, + sizeof(room->retroarch_version)); + strlcpy(room->nickname, host->nick, + sizeof(room->nickname)); + strlcpy(room->subsystem_name, host->subsystem_name, + sizeof(room->subsystem_name)); + strlcpy(room->corename, host->core, + sizeof(room->corename)); + strlcpy(room->frontend, host->frontend, + sizeof(room->frontend)); + strlcpy(room->coreversion, host->core_version, + sizeof(room->coreversion)); + strlcpy(room->gamename, host->content, + sizeof(room->gamename)); + strlcpy(room->address, host->address, + sizeof(room->address)); + room->has_password = host->has_password; + room->has_spectate_password = host->has_spectate_password; + room->lan = true; + } + } + + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); + +finished: + deinit_netplay_discovery(); +} + +static int action_ok_push_netplay_refresh_lan(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + task_push_netplay_lan_scan(netplay_refresh_lan_cb); + + return 0; +} +#endif #endif DEFAULT_ACTION_OK_DL_PUSH(action_ok_content_collection_list, FILEBROWSER_SELECT_COLLECTION, ACTION_OK_DL_CONTENT_COLLECTION_LIST, NULL) @@ -7758,6 +7703,9 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_DOWNLOAD_PL_ENTRY_THUMBNAILS, action_ok_pl_entry_content_thumbnails}, {MENU_ENUM_LABEL_UPDATE_LAKKA, action_ok_lakka_list}, {MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS, action_ok_push_netplay_refresh_rooms}, +#ifdef HAVE_NETPLAYDISCOVERY + {MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN, action_ok_push_netplay_refresh_lan}, +#endif #endif #ifdef HAVE_VIDEO_LAYOUT {MENU_ENUM_LABEL_ONSCREEN_VIDEO_LAYOUT_SETTINGS, action_ok_onscreen_video_layout_list}, @@ -8473,11 +8421,6 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, #ifdef HAVE_WIFI BIND_ACTION_OK(cbs, action_ok_wifi); #endif -#endif - break; - case MENU_NETPLAY_LAN_SCAN: -#if defined(HAVE_NETWORKING) && defined(HAVE_NETPLAYDISCOVERY) - BIND_ACTION_OK(cbs, action_ok_netplay_lan_scan); #endif break; case FILE_TYPE_CURSOR: diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index e779ef68f2..471ed644da 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -253,6 +253,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_framecount_show, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_memory_show, MENU_ENUM_SUBLABEL_MEMORY_SHOW) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_memory_update_interval, MENU_ENUM_SUBLABEL_MEMORY_UPDATE_INTERVAL) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_statistics_show, MENU_ENUM_SUBLABEL_STATISTICS_SHOW) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_ping_show, MENU_ENUM_SUBLABEL_NETPLAY_PING_SHOW) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_settings, MENU_ENUM_SUBLABEL_NETPLAY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_user_bind_settings, MENU_ENUM_SUBLABEL_INPUT_USER_BINDS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_hotkey_settings, MENU_ENUM_SUBLABEL_INPUT_HOTKEY_BINDS) @@ -287,9 +288,12 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_screenshot, ME DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_mute, MENU_ENUM_SUBLABEL_INPUT_META_MUTE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_osk, MENU_ENUM_SUBLABEL_INPUT_META_OSK) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_fps_toggle, MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_netplay_ping_toggle, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_PING_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_send_debug_info, MENU_ENUM_SUBLABEL_INPUT_META_SEND_DEBUG_INFO) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_netplay_host_toggle, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_HOST_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_netplay_game_watch, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_GAME_WATCH) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_netplay_player_chat, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_PLAYER_CHAT) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_netplay_fade_chat_toggle, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_enable_hotkey, MENU_ENUM_SUBLABEL_INPUT_META_ENABLE_HOTKEY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_volume_up, MENU_ENUM_SUBLABEL_INPUT_META_VOLUME_UP) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_volume_down, MENU_ENUM_SUBLABEL_INPUT_META_VOLUME_DOWN) @@ -613,9 +617,11 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_public_announce, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_ip_address, MENU_ENUM_SUBLABEL_NETPLAY_IP_ADDRESS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_tcp_udp_port, MENU_ENUM_SUBLABEL_NETPLAY_TCP_UDP_PORT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_max_connections, MENU_ENUM_SUBLABEL_NETPLAY_MAX_CONNECTIONS) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_max_ping, MENU_ENUM_SUBLABEL_NETPLAY_MAX_PING) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_password, MENU_ENUM_SUBLABEL_NETPLAY_PASSWORD) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_spectate_password, MENU_ENUM_SUBLABEL_NETPLAY_SPECTATE_PASSWORD) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_start_as_spectator, MENU_ENUM_SUBLABEL_NETPLAY_START_AS_SPECTATOR) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_fade_chat, MENU_ENUM_SUBLABEL_NETPLAY_FADE_CHAT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_allow_pausing, MENU_ENUM_SUBLABEL_NETPLAY_ALLOW_PAUSING) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_allow_slaves, MENU_ENUM_SUBLABEL_NETPLAY_ALLOW_SLAVES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_require_slaves, MENU_ENUM_SUBLABEL_NETPLAY_REQUIRE_SLAVES) @@ -671,6 +677,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_updater_show_experimental_cores DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_updater_auto_backup, MENU_ENUM_SUBLABEL_CORE_UPDATER_AUTO_BACKUP) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_updater_auto_backup_history_size, MENU_ENUM_SUBLABEL_CORE_UPDATER_AUTO_BACKUP_HISTORY_SIZE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_refresh_rooms, MENU_ENUM_SUBLABEL_NETPLAY_REFRESH_ROOMS) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_refresh_lan, MENU_ENUM_SUBLABEL_NETPLAY_REFRESH_LAN) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_rename_entry, MENU_ENUM_SUBLABEL_RENAME_ENTRY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_delete_entry, MENU_ENUM_SUBLABEL_DELETE_ENTRY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_information, MENU_ENUM_SUBLABEL_INFORMATION) @@ -1876,6 +1883,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case RARCH_FPS_TOGGLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_fps_toggle); return 0; + case RARCH_NETPLAY_PING_TOGGLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_netplay_ping_toggle); + return 0; case RARCH_SEND_DEBUG_INFO: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_send_debug_info); return 0; @@ -1885,6 +1895,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case RARCH_NETPLAY_GAME_WATCH: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_netplay_game_watch); return 0; + case RARCH_NETPLAY_PLAYER_CHAT: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_netplay_player_chat); + return 0; + case RARCH_NETPLAY_FADE_CHAT_TOGGLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_netplay_fade_chat_toggle); + return 0; case RARCH_ENABLE_HOTKEY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_enable_hotkey); return 0; @@ -2850,6 +2866,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_refresh_rooms); break; + case MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_refresh_lan); + break; case MENU_ENUM_LABEL_CORE_UPDATER_AUTO_EXTRACT_ARCHIVE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_updater_auto_extract_archive); break; @@ -2994,6 +3013,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_start_as_spectator); break; + case MENU_ENUM_LABEL_NETPLAY_FADE_CHAT: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_fade_chat); + break; case MENU_ENUM_LABEL_NETPLAY_ALLOW_PAUSING: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_allow_pausing); break; @@ -3012,6 +3034,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_spectate_password); break; + case MENU_ENUM_LABEL_NETPLAY_MAX_PING: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_max_ping); + break; case MENU_ENUM_LABEL_NETPLAY_MAX_CONNECTIONS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_max_connections); break; @@ -3916,6 +3941,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_max_swapchain_images); break; + case MENU_ENUM_LABEL_NETPLAY_PING_SHOW: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_ping_show); + break; case MENU_ENUM_LABEL_STATISTICS_SHOW: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_statistics_show); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 4bbded8135..3835dcd1d8 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -10571,6 +10571,7 @@ static void materialui_list_insert( string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CHEAT_APPLY_CHANGES)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_VIDEO_SHADER_PRESET)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_ENABLE_HOST)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_REMAP_FILE_LOAD)) || diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index 01f9404769..ceccbe408a 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -1876,6 +1876,10 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_ROOM]; case MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; +#ifdef HAVE_NETPLAYDISCOVERY + case MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN: + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; +#endif #endif case MENU_ENUM_LABEL_REBOOT: case MENU_ENUM_LABEL_RESET_TO_DEFAULT_CONFIG: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 4da4d10159..f73ca29ba3 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2824,6 +2824,10 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, return xmb->textures.list[XMB_TEXTURE_ROOM]; case MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS: return xmb->textures.list[XMB_TEXTURE_RELOAD]; +#ifdef HAVE_NETPLAYDISCOVERY + case MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN: + return xmb->textures.list[XMB_TEXTURE_RELOAD]; +#endif case MENU_ENUM_LABEL_NETWORK_INFORMATION: case MENU_ENUM_LABEL_NETWORK_SETTINGS: case MENU_ENUM_LABEL_WIFI_SETTINGS: diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 890c9376bc..1d860a982c 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -7302,9 +7302,11 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_NETPLAY_IP_ADDRESS, PARSE_ONLY_STRING, true}, {MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, PARSE_ONLY_UINT, true}, {MENU_ENUM_LABEL_NETPLAY_MAX_CONNECTIONS, PARSE_ONLY_UINT, true}, + {MENU_ENUM_LABEL_NETPLAY_MAX_PING, PARSE_ONLY_UINT, true}, {MENU_ENUM_LABEL_NETPLAY_PASSWORD, PARSE_ONLY_STRING, true}, {MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD, PARSE_ONLY_STRING, true}, {MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_NETPLAY_FADE_CHAT, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_NETPLAY_ALLOW_PAUSING, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_NETPLAY_ALLOW_SLAVES, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_NETPLAY_REQUIRE_SLAVES, PARSE_ONLY_BOOL, false}, @@ -8633,6 +8635,9 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_STATISTICS_SHOW, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_MEMORY_SHOW, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_MEMORY_UPDATE_INTERVAL, PARSE_ONLY_UINT, false }, +#if defined(HAVE_NETWORKING) && defined(HAVE_GFX_WIDGETS) + {MENU_ENUM_LABEL_NETPLAY_PING_SHOW, PARSE_ONLY_BOOL, false }, +#endif {MENU_ENUM_LABEL_MENU_SHOW_LOAD_CONTENT_ANIMATION, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_NOTIFICATION_SHOW_AUTOCONFIG, PARSE_ONLY_BOOL, false }, #ifdef HAVE_CHEATS @@ -8676,6 +8681,12 @@ unsigned menu_displaylist_build_list( build_list[i].checked = true; break; #ifdef HAVE_GFX_WIDGETS +#ifdef HAVE_NETWORKING + case MENU_ENUM_LABEL_NETPLAY_PING_SHOW: + if (widgets_active) + build_list[i].checked = true; + break; +#endif case MENU_ENUM_LABEL_MENU_SHOW_LOAD_CONTENT_ANIMATION: if (widgets_active) build_list[i].checked = true; @@ -9666,12 +9677,9 @@ static unsigned menu_displaylist_build_shader_parameter( #ifdef HAVE_NETWORKING unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list) { - char s[8300]; - int i = 0; - unsigned count = 0; - net_driver_state_t *net_st = networking_state_get_ptr(); - - s[0] = '\0'; + int i; + unsigned count = 0; + net_driver_state_t *net_st = networking_state_get_ptr(); menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, list); @@ -9715,71 +9723,77 @@ unsigned menu_displaylist_netplay_refresh_rooms(file_list_t *list) MENU_SETTING_ACTION, 0, 0)) count++; - if (net_st->room_count != 0) - { - for (i = 0; i < net_st->room_count; i++) - { - char country[8]; - char passworded[64]; - - if (!net_st->room_list[i].lan && - !string_is_empty(net_st->room_list[i].country)) - snprintf(country, sizeof(country), - "(%s)", net_st->room_list[i].country); - else - *country = '\0'; - - if (net_st->room_list[i].has_password || - net_st->room_list[i].has_spectate_password) - snprintf(passworded, sizeof(passworded), - "[%s]", msg_hash_to_str(MSG_ROOM_PASSWORDED)); - else - *passworded = '\0'; - - /* Uncomment this to debug mismatched room parameters*/ -#if 0 - RARCH_LOG("[Lobby]: Room Data: %d\n" - "Nickname: %s\n" - "Address: %s\n" - "Port: %d\n" - "Core: %s\n" - "Core Version: %s\n" - "Game: %s\n" - "Game CRC: %08x\n" - "Timestamp: %d\n", room_data->elems[j + 6].data, - net_st->room_list[i].nickname, - net_st->room_list[i].address, - net_st->room_list[i].port, - net_st->room_list[i].corename, - net_st->room_list[i].coreversion, - net_st->room_list[i].gamename, - net_st->room_list[i].gamecrc, - net_st->room_list[i].timestamp); +#ifdef HAVE_NETPLAYDISCOVERY + if (menu_entries_append_enum(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_REFRESH_LAN), + msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN), + MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN, + MENU_SETTING_ACTION, 0, 0)) + count++; #endif - snprintf(s, sizeof(s), "%s%s: %s%s", - passworded, - net_st->room_list[i].lan - ? msg_hash_to_str(MSG_LOCAL) - : (net_st->room_list[i].host_method - == NETPLAY_HOST_METHOD_MITM - ? msg_hash_to_str(MSG_INTERNET_RELAY) - : msg_hash_to_str(MSG_INTERNET)), - net_st->room_list[i].nickname, - country - ); + for (i = 0; i < net_st->room_count; i++) + { + char buf[8192]; + char passworded[64]; + char country[8]; + const char *room_type; + struct netplay_room *room = &net_st->room_list[i]; - if (menu_entries_append_enum(list, - s, - msg_hash_to_str(MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM), - MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM, - (unsigned)(MENU_SETTINGS_NETPLAY_ROOMS_START + i), 0, 0)) - count++; - } + if (room->has_password || room->has_spectate_password) + snprintf(passworded, sizeof(passworded), "[%s] ", + msg_hash_to_str(MSG_ROOM_PASSWORDED)); + else + *passworded = '\0'; - netplay_rooms_free(); + if (!room->lan && !string_is_empty(room->country)) + snprintf(country, sizeof(country), " (%s)", + room->country); + else + *country = '\0'; + + if (room->lan) + room_type = msg_hash_to_str(MSG_LOCAL); + else if (room->host_method == NETPLAY_HOST_METHOD_MITM) + room_type = msg_hash_to_str(MSG_INTERNET_RELAY); + else + room_type = msg_hash_to_str(MSG_INTERNET); + + snprintf(buf, sizeof(buf), "%s%s: %s%s", + passworded, room_type, + room->nickname, country); + + if (menu_entries_append_enum(list, + buf, + msg_hash_to_str(MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM), + MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM, + (unsigned)(MENU_SETTINGS_NETPLAY_ROOMS_START + i), 0, 0)) + count++; + + /* Uncomment this to debug mismatched room parameters*/ +#if 0 + RARCH_LOG("[Lobby]: Room Data: %d\n" + "Nickname: %s\n" + "Address: %s\n" + "Port: %d\n" + "Core: %s\n" + "Core Version: %s\n" + "Game: %s\n" + "Game CRC: %08x\n" + "Timestamp: %d\n", room_data->elems[j + 6].data, + room->nickname, + room->address, + room->port, + room->corename, + room->coreversion, + room->gamename, + room->gamecrc, + room->timestamp); +#endif } + netplay_rooms_free(); + return count; } @@ -9924,6 +9938,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, menu_displaylist_build_info_selective_t build_list[] = { {MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, PARSE_ONLY_UINT, true}, {MENU_ENUM_LABEL_NETPLAY_MAX_CONNECTIONS, PARSE_ONLY_UINT, true}, + {MENU_ENUM_LABEL_NETPLAY_MAX_PING, PARSE_ONLY_UINT, true}, {MENU_ENUM_LABEL_NETPLAY_PUBLIC_ANNOUNCE, PARSE_ONLY_BOOL, true }, {MENU_ENUM_LABEL_NETPLAY_USE_MITM_SERVER, PARSE_ONLY_BOOL, true }, {MENU_ENUM_LABEL_NETPLAY_MITM_SERVER, PARSE_ONLY_STRING, false}, diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 4263cfbcf0..bc9066396d 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -38,6 +38,10 @@ #include "../accessibility.h" #endif +#ifdef HAVE_NETWORKING +#include "../network/netplay/netplay.h" +#endif + #include "../audio/audio_driver.h" #include "menu_driver.h" @@ -6690,7 +6694,12 @@ void menu_driver_toggle( if (settings) { +#ifdef HAVE_NETWORKING + pause_libretro = settings->bools.menu_pause_libretro && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL); +#else pause_libretro = settings->bools.menu_pause_libretro; +#endif #ifdef HAVE_AUDIOMIXER audio_enable_menu = settings->bools.audio_enable_menu; #endif diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 423ebec87e..cc04b26426 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -14462,6 +14462,26 @@ static bool setting_append_list( SD_FLAG_NONE); #ifdef HAVE_GFX_WIDGETS +#ifdef HAVE_NETWORKING + CONFIG_BOOL( + list, list_info, + &settings->bools.netplay_ping_show, + MENU_ENUM_LABEL_NETPLAY_PING_SHOW, + MENU_ENUM_LABEL_VALUE_NETPLAY_PING_SHOW, + DEFAULT_NETPLAY_PING_SHOW, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + (*list)[list_info->index - 1].action_ok = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_left = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_right = &setting_bool_action_right_with_refresh; +#endif + CONFIG_BOOL( list, list_info, &settings->bools.menu_show_load_content_animation, @@ -19204,6 +19224,21 @@ static bool setting_append_list( (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; menu_settings_list_current_add_range(list, list_info, 1, 31, 1, true, true); + CONFIG_UINT( + list, list_info, + &settings->uints.netplay_max_ping, + MENU_ENUM_LABEL_NETPLAY_MAX_PING, + MENU_ENUM_LABEL_VALUE_NETPLAY_MAX_PING, + netplay_max_ping, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_SPINBOX; + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; + menu_settings_list_current_add_range(list, list_info, 0, 500, 25, true, true); + CONFIG_STRING( list, list_info, settings->paths.netplay_password, @@ -19251,6 +19286,21 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE); + CONFIG_BOOL( + list, list_info, + &settings->bools.netplay_fade_chat, + MENU_ENUM_LABEL_NETPLAY_FADE_CHAT, + MENU_ENUM_LABEL_VALUE_NETPLAY_FADE_CHAT, + netplay_fade_chat, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + CONFIG_BOOL( list, list_info, &settings->bools.netplay_allow_pausing, diff --git a/msg_hash.h b/msg_hash.h index 4e85ea4adb..ea4360299d 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -200,6 +200,7 @@ enum msg_hash_enums MSG_NETPLAY_ENDIAN_DEPENDENT, MSG_NETPLAY_PLATFORM_DEPENDENT, MSG_NETPLAY_ENTER_PASSWORD, + MSG_NETPLAY_ENTER_CHAT, MSG_NETPLAY_INCORRECT_PASSWORD, MSG_NETPLAY_SERVER_NAMED_HANGUP, MSG_NETPLAY_SERVER_HANGUP, @@ -330,6 +331,7 @@ enum msg_hash_enums MSG_COULD_NOT_FIND_ANY_NEXT_DRIVER, MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION, MSG_CONTENT_CRC32S_DIFFER, + MSG_PING_TOO_HIGH, MSG_RECORDING_TERMINATED_DUE_TO_RESIZE, MSG_FAILED_TO_START_RECORDING, MSG_REVERTING_SAVEFILE_DIRECTORY_TO, @@ -878,9 +880,12 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PING_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_SEND_DEBUG_INFO, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_HOST_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_PLAYER_CHAT, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_DOWN, @@ -935,9 +940,12 @@ enum msg_hash_enums MENU_ENUM_SUBLABEL_INPUT_META_MUTE, MENU_ENUM_SUBLABEL_INPUT_META_OSK, MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE, + MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_PING_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_SEND_DEBUG_INFO, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_HOST_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_GAME_WATCH, + MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_PLAYER_CHAT, + MENU_ENUM_SUBLABEL_INPUT_META_NETPLAY_FADE_CHAT_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_ENABLE_HOTKEY, MENU_ENUM_SUBLABEL_INPUT_META_VOLUME_UP, MENU_ENUM_SUBLABEL_INPUT_META_VOLUME_DOWN, @@ -1633,6 +1641,7 @@ enum msg_hash_enums MENU_LABEL(SCAN_DIRECTORY), MENU_LABEL(SCAN_FILE), MENU_LABEL(NETPLAY_REFRESH_ROOMS), + MENU_LABEL(NETPLAY_REFRESH_LAN), MENU_LABEL(NETPLAY_ROOM_NICKNAME), MENU_LABEL(NETPLAY_ROOM_NICKNAME_LAN), MENU_LABEL(ADD_CONTENT_LIST), @@ -1906,6 +1915,7 @@ enum msg_hash_enums MENU_LABEL(NETPLAY_DELAY_FRAMES), MENU_LABEL(NETPLAY_PUBLIC_ANNOUNCE), MENU_LABEL(NETPLAY_START_AS_SPECTATOR), + MENU_LABEL(NETPLAY_FADE_CHAT), MENU_LABEL(NETPLAY_ALLOW_PAUSING), MENU_LABEL(NETPLAY_ALLOW_SLAVES), MENU_LABEL(NETPLAY_REQUIRE_SLAVES), @@ -1916,8 +1926,10 @@ enum msg_hash_enums MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE), MENU_LABEL(NETPLAY_TCP_UDP_PORT), MENU_LABEL(NETPLAY_MAX_CONNECTIONS), + MENU_LABEL(NETPLAY_MAX_PING), MENU_LABEL(NETPLAY_NAT_TRAVERSAL), MENU_LABEL(NETPLAY_REQUEST_DEVICE_I), + MENU_LABEL(NETPLAY_PING_SHOW), MENU_ENUM_LABEL_NETPLAY_REQUEST_DEVICE_1, MENU_ENUM_LABEL_NETPLAY_REQUEST_DEVICE_LAST = MENU_ENUM_LABEL_NETPLAY_REQUEST_DEVICE_1 + MAX_USERS, MENU_ENUM_LABEL_VALUE_NETPLAY_SHARE_NONE, diff --git a/network/discord.c b/network/discord.c index b3d19edf3f..b4b8e31bf2 100644 --- a/network/discord.c +++ b/network/discord.c @@ -161,7 +161,7 @@ static void handle_discord_join_cb(retro_task_t *task, http_transfer_data_t *data = (http_transfer_data_t*)task_data; discord_state_t *discord_st = &discord_state_st; - if (!data || err || !data->data) + if (!data || err || !data->data || !data->len) goto finish; data->data = (char*)realloc(data->data, data->len + 1); @@ -172,17 +172,17 @@ static void handle_discord_join_cb(retro_task_t *task, if (room) { - bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM; - const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address; - unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port; + if (room->host_method == NETPLAY_HOST_METHOD_MITM) + snprintf(join_hostname, sizeof(join_hostname), "%s|%d|%s", + room->mitm_address, room->mitm_port, room->mitm_session); + else + snprintf(join_hostname, sizeof(join_hostname), "%s|%d", + room->address, room->port); if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) deinit_netplay(); netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); - snprintf(join_hostname, sizeof(join_hostname), "%s|%d", - srv_address, srv_port); - task_push_netplay_crc_scan(room->gamecrc, room->gamename, join_hostname, room->corename, room->subsystem_name); discord_st->connecting = true; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index bdee47b7b2..1482345663 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -2,6 +2,7 @@ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2016-2017 - Gregor Richards + * Copyright (C) 2021-2021 - Roberto V. Rampim * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- @@ -35,13 +36,23 @@ #include "../../core.h" -#define NETPLAY_HOST_STR_LEN 32 +#include "netplay_protocol.h" + +#define NETPLAY_NICK_LEN 32 +#define NETPLAY_HOST_STR_LEN 32 #define NETPLAY_HOST_LONGSTR_LEN 256 +#define NETPLAY_CHAT_MAX_MESSAGES 5 +#define NETPLAY_CHAT_MAX_SIZE 96 +#define NETPLAY_CHAT_FRAME_TIME 900 +#define NETPLAY_CHAT_NICKNAME_COLOR 0x00800000 +#define NETPLAY_CHAT_MESSAGE_COLOR 0xFFFFFF00 + enum rarch_netplay_ctl_state { RARCH_NETPLAY_CTL_NONE = 0, RARCH_NETPLAY_CTL_GAME_WATCH, + RARCH_NETPLAY_CTL_PLAYER_CHAT, RARCH_NETPLAY_CTL_POST_FRAME, RARCH_NETPLAY_CTL_PRE_FRAME, RARCH_NETPLAY_CTL_ENABLE_SERVER, @@ -52,6 +63,7 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_IS_SERVER, RARCH_NETPLAY_CTL_IS_CONNECTED, RARCH_NETPLAY_CTL_IS_DATA_INITED, + RARCH_NETPLAY_CTL_ALLOW_PAUSE, RARCH_NETPLAY_CTL_PAUSE, RARCH_NETPLAY_CTL_UNPAUSE, RARCH_NETPLAY_CTL_LOAD_SAVESTATE, @@ -104,17 +116,16 @@ typedef struct netplay netplay_t; struct ad_packet { uint32_t header; - uint32_t protocol_version; - uint32_t port; - char address[NETPLAY_HOST_STR_LEN]; - char retroarch_version[NETPLAY_HOST_STR_LEN]; - char nick[NETPLAY_HOST_STR_LEN]; - char frontend[NETPLAY_HOST_STR_LEN]; - char core[NETPLAY_HOST_STR_LEN]; - char core_version[NETPLAY_HOST_STR_LEN]; - char content[NETPLAY_HOST_LONGSTR_LEN]; - char content_crc[NETPLAY_HOST_STR_LEN]; - char subsystem_name[NETPLAY_HOST_STR_LEN]; + int content_crc; + int port; + uint32_t has_password; + char nick[NETPLAY_NICK_LEN]; + char frontend[NETPLAY_HOST_STR_LEN]; + char core[NETPLAY_HOST_STR_LEN]; + char core_version[NETPLAY_HOST_STR_LEN]; + char retroarch_version[NETPLAY_HOST_STR_LEN]; + char content[NETPLAY_HOST_LONGSTR_LEN]; + char subsystem_name[NETPLAY_HOST_LONGSTR_LEN]; }; typedef struct mitm_server @@ -133,7 +144,7 @@ static const mitm_server_t netplay_mitm_server_list[] = { struct netplay_room { struct netplay_room *next; - int id; + int id; int port; int mitm_port; int gamecrc; @@ -148,11 +159,12 @@ struct netplay_room char coreversion [256]; char gamename [256]; char address [256]; + char mitm_handle [33]; char mitm_address [256]; + char mitm_session [33]; bool has_password; bool has_spectate_password; bool lan; - bool fixed; }; struct netplay_rooms @@ -163,18 +175,18 @@ struct netplay_rooms struct netplay_host { - struct sockaddr addr; - socklen_t addrlen; int content_crc; int port; char address[NETPLAY_HOST_STR_LEN]; - char nick[NETPLAY_HOST_STR_LEN]; + char nick[NETPLAY_NICK_LEN]; char frontend[NETPLAY_HOST_STR_LEN]; char core[NETPLAY_HOST_STR_LEN]; char core_version[NETPLAY_HOST_STR_LEN]; char retroarch_version[NETPLAY_HOST_STR_LEN]; char content[NETPLAY_HOST_LONGSTR_LEN]; char subsystem_name[NETPLAY_HOST_LONGSTR_LEN]; + bool has_password; + bool has_spectate_password; }; struct netplay_host_list @@ -183,32 +195,60 @@ struct netplay_host_list size_t size; }; +struct netplay_chat_data +{ + char nick[NETPLAY_NICK_LEN]; + char msg[NETPLAY_CHAT_MAX_SIZE]; + uint32_t frames; +}; + +struct netplay_chat_buffer +{ + char nick[NETPLAY_NICK_LEN]; + char msg[NETPLAY_CHAT_MAX_SIZE]; + uint8_t alpha; +}; + +struct netplay_chat +{ + struct + { + struct netplay_chat_data data; + struct netplay_chat_buffer buffer; + } messages[NETPLAY_CHAT_MAX_MESSAGES]; + uint32_t message_slots; +}; + typedef struct { netplay_t *data; /* Used while Netplay is running */ struct netplay_room host_room; /* ptr alignment */ - netplay_t *handshake_password; struct netplay_room *room_list; struct netplay_rooms *rooms_data; +#ifdef HAVE_NETPLAYDISCOVERY + /* LAN discovery sockets */ + int lan_ad_server_fd; + int lan_ad_client_fd; + /* Packet buffer for advertisement and responses */ + struct ad_packet ad_packet_buffer; /* uint32_t alignment */ /* List of discovered hosts */ struct netplay_host_list discovered_hosts; -#ifdef HAVE_NETPLAYDISCOVERY size_t discovered_hosts_allocated; #endif int room_count; int reannounce; - unsigned server_port_deferred; - /* Packet buffer for advertisement and responses */ - struct ad_packet ad_packet_buffer; /* uint32_t alignment */ + int reping; + int latest_ping; uint16_t mapping[RETROK_LAST]; - char server_address_deferred[512]; + unsigned server_port_deferred; + char server_address_deferred[256]; + char server_session_deferred[32]; + bool netplay_client_deferred; /* Only used before init_netplay */ bool netplay_enabled; bool netplay_is_client; /* Used to avoid recursive netplay calls */ bool in_netplay; - bool netplay_client_deferred; - bool is_mitm; bool has_set_netplay_mode; bool has_set_netplay_ip_address; bool has_set_netplay_ip_port; @@ -216,6 +256,8 @@ typedef struct bool has_set_netplay_check_frames; /* NAT traversal info (if NAT traversal is used and serving) */ struct nat_traversal_data nat_traversal_request; + /* Chat messages */ + struct netplay_chat chat; } net_driver_state_t; net_driver_state_t *networking_state_get_ptr(void); @@ -246,6 +288,13 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused); */ void netplay_toggle_play_spectate(netplay_t *netplay); +/** + * netplay_input_chat + * + * Opens an input menu for sending netplay chat + */ +void netplay_input_chat(netplay_t *netplay); + /** * netplay_load_savestate * @netplay : pointer to netplay object @@ -322,9 +371,9 @@ void deinit_netplay(void); /** * init_netplay - * @direct_host : Host to connect to directly, if applicable (client only) * @server : server address to connect to (client only) * @port : TCP port to host on/connect to + * @mitm_session : Session id for MITM/tunnel (client only). * * Initializes netplay. * @@ -332,9 +381,9 @@ void deinit_netplay(void); * * Returns: true (1) if successful, otherwise false (0). **/ -bool init_netplay(void *direct_host, const char *server, unsigned port); +bool init_netplay(const char *server, unsigned port, const char *mitm_session); -bool init_netplay_deferred(const char* server, unsigned port); +bool init_netplay_deferred(const char *server, unsigned port, const char *mitm_session); void video_frame_net(const void *data, unsigned width, unsigned height, size_t pitch); @@ -355,4 +404,8 @@ bool netplay_discovery_driver_ctl( enum rarch_netplay_discovery_ctl_state state, void *data); #endif +bool netplay_decode_hostname(const char *hostname, + char *address, unsigned *port, char *session, size_t len); +bool netplay_is_lan_address(struct sockaddr_in *addr); + #endif diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 23bf1f0ba3..d77ad37d9d 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -2,6 +2,7 @@ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2021 - Daniel De Matteis * Copyright (C) 2016-2017 - Gregor Richards + * Copyright (C) 2021-2021 - Roberto V. Rampim * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- @@ -32,9 +33,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -65,6 +68,11 @@ #ifdef HAVE_MENU #include "../../menu/menu_input.h" +#include "../../menu/menu_driver.h" +#endif + +#ifdef HAVE_GFX_WIDGETS +#include "../../gfx/gfx_widgets.h" #endif #ifdef HAVE_DISCORD @@ -82,6 +90,26 @@ #define HAVE_INET6 1 #endif +#ifdef TCP_NODELAY +#define SET_TCP_NODELAY(fd) \ + { \ + int on = 1; \ + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, \ + (const char *) &on, sizeof(on)) < 0) \ + RARCH_WARN("[Netplay] Could not set netplay TCP socket to nodelay. Expect jitter.\n"); \ + } +#else +#define SET_TCP_NODELAY(fd) +#endif + +#if defined(F_SETFD) && defined(FD_CLOEXEC) +#define SET_FD_CLOEXEC(fd) \ + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) \ + RARCH_WARN("[Netplay] Cannot set netplay port to close-on-exec. It may fail to reopen.\n"); +#else +#define SET_FD_CLOEXEC(fd) +#endif + #define RECV(buf, sz) \ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \ if (recvd >= 0 && recvd < (ssize_t) (sz)) \ @@ -91,27 +119,23 @@ } \ else if (recvd < 0) +#define SET_PING() \ + ping = (int32_t)((cpu_features_get_time_usec() - connection->ping_timer) / 1000); \ + if (connection->ping < 0 || ping < connection->ping) \ + connection->ping = ping; + #define NETPLAY_MAGIC 0x52414E50 /* RANP */ #define FULL_MAGIC 0x46554C4C /* FULL */ #define POKE_MAGIC 0x504F4B45 /* POKE */ -/* - * AD PACKET FORMAT: - * - * Request: - * 1 word: RANQ (RetroArch Netplay Query) - * 1 word: Netplay protocol version - * - * Reply: - * 1 word : RANS (RetroArch Netplay Server) - * 1 word : Netplay protocol version - * 1 word : Port - * 8 words: RetroArch version - * 8 words: Nick - * 8 words: Core name - * 8 words: Core version - * 8 words: Content name (currently always blank) - */ +/* Discovery magics */ +#define DISCOVERY_QUERY_MAGIC 0x52414E51 /* RANQ */ +#define DISCOVERY_RESPONSE_MAGIC 0x52414E53 /* RANS */ + +/* MITM magics */ +#define MITM_SESSION_MAGIC 0x52415453 /* RATS */ +#define MITM_LINK_MAGIC 0x5241544C /* RATL */ +#define MITM_PING_MAGIC 0x52415450 /* RATP */ struct vote_count { @@ -138,15 +162,6 @@ struct info_buf_s char core_version[NETPLAY_NICK_LEN]; }; -/* TODO/FIXME - globals */ -static unsigned long simple_rand_next = 1; - -#ifdef HAVE_NETPLAYDISCOVERY -/* LAN discovery sockets */ -static int lan_ad_server_fd = -1; -static int lan_ad_client_fd = -1; -#endif - /* The keys supported by netplay */ enum netplay_keys { @@ -179,9 +194,7 @@ net_driver_state_t *networking_state_get_ptr(void) return &networking_driver_st; } -#ifdef HAVE_NETPLAYDISCOVERY #ifdef HAVE_SOCKET_LEGACY - #ifndef htons /* The fact that I need to write this is deeply depressing */ static int16_t htons_for_morons(int16_t value) @@ -196,76 +209,164 @@ static int16_t htons_for_morons(int16_t value) #define htons htons_for_morons #endif +#ifndef ntohs +static int16_t ntohs_for_morons(int16_t value) +{ + union { + int32_t l; + int16_t s[2]; + } val; + val.l = ntohl(value); + return val.l == value ? val.s[1] : val.s[0]; +} +#define ntohs ntohs_for_morons +#endif #endif -static bool netplay_lan_ad_client(void) +#ifdef HAVE_NETPLAYDISCOVERY +/** Initialize Netplay discovery (client) */ +bool init_netplay_discovery(void) +{ + struct addrinfo *addr = NULL; + net_driver_state_t *net_st = &networking_driver_st; + int fd = socket_init( + (void **) &addr, 0, NULL, + SOCKET_TYPE_DATAGRAM); + bool ret = fd >= 0 && + socket_bind(fd, addr); + + if (ret) + { +#if defined(SOL_SOCKET) && defined(SO_BROADCAST) + /* Make it broadcastable */ + int broadcast = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, + (const char *) &broadcast, sizeof(broadcast)) < 0) + RARCH_WARN("[Discovery] Failed to set netplay discovery port to broadcast.\n"); +#endif + + net_st->lan_ad_client_fd = fd; + } + else + { + if (fd >= 0) + socket_close(fd); + + net_st->lan_ad_client_fd = -1; + + RARCH_ERR("[Discovery] Failed to initialize netplay advertisement client socket.\n"); + } + + if (addr) + freeaddrinfo_retro(addr); + + return ret; +} + +/** Deinitialize Netplay discovery (client) */ +void deinit_netplay_discovery(void) { - unsigned i; - fd_set fds; - socklen_t addr_size; - struct sockaddr their_addr; - struct timeval tmp_tv = {0}; net_driver_state_t *net_st = &networking_driver_st; - if (lan_ad_client_fd < 0) - return false; + if (net_st->lan_ad_client_fd >= 0) + { + socket_close(net_st->lan_ad_client_fd); + net_st->lan_ad_client_fd = -1; + } +} - their_addr.sa_family = 0; - for (i = 0; i < 14; i++) - their_addr.sa_data[i] = 0; +static bool netplay_lan_ad_client_query(void) +{ + char port[6]; + uint32_t header; + struct addrinfo *addr = NULL; + struct addrinfo hints = {0}; + net_driver_state_t *net_st = &networking_driver_st; + bool ret = false; + + /* Get the broadcast address (IPv4 only for now) */ + snprintf(port, sizeof(port), "%hu", + (uint16_t) RARCH_DEFAULT_PORT); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + if (getaddrinfo_retro("255.255.255.255", port, &hints, &addr)) + return ret; + if (!addr) + return ret; + + /* Put together the request */ + header = htonl(DISCOVERY_QUERY_MAGIC); + + /* And send it off */ + if (sendto(net_st->lan_ad_client_fd, (char *) &header, sizeof(header), + 0, addr->ai_addr, addr->ai_addrlen) == sizeof(header)) + ret = true; + else + RARCH_ERR("[Discovery] Failed to send netplay discovery query (error: %d).\n", errno); + + freeaddrinfo_retro(addr); + + return ret; +} + +static bool netplay_lan_ad_client_response(void) +{ + net_driver_state_t *net_st = &networking_driver_st; /* Check for any ad queries */ for (;;) { - FD_ZERO(&fds); - FD_SET(lan_ad_client_fd, &fds); - if (socket_select(lan_ad_client_fd + 1, - &fds, NULL, NULL, &tmp_tv) <= 0) - break; + fd_set fds; + struct timeval tv = {0}; + struct sockaddr_storage their_addr = {0}; + socklen_t addr_size = sizeof(their_addr); - if (!FD_ISSET(lan_ad_client_fd, &fds)) + FD_ZERO(&fds); + FD_SET(net_st->lan_ad_client_fd, &fds); + tv.tv_usec = 500000; + if (socket_select(net_st->lan_ad_client_fd + 1, + &fds, NULL, NULL, &tv) <= 0) break; /* Somebody queried, so check that it's valid */ - addr_size = sizeof(their_addr); - - if (recvfrom(lan_ad_client_fd, (char*)&net_st->ad_packet_buffer, - sizeof(struct ad_packet), 0, &their_addr, &addr_size) >= - (ssize_t) sizeof(struct ad_packet)) + if (recvfrom(net_st->lan_ad_client_fd, + (char *) &net_st->ad_packet_buffer, + sizeof(net_st->ad_packet_buffer), 0, + (struct sockaddr *) &their_addr, + &addr_size) == sizeof(net_st->ad_packet_buffer)) { + char address[256]; struct netplay_host *host = NULL; /* Make sure it's a valid response */ - if (memcmp((void *) &net_st->ad_packet_buffer, "RANS", 4)) - continue; - - /* For this version */ - if (ntohl(net_st->ad_packet_buffer.protocol_version) - != NETPLAY_PROTOCOL_VERSION) + if (ntohl(net_st->ad_packet_buffer.header) != + DISCOVERY_RESPONSE_MAGIC) continue; /* And that we know how to handle it */ - if (their_addr.sa_family == AF_INET) + if (their_addr.ss_family != AF_INET) + continue; + if (!netplay_is_lan_address( + (struct sockaddr_in *) &their_addr)) + continue; + +#ifndef HAVE_SOCKET_LEGACY + if (getnameinfo((struct sockaddr *) + &their_addr, sizeof(their_addr), + address, sizeof(address), NULL, 0, + NI_NUMERICHOST)) + continue; +#else + /* We need to convert the address manually */ { - struct sockaddr_in *sin = NULL; - - RARCH_WARN ("[Discovery] Using IPv4 for discovery\n"); - sin = (struct sockaddr_in *) &their_addr; - sin->sin_port = htons(ntohl(net_st->ad_packet_buffer.port)); - - } -#ifdef HAVE_INET6 - else if (their_addr.sa_family == AF_INET6) - { - struct sockaddr_in6 *sin6 = NULL; - RARCH_WARN ("[Discovery] Using IPv6 for discovery\n"); - sin6 = (struct sockaddr_in6 *) &their_addr; - sin6->sin6_port = htons(net_st->ad_packet_buffer.port); - + uint8_t *addr8 = (uint8_t *) + &((struct sockaddr_in *) &their_addr)->sin_addr; + snprintf(address, sizeof(address), + "%d.%d.%d.%d", + (int)addr8[0], (int)addr8[1], + (int)addr8[2], (int)addr8[3]); } #endif - else - continue; /* Allocate space for it */ if (net_st->discovered_hosts.size >= net_st->discovered_hosts_allocated) @@ -299,150 +400,62 @@ static bool netplay_lan_ad_client(void) host = &net_st->discovered_hosts.hosts[net_st->discovered_hosts.size++]; /* Copy in the response */ - memset(host, 0, sizeof(struct netplay_host)); - host->addr = their_addr; - host->addrlen = addr_size; - - host->port = ntohl(net_st->ad_packet_buffer.port); - + host->content_crc = ntohl(net_st->ad_packet_buffer.content_crc); + host->port = ntohl(net_st->ad_packet_buffer.port); strlcpy(host->address, - net_st->ad_packet_buffer.address, - NETPLAY_HOST_STR_LEN); + address, + sizeof(host->address)); strlcpy(host->nick, - net_st->ad_packet_buffer.nick, - NETPLAY_HOST_STR_LEN); - strlcpy(host->core, - net_st->ad_packet_buffer.core, - NETPLAY_HOST_STR_LEN); - strlcpy(host->retroarch_version, - net_st->ad_packet_buffer.retroarch_version, - NETPLAY_HOST_STR_LEN); - strlcpy(host->core_version, - net_st->ad_packet_buffer.core_version, - NETPLAY_HOST_STR_LEN); - strlcpy(host->content, - net_st->ad_packet_buffer.content, - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(host->subsystem_name, - net_st->ad_packet_buffer.subsystem_name, - NETPLAY_HOST_LONGSTR_LEN); + net_st->ad_packet_buffer.nick, + sizeof(host->nick)); strlcpy(host->frontend, - net_st->ad_packet_buffer.frontend, - NETPLAY_HOST_STR_LEN); - - host->content_crc = - atoi(net_st->ad_packet_buffer.content_crc); - host->nick[NETPLAY_HOST_STR_LEN-1] = - host->core[NETPLAY_HOST_STR_LEN-1] = - host->core_version[NETPLAY_HOST_STR_LEN-1] = - host->content[NETPLAY_HOST_LONGSTR_LEN-1] = '\0'; + net_st->ad_packet_buffer.frontend, + sizeof(host->frontend)); + strlcpy(host->core, + net_st->ad_packet_buffer.core, + sizeof(host->core)); + strlcpy(host->core_version, + net_st->ad_packet_buffer.core_version, + sizeof(host->core_version)); + strlcpy(host->retroarch_version, + net_st->ad_packet_buffer.retroarch_version, + sizeof(host->retroarch_version)); + strlcpy(host->content, + net_st->ad_packet_buffer.content, + sizeof(host->content)); + strlcpy(host->subsystem_name, + net_st->ad_packet_buffer.subsystem_name, + sizeof(host->subsystem_name)); + if (net_st->ad_packet_buffer.has_password & 1) + host->has_password = true; + else + host->has_password = false; + if (net_st->ad_packet_buffer.has_password & 2) + host->has_spectate_password = true; + else + host->has_spectate_password = false; } } return true; } -/** Initialize Netplay discovery (client) */ -bool init_netplay_discovery(void) -{ - struct addrinfo *addr = NULL; - int fd = socket_init((void **)&addr, 0, NULL, SOCKET_TYPE_DATAGRAM); - - if (fd < 0) - goto error; - - if (!socket_bind(fd, (void*)addr)) - { - socket_close(fd); - goto error; - } - - lan_ad_client_fd = fd; - freeaddrinfo_retro(addr); - return true; - -error: - if (addr) - freeaddrinfo_retro(addr); - RARCH_ERR("[Discovery] Failed to initialize netplay advertisement client socket.\n"); - return false; -} - -/** Deinitialize and free Netplay discovery */ -/* TODO/FIXME - this is apparently never called? */ -void deinit_netplay_discovery(void) -{ - if (lan_ad_client_fd >= 0) - { - socket_close(lan_ad_client_fd); - lan_ad_client_fd = -1; - } -} - /** Discovery control */ -/* TODO/FIXME: implement net_ifinfo and ntohs for consoles */ bool netplay_discovery_driver_ctl( - enum rarch_netplay_discovery_ctl_state state, void *data) + enum rarch_netplay_discovery_ctl_state state, void *data) { - int ret; - char port_str[6]; - unsigned k = 0; net_driver_state_t *net_st = &networking_driver_st; - if (lan_ad_client_fd < 0) + if (net_st->lan_ad_client_fd < 0) return false; switch (state) { case RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY: - { - net_ifinfo_t interfaces; - struct addrinfo hints = {0}, *addr; - - if (!net_ifinfo_new(&interfaces)) - return false; - - /* Get the broadcast address (IPv4 only for now) */ - snprintf(port_str, 6, "%hu", (unsigned short) RARCH_DEFAULT_PORT); - if (getaddrinfo_retro("255.255.255.255", port_str, &hints, &addr) < 0) - return false; - - /* Make it broadcastable */ -#if defined(SOL_SOCKET) && defined(SO_BROADCAST) - { - int can_broadcast = 1; - if (setsockopt(lan_ad_client_fd, SOL_SOCKET, SO_BROADCAST, - (const char *)&can_broadcast, sizeof(can_broadcast)) < 0) - RARCH_WARN("[Discovery] Failed to set netplay discovery port to broadcast\n"); - } -#endif - - /* Put together the request */ - memcpy((void *)&net_st->ad_packet_buffer, "RANQ", 4); - net_st->ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION); - - for (k = 0; k < (unsigned)interfaces.size; k++) - { - strlcpy(net_st->ad_packet_buffer.address, - interfaces.entries[k].host, - NETPLAY_HOST_STR_LEN); - - /* And send it off */ - ret = (int)sendto(lan_ad_client_fd, (const char *) - &net_st->ad_packet_buffer, - sizeof(struct ad_packet), 0, addr->ai_addr, addr->ai_addrlen); - if (ret < (ssize_t) (2*sizeof(uint32_t))) - RARCH_WARN("[Discovery] Failed to send netplay discovery query (error: %d)\n", errno); - } - - freeaddrinfo_retro(addr); - net_ifinfo_free(&interfaces); - - break; - } + return netplay_lan_ad_client_query(); case RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES: - if (!netplay_lan_ad_client()) + if (!netplay_lan_ad_client_response()) return false; *((struct netplay_host_list **) data) = &net_st->discovered_hosts; break; @@ -454,32 +467,51 @@ bool netplay_discovery_driver_ctl( default: return false; } + return true; } -static bool init_lan_ad_server_socket(netplay_t *netplay, uint16_t port) +/** Initialize Netplay discovery */ +static bool init_lan_ad_server_socket(void) { - struct addrinfo *addr = NULL; - int fd = socket_init((void **) &addr, port, NULL, SOCKET_TYPE_DATAGRAM); + struct addrinfo *addr = NULL; + net_driver_state_t *net_st = &networking_driver_st; + int fd = socket_init( + (void **) &addr, RARCH_DEFAULT_PORT, NULL, + SOCKET_TYPE_DATAGRAM); + bool ret = fd >= 0 && + socket_bind(fd, addr); - if (fd < 0) - goto error; - - if (!socket_bind(fd, (void*)addr)) + if (ret) { - socket_close(fd); - goto error; + net_st->lan_ad_server_fd = fd; + } + else + { + if (fd >= 0) + socket_close(fd); + + net_st->lan_ad_server_fd = -1; + + RARCH_ERR("[Discovery] Failed to initialize netplay advertisement socket.\n");; } - lan_ad_server_fd = fd; - freeaddrinfo_retro(addr); - - return true; - -error: if (addr) freeaddrinfo_retro(addr); - return false; + + return ret; +} + +/** Deinitialize Netplay discovery */ +static void deinit_lan_ad_server_socket(void) +{ + net_driver_state_t *net_st = &networking_driver_st; + + if (net_st->lan_ad_server_fd >= 0) + { + socket_close(net_st->lan_ad_server_fd); + net_st->lan_ad_server_fd = -1; + } } /** @@ -487,193 +519,140 @@ error: * * Respond to any LAN ad queries that the netplay server has received. */ -bool netplay_lan_ad_server(netplay_t *netplay) +static bool netplay_lan_ad_server(netplay_t *netplay) { - /* TODO/FIXME: implement net_ifinfo and ntohs for consoles */ fd_set fds; - int ret; - unsigned i; - char buf[4096]; - net_ifinfo_t interfaces; - socklen_t addr_size; - char reply_addr[NETPLAY_HOST_STR_LEN], port_str[6]; - struct sockaddr their_addr; - struct timeval tmp_tv = {0}; - unsigned k = 0; - struct addrinfo *our_addr, hints = {0}; - struct string_list *subsystem = path_get_subsystem_list(); - net_driver_state_t *net_st = &networking_driver_st; - - interfaces.entries = NULL; - interfaces.size = 0; - - their_addr.sa_family = 0; - for (i = 0; i < 14; i++) - their_addr.sa_data[i] = 0; - - if (!net_ifinfo_new(&interfaces)) - return false; - - if ( (lan_ad_server_fd < 0) - && !init_lan_ad_server_socket(netplay, RARCH_DEFAULT_PORT)) - { - RARCH_ERR("[Discovery] Failed to initialize netplay advertisement socket\n"); - return false; - } + uint32_t header; + struct timeval tv = {0}; + struct sockaddr_storage their_addr = {0}; + socklen_t addr_size = sizeof(their_addr); + net_driver_state_t *net_st = &networking_driver_st; /* Check for any ad queries */ - for (;;) + FD_ZERO(&fds); + FD_SET(net_st->lan_ad_server_fd, &fds); + if (socket_select(net_st->lan_ad_server_fd + 1, + &fds, NULL, NULL, &tv) < 0) { - FD_ZERO(&fds); - FD_SET(lan_ad_server_fd, &fds); - if (socket_select(lan_ad_server_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - break; - if (!FD_ISSET(lan_ad_server_fd, &fds)) - break; - - /* Somebody queried, so check that it's valid */ - addr_size = sizeof(their_addr); - ret = (int)recvfrom(lan_ad_server_fd, (char*) - &net_st->ad_packet_buffer, - sizeof(struct ad_packet), 0, &their_addr, &addr_size); - if (ret >= (ssize_t) (2 * sizeof(uint32_t))) - { - char s[NETPLAY_HOST_STR_LEN]; - uint32_t content_crc = 0; - - /* Make sure it's a valid query */ - if (memcmp((void *) &net_st->ad_packet_buffer, "RANQ", 4)) - { - RARCH_LOG("[Discovery] Invalid query\n"); - continue; - } - - /* For this version */ - if (ntohl(net_st->ad_packet_buffer.protocol_version) != - NETPLAY_PROTOCOL_VERSION) - { - RARCH_LOG("[Discovery] Invalid protocol version\n"); - continue; - } - - if (!string_is_empty(net_st->ad_packet_buffer.address)) - strlcpy(reply_addr, - net_st->ad_packet_buffer.address, - NETPLAY_HOST_STR_LEN); - - for (k = 0; k < interfaces.size; k++) - { - char *p; - char sub[NETPLAY_HOST_STR_LEN]; - char frontend_architecture_tmp[32]; - char frontend[256]; - const frontend_ctx_driver_t *frontend_drv = - (const frontend_ctx_driver_t*) - frontend_driver_get_cpu_architecture_str( - frontend_architecture_tmp, sizeof(frontend_architecture_tmp)); - snprintf(frontend, sizeof(frontend), "%s %s", - frontend_drv->ident, frontend_architecture_tmp); - - p=strrchr(reply_addr,'.'); - if (p) - { - strlcpy(sub, reply_addr, p - reply_addr + 1); - if (strstr(interfaces.entries[k].host, sub) && - !strstr(interfaces.entries[k].host, "127.0.0.1")) - { - struct retro_system_info *info = &runloop_state_get_ptr()->system.info; - - RARCH_LOG ("[Discovery] Query received on common interface: %s/%s (theirs / ours) \n", - reply_addr, interfaces.entries[k].host); - - /* Now build our response */ - buf[0] = '\0'; - content_crc = content_get_crc(); - - memset(&net_st->ad_packet_buffer, - 0, sizeof(struct ad_packet)); - memcpy(&net_st->ad_packet_buffer, "RANS", 4); - - if (subsystem) - { - unsigned i; - - for (i = 0; i < subsystem->size; i++) - { - strlcat(buf, path_basename(subsystem->elems[i].data), NETPLAY_HOST_LONGSTR_LEN); - if (i < subsystem->size - 1) - strlcat(buf, "|", NETPLAY_HOST_LONGSTR_LEN); - } - strlcpy(net_st->ad_packet_buffer.content, buf, - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(net_st->ad_packet_buffer.subsystem_name, - path_get(RARCH_PATH_SUBSYSTEM), - NETPLAY_HOST_STR_LEN); - } - else - { - strlcpy(net_st->ad_packet_buffer.content, - !string_is_empty( - path_basename(path_get(RARCH_PATH_BASENAME))) - ? path_basename(path_get(RARCH_PATH_BASENAME)) - : "N/A", - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(net_st->ad_packet_buffer.subsystem_name, - "N/A", - NETPLAY_HOST_STR_LEN); - } - - strlcpy(net_st->ad_packet_buffer.address, - interfaces.entries[k].host, - NETPLAY_HOST_STR_LEN); - net_st->ad_packet_buffer.protocol_version = - htonl(NETPLAY_PROTOCOL_VERSION); - net_st->ad_packet_buffer.port = htonl(netplay->tcp_port); - strlcpy(net_st->ad_packet_buffer.retroarch_version, - PACKAGE_VERSION, - NETPLAY_HOST_STR_LEN); - strlcpy(net_st->ad_packet_buffer.nick, - netplay->nick, NETPLAY_HOST_STR_LEN); - strlcpy(net_st->ad_packet_buffer.frontend, - frontend, NETPLAY_HOST_STR_LEN); - - if (info) - { - strlcpy(net_st->ad_packet_buffer.core, - info->library_name, - NETPLAY_HOST_STR_LEN); - strlcpy(net_st->ad_packet_buffer.core_version, - info->library_version, - NETPLAY_HOST_STR_LEN); - } - - snprintf(s, sizeof(s), "%ld", (long)content_crc); - strlcpy(net_st->ad_packet_buffer.content_crc, s, - NETPLAY_HOST_STR_LEN); - - /* Build up the destination address*/ - snprintf(port_str, 6, "%hu", ntohs(((struct sockaddr_in*)(&their_addr))->sin_port)); - if (getaddrinfo_retro(reply_addr, port_str, &hints, &our_addr) < 0) - continue; - - RARCH_LOG ("[Discovery] Sending reply to %s \n", reply_addr); - - /* And send it */ - sendto(lan_ad_server_fd, - (const char*)&net_st->ad_packet_buffer, - sizeof(struct ad_packet), - 0, our_addr->ai_addr, our_addr->ai_addrlen); - freeaddrinfo_retro(our_addr); - } - else - continue; - } - else - continue; - } - } + deinit_lan_ad_server_socket(); + return false; } - net_ifinfo_free(&interfaces); + if (!FD_ISSET(net_st->lan_ad_server_fd, &fds)) + return true; + + /* Somebody queried, so check that it's valid */ + if (recvfrom(net_st->lan_ad_server_fd, + (char *) &header, sizeof(header), 0, + (struct sockaddr *) &their_addr, + &addr_size) == sizeof(header)) + { + const frontend_ctx_driver_t *frontend_drv; + char frontend_architecture_tmp[32]; + uint32_t content_crc = 0; + struct retro_system_info *system = &runloop_state_get_ptr()->system.info; + struct string_list *subsystem = path_get_subsystem_list(); + settings_t *settings = config_get_ptr(); + + /* Make sure it's a valid query */ + if (ntohl(header) != DISCOVERY_QUERY_MAGIC) + { + RARCH_WARN("[Discovery] Invalid query.\n"); + return true; + } + + if (their_addr.ss_family != AF_INET) + return true; + if (!netplay_is_lan_address( + (struct sockaddr_in *) &their_addr)) + return true; + + RARCH_LOG("[Discovery] Query received on LAN interface.\n"); + + /* Now build our response */ + memset(&net_st->ad_packet_buffer, 0, + sizeof(net_st->ad_packet_buffer)); + + net_st->ad_packet_buffer.header = htonl(DISCOVERY_RESPONSE_MAGIC); + + net_st->ad_packet_buffer.port = htonl((int) netplay->tcp_port); + + strlcpy(net_st->ad_packet_buffer.nick, netplay->nick, + sizeof(net_st->ad_packet_buffer.nick)); + + frontend_drv = + (const frontend_ctx_driver_t*) frontend_driver_get_cpu_architecture_str( + frontend_architecture_tmp, sizeof(frontend_architecture_tmp)); + if (frontend_drv) + snprintf(net_st->ad_packet_buffer.frontend, + sizeof(net_st->ad_packet_buffer.frontend), + "%s %s", + frontend_drv->ident, frontend_architecture_tmp); + else + strlcpy(net_st->ad_packet_buffer.frontend, + "N/A", + sizeof(net_st->ad_packet_buffer.frontend)); + + strlcpy(net_st->ad_packet_buffer.core, + system->library_name, + sizeof(net_st->ad_packet_buffer.core)); + strlcpy(net_st->ad_packet_buffer.core_version, + system->library_version, + sizeof(net_st->ad_packet_buffer.core_version)); + + strlcpy(net_st->ad_packet_buffer.retroarch_version, + PACKAGE_VERSION, + sizeof(net_st->ad_packet_buffer.retroarch_version)); + + if (subsystem && subsystem->size > 0) + { + unsigned i; + char buf[4096]; + + buf[0] = '\0'; + for (i = 0;;) + { + strlcat(buf, path_basename(subsystem->elems[i++].data), sizeof(buf)); + if (i >= subsystem->size) + break; + strlcat(buf, "|", sizeof(buf)); + } + + strlcpy(net_st->ad_packet_buffer.content, buf, + sizeof(net_st->ad_packet_buffer.content)); + strlcpy(net_st->ad_packet_buffer.subsystem_name, + path_get(RARCH_PATH_SUBSYSTEM), + sizeof(net_st->ad_packet_buffer.subsystem_name)); + + content_crc = 0; + } + else + { + const char *base = path_basename(path_get(RARCH_PATH_BASENAME)); + + strlcpy(net_st->ad_packet_buffer.content, + !string_is_empty(base) ? base : "N/A", + sizeof(net_st->ad_packet_buffer.content)); + strlcpy(net_st->ad_packet_buffer.subsystem_name, "N/A", + sizeof(net_st->ad_packet_buffer.subsystem_name)); + + content_crc = content_get_crc(); + } + + net_st->ad_packet_buffer.content_crc = htonl(content_crc); + + net_st->ad_packet_buffer.has_password = 0; + if (!string_is_empty(settings->paths.netplay_password)) + net_st->ad_packet_buffer.has_password |= 1; + if (!string_is_empty(settings->paths.netplay_spectate_password)) + net_st->ad_packet_buffer.has_password |= 2; + + /* Send our response */ + sendto(net_st->lan_ad_server_fd, + (char *) &net_st->ad_packet_buffer, + sizeof(net_st->ad_packet_buffer), + 0, (struct sockaddr *) &their_addr, sizeof(their_addr)); + } + return true; } #endif @@ -804,17 +783,17 @@ static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb) return (pma & ebit) != (pmb & ebit); } -static int simple_rand(void) +static int simple_rand(unsigned long *simple_rand_next) { - simple_rand_next = simple_rand_next * 1103515245 + 12345; - return((unsigned)(simple_rand_next / 65536) % 32768); + *simple_rand_next = *simple_rand_next * 1103515245 + 12345; + return((unsigned)(*simple_rand_next / 65536) % 32768); } -static uint32_t simple_rand_uint32(void) +static uint32_t simple_rand_uint32(unsigned long *simple_rand_next) { - uint32_t part0 = simple_rand(); - uint32_t part1 = simple_rand(); - uint32_t part2 = simple_rand(); + uint32_t part0 = simple_rand(simple_rand_next); + uint32_t part1 = simple_rand(simple_rand_next); + uint32_t part2 = simple_rand(simple_rand_next); return ((part0 << 30) + (part1 << 15) + part2); } @@ -852,33 +831,46 @@ static void netplay_deinit_socket_buffer(struct socket_buffer *sbuf) * Initialize our handshake and send the first part of the handshake protocol. */ bool netplay_handshake_init_send(netplay_t *netplay, - struct netplay_connection *connection) + struct netplay_connection *connection, uint32_t protocol) { uint32_t header[6]; - unsigned conn_salt = 0; settings_t *settings = config_get_ptr(); header[0] = htonl(NETPLAY_MAGIC); header[1] = htonl(netplay_platform_magic()); header[2] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - header[3] = 0; - header[4] = htonl(NETPLAY_PROTOCOL_VERSION); - header[5] = htonl(netplay_impl_magic()); - if (netplay->is_server && - (settings->paths.netplay_password[0] || - settings->paths.netplay_spectate_password[0])) + if (netplay->is_server) { - /* Demand a password */ - if (simple_rand_next == 1) - simple_rand_next = (unsigned int)time(NULL); - connection->salt = simple_rand_uint32(); - if (connection->salt == 0) - connection->salt = 1; - conn_salt = connection->salt; + if (!string_is_empty(settings->paths.netplay_password) || + !string_is_empty(settings->paths.netplay_spectate_password)) + { + /* Demand a password */ + if (netplay->simple_rand_next == 1) + netplay->simple_rand_next = (unsigned long) time(NULL); + connection->salt = simple_rand_uint32(&netplay->simple_rand_next); + if (!connection->salt) + connection->salt = 1; + header[3] = htonl(connection->salt); + } + else + header[3] = 0; + } + else + { + /* HACK ALERT!!! + * We need to do this in order to maintain full backwards compatibility. + * Send our highest available protocol in the unused salt field. + * Servers can then pick the best protocol choice for the client. */ + header[3] = htonl(HIGH_NETPLAY_PROTOCOL_VERSION); } - header[3] = htonl(conn_salt); + header[4] = htonl(protocol); + header[5] = htonl(netplay_impl_magic()); + + /* First ping */ + connection->ping = -1; + connection->ping_timer = cpu_features_get_time_usec(); if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, sizeof(header)) || @@ -894,9 +886,14 @@ static void handshake_password(void *ignore, const char *line) struct password_buf_s password_buf; char password[8+NETPLAY_PASS_LEN]; /* 8 for salt, 128 for password */ char hash[NETPLAY_PASS_HASH_LEN+1]; /* + NULL terminator */ + struct netplay_connection *connection; net_driver_state_t *net_st = &networking_driver_st; - netplay_t *netplay = net_st->handshake_password; - struct netplay_connection *connection = &netplay->connections[0]; + netplay_t *netplay = net_st->data; + + if (!netplay) + return; + + connection = &netplay->connections[0]; snprintf(password, sizeof(password), "%08lX", (unsigned long)connection->salt); if (!string_is_empty(line)) @@ -918,6 +915,33 @@ static void handshake_password(void *ignore, const char *line) } #endif +/** + * netplay_handshake_nick + * + * Send a NICK command. + */ +static bool netplay_handshake_nick(netplay_t *netplay, + struct netplay_connection *connection) +{ + struct nick_buf_s nick_buf = {0}; + + /* Send our nick */ + nick_buf.cmd[0] = htonl(NETPLAY_CMD_NICK); + nick_buf.cmd[1] = htonl(sizeof(nick_buf.nick)); + strlcpy(nick_buf.nick, netplay->nick, sizeof(nick_buf.nick)); + + /* Second ping */ + connection->ping_timer = cpu_features_get_time_usec(); + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + &nick_buf, sizeof(nick_buf)) || + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, + false)) + return false; + + return true; +} + /** * netplay_handshake_init * @@ -928,16 +952,16 @@ bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { ssize_t recvd; - struct nick_buf_s nick_buf; uint32_t header[6]; uint32_t netplay_magic = 0; + uint32_t hi_protocol = 0; + uint32_t lo_protocol = 0; uint32_t local_pmagic = 0; uint32_t remote_pmagic = 0; - uint32_t remote_version = 0; uint32_t compression = 0; + int32_t ping = 0; struct compression_transcoder *ctrans = NULL; const char *dmsg = NULL; - net_driver_state_t *net_st = &networking_driver_st; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; @@ -959,6 +983,9 @@ bool netplay_handshake_init(netplay_t *netplay, /* Poking the server for information? Just disconnect */ if (netplay_magic == POKE_MAGIC) { + /* Send it our highest available protocol. */ + netplay_handshake_init_send(netplay, connection, + HIGH_NETPLAY_PROTOCOL_VERSION); socket_close(connection->fd); connection->active = false; netplay_deinit_socket_buffer(&connection->send_packet_buffer); @@ -968,6 +995,9 @@ bool netplay_handshake_init(netplay_t *netplay, } else { + /* Only the client is able to estimate latency at this point. */ + SET_PING() + if (netplay_magic == FULL_MAGIC) { dmsg = msg_hash_to_str(MSG_NETPLAY_HOST_FULL); @@ -975,7 +1005,6 @@ bool netplay_handshake_init(netplay_t *netplay, } } - if (netplay_magic != NETPLAY_MAGIC) { dmsg = msg_hash_to_str(MSG_NETPLAY_NOT_RETROARCH); @@ -988,15 +1017,47 @@ bool netplay_handshake_init(netplay_t *netplay, netplay->is_server ? MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT : MSG_FAILED_TO_RECEIVE_HEADER_FROM_HOST); - goto error; } - remote_version = ntohl(header[4]); - if (remote_version < NETPLAY_PROTOCOL_VERSION) + /* HACK ALERT!!! + * We need to do this in order to maintain full backwards compatibility. + * If client sent a non zero salt, assume it's the highest supported protocol. */ + if (netplay->is_server) { - dmsg = msg_hash_to_str(MSG_NETPLAY_OUT_OF_DATE); - goto error; + hi_protocol = ntohl(header[3]); + lo_protocol = ntohl(header[4]); + + if (!hi_protocol) + /* Older clients don't send a high protocol. */ + connection->netplay_protocol = lo_protocol; + else if (hi_protocol > HIGH_NETPLAY_PROTOCOL_VERSION) + /* Run at our highest supported protocol. */ + connection->netplay_protocol = HIGH_NETPLAY_PROTOCOL_VERSION; + else + /* Otherwise run at the client's highest supported protocol. */ + connection->netplay_protocol = hi_protocol; + + if (connection->netplay_protocol < LOW_NETPLAY_PROTOCOL_VERSION) + { + /* Send it so that a proper notification can be shown there. */ + netplay_handshake_init_send(netplay, connection, + LOW_NETPLAY_PROTOCOL_VERSION); + dmsg = msg_hash_to_str(MSG_NETPLAY_OUT_OF_DATE); + goto error; + } + } + else + { + /* Clients only see one protocol. */ + connection->netplay_protocol = ntohl(header[4]); + + if (connection->netplay_protocol < LOW_NETPLAY_PROTOCOL_VERSION || + connection->netplay_protocol > HIGH_NETPLAY_PROTOCOL_VERSION) + { + dmsg = msg_hash_to_str(MSG_NETPLAY_OUT_OF_DATE); + goto error; + } } /* We only care about platform magic if our core is quirky */ @@ -1020,7 +1081,7 @@ bool netplay_handshake_init(netplay_t *netplay, { /* We allow the connection but warn that this could cause issues. */ dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_VERSIONS); - RARCH_WARN("%s\n", dmsg); + RARCH_WARN("[Netplay] %s\n", dmsg); if (extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); @@ -1066,35 +1127,32 @@ bool netplay_handshake_init(netplay_t *netplay, if (!ctrans->compression_stream || !ctrans->decompression_stream) return false; - /* If a password is demanded, ask for it */ - if (!netplay->is_server && (connection->salt = ntohl(header[3]))) + /* Finally send our header, if server. */ + if (netplay->is_server) { -#ifdef HAVE_MENU - menu_input_ctx_line_t line; - retroarch_menu_running(); -#endif - - net_st->handshake_password = netplay; - -#ifdef HAVE_MENU - memset(&line, 0, sizeof(line)); - line.label = msg_hash_to_str(MSG_NETPLAY_ENTER_PASSWORD); - line.label_setting = "no_setting"; - line.cb = handshake_password; - if (!menu_input_dialog_start(&line)) + if (!netplay_handshake_init_send(netplay, connection, + connection->netplay_protocol)) return false; -#endif } + /* If a password is demanded, ask for it */ + else + { + if ((connection->salt = ntohl(header[3]))) + { +#ifdef HAVE_MENU + menu_input_ctx_line_t line = {0}; + retroarch_menu_running(); + line.label = msg_hash_to_str(MSG_NETPLAY_ENTER_PASSWORD); + line.label_setting = "no_setting"; + line.cb = handshake_password; + if (!menu_input_dialog_start(&line)) + return false; +#endif + } - /* Send our nick */ - nick_buf.cmd[0] = htonl(NETPLAY_CMD_NICK); - nick_buf.cmd[1] = htonl(sizeof(nick_buf.nick)); - memset(nick_buf.nick, 0, sizeof(nick_buf.nick)); - strlcpy(nick_buf.nick, netplay->nick, sizeof(nick_buf.nick)); - if (!netplay_send(&connection->send_packet_buffer, connection->fd, &nick_buf, - sizeof(nick_buf)) || - !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) - return false; + if (!netplay_handshake_nick(netplay, connection)) + return false; + } /* Move on to the next mode */ connection->mode = NETPLAY_CONNECTION_PRE_NICK; @@ -1105,7 +1163,7 @@ bool netplay_handshake_init(netplay_t *netplay, error: if (dmsg) { - RARCH_ERR("%s\n", dmsg); + RARCH_ERR("[Netplay] %s\n", dmsg); /* These notifications are useful to the client in figuring out what caused its premature disconnection, but they are quite useless (annoying) to the server. Let them be optional if server. */ @@ -1168,11 +1226,9 @@ static void netplay_handshake_ready(netplay_t *netplay, static bool netplay_handshake_info(netplay_t *netplay, struct netplay_connection *connection) { - struct info_buf_s info_buf; - uint32_t content_crc = 0; + struct info_buf_s info_buf = {0}; struct retro_system_info *system = &runloop_state_get_ptr()->system.info; - memset(&info_buf, 0, sizeof(info_buf)); info_buf.cmd[0] = htonl(NETPLAY_CMD_INFO); info_buf.cmd[1] = htonl(sizeof(info_buf) - 2*sizeof(uint32_t)); @@ -1193,10 +1249,10 @@ static bool netplay_handshake_info(netplay_t *netplay, } /* Get our content CRC */ - content_crc = content_get_crc(); + info_buf.content_crc = htonl(content_get_crc()); - if (content_crc != 0) - info_buf.content_crc = htonl(content_crc); + /* Third ping */ + connection->ping_timer = cpu_features_get_time_usec(); /* Send it off and wait for info back */ if (!netplay_send(&connection->send_packet_buffer, connection->fd, @@ -1205,7 +1261,6 @@ static bool netplay_handshake_info(netplay_t *netplay, false)) return false; - connection->mode = NETPLAY_CONNECTION_PRE_INFO; return true; } @@ -1346,6 +1401,30 @@ static bool netplay_handshake_sync(netplay_t *netplay, autosave_unlock(); #endif + /* Send our settings. */ + if (connection->netplay_protocol >= 6) + { + uint32_t allow_pausing; + int32_t frames[2]; + + allow_pausing = htonl((uint32_t) netplay->allow_pausing); + if (!netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_SETTING_ALLOW_PAUSING, + &allow_pausing, sizeof(allow_pausing))) + return false; + + frames[0] = htonl(netplay->input_latency_frames_min); + frames[1] = htonl(netplay->input_latency_frames_max); + if (!netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES, + frames, sizeof(frames))) + return false; + } + + if (!netplay_send_flush(&connection->send_packet_buffer, + connection->fd, false)) + return false; + /* Now we're ready! */ connection->mode = NETPLAY_CONNECTION_SPECTATING; netplay_handshake_ready(netplay, connection); @@ -1364,9 +1443,10 @@ static bool netplay_handshake_pre_nick(netplay_t *netplay, { struct nick_buf_s nick_buf; ssize_t recvd; + int32_t ping = 0; const char *dmsg = NULL; settings_t *settings = config_get_ptr(); - bool extra_notifications = settings->bools.notification_show_netplay_extra; + bool extra_notifications = settings->bools.notification_show_netplay_extra; RECV(&nick_buf, sizeof(nick_buf)) {} @@ -1379,7 +1459,7 @@ static bool netplay_handshake_pre_nick(netplay_t *netplay, netplay->is_server ? MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT : MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST); - RARCH_ERR("%s\n", dmsg); + RARCH_ERR("[Netplay] %s\n", dmsg); /* Useful to the client in figuring out what caused its premature disconnection, but not as useful to the server. @@ -1390,21 +1470,26 @@ static bool netplay_handshake_pre_nick(netplay_t *netplay, return false; } + SET_PING() + strlcpy(connection->nick, nick_buf.nick, - (sizeof(connection->nick) < sizeof(nick_buf.nick)) ? - sizeof(connection->nick) : sizeof(nick_buf.nick)); + sizeof(connection->nick)); if (netplay->is_server) { + if (!netplay_handshake_nick(netplay, connection)) + return false; + /* There's a password, so just put them in PRE_PASSWORD mode */ - if ( settings->paths.netplay_password[0] || - settings->paths.netplay_spectate_password[0]) + if ( !string_is_empty(settings->paths.netplay_password) || + !string_is_empty(settings->paths.netplay_spectate_password)) connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD; else { - connection->can_play = true; if (!netplay_handshake_info(netplay, connection)) return false; + + connection->can_play = true; connection->mode = NETPLAY_CONNECTION_PRE_INFO; } } @@ -1444,15 +1529,14 @@ static bool netplay_handshake_pre_password(netplay_t *netplay, ntohl(password_buf.cmd[0]) != NETPLAY_CMD_PASSWORD || ntohl(password_buf.cmd[1]) != sizeof(password_buf.password)) { - RARCH_ERR("Failed to receive netplay password.\n"); + RARCH_ERR("[Netplay] Failed to receive netplay password.\n"); return false; } /* Calculate the correct password hash(es) and compare */ - correct = false; snprintf(password, sizeof(password), "%08lX", (unsigned long)connection->salt); - if (settings->paths.netplay_password[0]) + if (!string_is_empty(settings->paths.netplay_password)) { strlcpy(password + 8, settings->paths.netplay_password, sizeof(password)-8); @@ -1465,7 +1549,7 @@ static bool netplay_handshake_pre_password(netplay_t *netplay, connection->can_play = true; } } - if (settings->paths.netplay_spectate_password[0]) + if (!correct && !string_is_empty(settings->paths.netplay_spectate_password)) { strlcpy(password + 8, settings->paths.netplay_spectate_password, sizeof(password)-8); @@ -1479,7 +1563,7 @@ static bool netplay_handshake_pre_password(netplay_t *netplay, /* Just disconnect if it was wrong */ if (!correct) { - RARCH_WARN("A client tried to connect with the wrong password.\n"); + RARCH_WARN("[Netplay] A client tried to connect with the wrong password.\n"); return false; } @@ -1506,30 +1590,37 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, uint32_t cmd_size; ssize_t recvd; uint32_t content_crc = 0; + int32_t ping = 0; const char *dmsg = NULL; struct retro_system_info *system = &runloop_state_get_ptr()->system.info; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; + unsigned max_ping = settings->uints.netplay_max_ping; RECV(&info_buf, sizeof(info_buf.cmd)) { if (!netplay->is_server) { dmsg = msg_hash_to_str(MSG_NETPLAY_INCORRECT_PASSWORD); - RARCH_ERR("%s\n", dmsg); + RARCH_ERR("[Netplay] %s\n", dmsg); runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); return false; } - } if (recvd < 0 || ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO) { - RARCH_ERR("Failed to receive netplay info.\n"); + RARCH_ERR("[Netplay] Failed to receive netplay info.\n"); return false; } + if (netplay->is_server) + { + /* Only the server is able to estimate latency at this point. */ + SET_PING() + } + cmd_size = ntohl(info_buf.cmd[1]); if (cmd_size != sizeof(info_buf) - sizeof(info_buf.cmd)) { @@ -1538,7 +1629,7 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, if (cmd_size != 0) { /* Huh? */ - RARCH_ERR("Invalid NETPLAY_CMD_INFO payload size.\n"); + RARCH_ERR("[Netplay] Invalid NETPLAY_CMD_INFO payload size.\n"); return false; } @@ -1562,7 +1653,7 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, { /* Wrong core! */ dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_CORES); - RARCH_ERR("%s\n", dmsg); + RARCH_ERR("[Netplay] %s\n", dmsg); /* Useful to the client in figuring out what caused its premature disconnection, but not as useful to the server. @@ -1578,7 +1669,7 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, system->library_version, sizeof(info_buf.core_version))) { dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_CORE_VERSIONS); - RARCH_WARN("%s\n", dmsg); + RARCH_WARN("[Netplay] %s\n", dmsg); if (extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); @@ -1593,7 +1684,7 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, if (ntohl(info_buf.content_crc) != content_crc) { dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); - RARCH_WARN("%s\n", dmsg); + RARCH_WARN("[Netplay] %s\n", dmsg); if (extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, @@ -1604,6 +1695,9 @@ static bool netplay_handshake_pre_info(netplay_t *netplay, /* Now switch to the right mode */ if (netplay->is_server) { + if (max_ping && (unsigned)connection->ping > max_ping) + return false; + if (!netplay_handshake_sync(netplay, connection)) return false; } @@ -1642,15 +1736,20 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, if (netplay->is_server) return false; - RECV(cmd, sizeof(cmd)) {} + RECV(cmd, sizeof(cmd)) + { + const char *dmsg = msg_hash_to_str(MSG_PING_TOO_HIGH); + RARCH_ERR("[Netplay] %s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + return false; + } /* Only expecting a sync command */ - if (recvd < 0 || - ntohl(cmd[0]) != NETPLAY_CMD_SYNC || + if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || ntohl(cmd[1]) < (2+2*MAX_INPUT_DEVICES)*sizeof(uint32_t) + (MAX_INPUT_DEVICES)*sizeof(uint8_t) + NETPLAY_NICK_LEN) { - RARCH_ERR("Failed to receive netplay sync.\n"); + RARCH_ERR("[Netplay] Failed to receive netplay sync.\n"); return false; } @@ -1773,7 +1872,7 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, { RECV(mem_info.data, local_sram_size) { - RARCH_ERR("%s\n", + RARCH_ERR("[Netplay] %s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); #ifdef HAVE_THREADS autosave_unlock(); @@ -1790,7 +1889,7 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, { RECV(&quickbuf, (remote_sram_size > sizeof(uint32_t)) ? sizeof(uint32_t) : remote_sram_size) { - RARCH_ERR("%s\n", + RARCH_ERR("[Netplay] %s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); #ifdef HAVE_THREADS autosave_unlock(); @@ -2178,7 +2277,7 @@ bool netplay_send( /* Can only be that this is simply too big * for our buffer, in which case we just * need to do a blocking send */ - if (!socket_send_all_blocking(sockfd, buf, len, false)) + if (!socket_send_all_blocking(sockfd, buf, len, true)) return false; return true; } @@ -2429,14 +2528,14 @@ static bool netplay_full(netplay_t *netplay, int sockfd) too for backwards compatibility */ memset(header, 0, sizeof(header)); header[0] = htonl(FULL_MAGIC); - header[4] = htonl(NETPLAY_PROTOCOL_VERSION); + header[4] = htonl(HIGH_NETPLAY_PROTOCOL_VERSION); /* The kernel might close the socket before sending our data. This is fine; the header is just a warning for the client. */ socket_send_all_nonblocking(sockfd, header, - sizeof(header), false); + sizeof(header), true); return true; } @@ -3013,7 +3112,7 @@ static void netplay_handle_frame_hash(netplay_t *netplay, if (netplay->check_frames < 0) { /* Just report */ - RARCH_ERR("Netplay CRCs mismatch!\n"); + RARCH_ERR("[Netplay] Netplay CRCs mismatch!\n"); } else netplay_cmd_request_savestate(netplay); @@ -3024,13 +3123,274 @@ static void netplay_handle_frame_hash(netplay_t *netplay, } } +/** + * handle_connection + * @netplay : pointer to netplay object + * @error : value of pointer is set to true if a critical error occurs + * + * Accepts a new client connection. + * + * Returns: fd of a new connection or -1 if there was no new connection. + */ +static int handle_connection(netplay_t *netplay, bool *error) +{ + fd_set fds; + int result; + struct timeval tv = {0}; + int new_fd = -1; + + FD_ZERO(&fds); + FD_SET(netplay->listen_fd, &fds); + + /* Check for a connection */ + result = socket_select(netplay->listen_fd + 1, &fds, NULL, NULL, &tv); + + if (result < 0) + goto critical_failure; + + if (result) + { + struct sockaddr_storage their_addr; + socklen_t addr_size = sizeof(their_addr); + + new_fd = accept(netplay->listen_fd, + (struct sockaddr*) &their_addr, &addr_size); + + if (new_fd >= 0) + { + /* Set the socket nonblocking */ + if (!socket_nonblock(new_fd)) + goto critical_failure; + + SET_TCP_NODELAY(new_fd) + SET_FD_CLOEXEC(new_fd) + } + else + goto critical_failure; + } + + return new_fd; + +critical_failure: + if (new_fd >= 0) + socket_close(new_fd); + + *error = true; + + return -1; +} + +static bool netplay_tunnel_connect(int fd, const struct addrinfo *addr) +{ + int result; + + SET_TCP_NODELAY(fd) + SET_FD_CLOEXEC(fd) + + if (!socket_nonblock(fd)) + return false; + + result = socket_connect(fd, (void*) addr, false); + if (result && !isagain(result)) +#if !defined(_WIN32) && defined(EINPROGRESS) + return result < 0 && errno == EINPROGRESS; +#else + return false; +#endif + + return true; +} + +/** + * handle_mitm_connection + * @netplay : pointer to netplay object + * @error : value of pointer is set to true if a critical error occurs + * + * Do three things here. + * 1: Check if any pending tunnel connection is ready. + * 2: Check the tunnel server to see if we need to link to a client. + * 3: Reply to ping requests from the tunnel server. + * + * For performance reasons, only create and complete one connection per call. + * + * Returns: fd of a new completed connection or -1 if no connection was completed. + */ +static int handle_mitm_connection(netplay_t *netplay, bool *error) +{ + size_t i; + void* recv_buf; + size_t recv_len; + ssize_t recvd; + int new_fd = -1; + retro_time_t ctime = cpu_features_get_time_usec(); + + /* We want to call select individually in order to handle errors on a per + connection basis. */ + for (i = 0; i < NETPLAY_MITM_MAX_PENDING; i++) + { + int fd = netplay->mitm_pending->fds[i]; + if (fd >= 0) + { + fd_set wfd, efd; + struct timeval tv = {0}; + + FD_ZERO(&wfd); + FD_ZERO(&efd); + FD_SET(fd, &wfd); + FD_SET(fd, &efd); + + if (socket_select(fd + 1, NULL, &wfd, &efd, &tv) < 0 || + FD_ISSET(fd, &efd)) + { + /* Error */ + RARCH_ERR("[Netplay] Tunnel link connection failed.\n"); + } + else if (FD_ISSET(fd, &wfd)) + { + /* Connection is ready. + Send the linking id. */ + mitm_id_t *lid = &netplay->mitm_pending->ids[i]; + if (socket_send_all_nonblocking(fd, lid, sizeof(*lid), true) == sizeof(*lid)) + { + new_fd = fd; + RARCH_LOG("[Netplay] Tunnel link connection completed.\n"); + } + else + { + /* We couldn't send our id in one call. Assume error. */ + socket_close(fd); + RARCH_ERR("[Netplay] Tunnel link connection failed after handshake.\n"); + } + netplay->mitm_pending->fds[i] = -1; + break; + } + else + { + /* Check if the connection timeouted. */ + retro_time_t timeout = netplay->mitm_pending->timeouts[i]; + if (ctime < timeout) + continue; + RARCH_ERR("[Netplay] Tunnel link connection timeout.\n"); + } + + socket_close(fd); + netplay->mitm_pending->fds[i] = -1; + } + } + + recv_buf = (void*) ((size_t) &netplay->mitm_pending->id_buf + + netplay->mitm_pending->id_recvd); + if (netplay->mitm_pending->id_recvd < sizeof(netplay->mitm_pending->id_buf.magic)) + recv_len = sizeof(netplay->mitm_pending->id_buf.magic) - + netplay->mitm_pending->id_recvd; + else + recv_len = sizeof(netplay->mitm_pending->id_buf) - + netplay->mitm_pending->id_recvd; + + recvd = socket_receive_all_nonblocking(netplay->listen_fd, + error, recv_buf, recv_len); + + if (recvd < 0 || (size_t)recvd > recv_len) + { + RARCH_ERR("[Netplay] Tunnel server error.\n"); + goto critical_failure; + } + + netplay->mitm_pending->id_recvd += recvd; + if (netplay->mitm_pending->id_recvd >= sizeof(netplay->mitm_pending->id_buf.magic)) + { + switch (ntohl(netplay->mitm_pending->id_buf.magic)) + { + case MITM_LINK_MAGIC: + { + if (netplay->mitm_pending->id_recvd == sizeof(netplay->mitm_pending->id_buf)) + { + netplay->mitm_pending->id_recvd = 0; + + /* Find a free spot to allocate this connection. */ + for (i = 0; i < NETPLAY_MITM_MAX_PENDING; i++) + if (netplay->mitm_pending->fds[i] < 0) + break; + if (i < NETPLAY_MITM_MAX_PENDING) + { + int fd = socket( + netplay->mitm_pending->addr->ai_family, + netplay->mitm_pending->addr->ai_socktype, + netplay->mitm_pending->addr->ai_protocol + ); + if (fd >= 0) + { + if (netplay_tunnel_connect(fd, netplay->mitm_pending->addr)) + { + netplay->mitm_pending->fds[i] = fd; + memcpy(&netplay->mitm_pending->ids[i], + &netplay->mitm_pending->id_buf, + sizeof(*netplay->mitm_pending->ids)); + /* 30 seconds */ + netplay->mitm_pending->timeouts[i] = ctime + 30000000; + RARCH_LOG("[Netplay] Queued tunnel link connection.\n"); + } + else + { + socket_close(fd); + RARCH_ERR("[Netplay] Failed to connect to tunnel server.\n"); + } + } + else + { + RARCH_ERR("[Netplay] Failed to create socket for tunnel link connection.\n"); + } + } + else + { + RARCH_WARN("[Netplay] Cannot create more tunnel link connections.\n"); + } + } + break; + } + case MITM_PING_MAGIC: + { + /* Tunnel server requested for us to reply to a ping request. */ + void *ping = &netplay->mitm_pending->id_buf.magic; + size_t len = sizeof(netplay->mitm_pending->id_buf.magic); + + netplay->mitm_pending->id_recvd = 0; + + if (socket_send_all_nonblocking(netplay->listen_fd, + ping, len, true) != len) + { + /* We couldn't send our ping reply in one call. Assume error. */ + RARCH_ERR("[Netplay] Tunnel ping reply failed.\n"); + goto critical_failure; + } + + break; + } + default: + RARCH_ERR("[Netplay] Received unknown magic from tunnel server.\n"); + goto critical_failure; + } + } + + return new_fd; + +critical_failure: + if (new_fd >= 0) + socket_close(new_fd); + + *error = true; + + return -1; +} + /** * netplay_sync_pre_frame * @netplay : pointer to netplay object + * @disconnect : disconnect netplay * * Pre-frame for Netplay synchronization. */ -bool netplay_sync_pre_frame(netplay_t *netplay) +bool netplay_sync_pre_frame(netplay_t *netplay, bool *disconnect) { retro_ctx_serialize_info_t serial_info; @@ -3089,49 +3449,22 @@ bool netplay_sync_pre_frame(netplay_t *netplay) if (netplay->is_server) { - fd_set fds; - struct timeval tmp_tv = {0}; - int new_fd; - struct sockaddr_storage their_addr; - socklen_t addr_size; - struct netplay_connection *connection; - size_t connection_num; + int new_fd = -1; + bool server_error = false; - /* Check for a connection */ - FD_ZERO(&fds); - FD_SET(netplay->listen_fd, &fds); - if (socket_select(netplay->listen_fd + 1, - &fds, NULL, NULL, &tmp_tv) > 0 && - FD_ISSET(netplay->listen_fd, &fds)) + if (netplay->mitm_pending) + new_fd = handle_mitm_connection(netplay, &server_error); + else + new_fd = handle_connection(netplay, &server_error); + + if (server_error) { - addr_size = sizeof(their_addr); - new_fd = accept(netplay->listen_fd, - (struct sockaddr*)&their_addr, &addr_size); - - if (new_fd < 0) - goto process; - - /* Set the socket nonblocking */ - if (!socket_nonblock(new_fd)) - { - /* Catastrophe! */ - socket_close(new_fd); - goto process; - } - -#if defined(IPPROTO_TCP) && defined(TCP_NODELAY) - { - int on = 1; - if (setsockopt(new_fd, IPPROTO_TCP, TCP_NODELAY, (const char*) &on, sizeof(on)) < 0) - RARCH_WARN("Could not set netplay TCP socket to nodelay. Expect jitter.\n"); - } -#endif - -#if defined(F_SETFD) && defined(FD_CLOEXEC) - /* Don't let any inherited processes keep open our port */ - if (fcntl(new_fd, F_SETFD, FD_CLOEXEC) < 0) - RARCH_WARN("Cannot set Netplay port to close-on-exec. It may fail to reopen if the client disconnects.\n"); -#endif + *disconnect = true; + } + else if (new_fd >= 0) + { + struct netplay_connection *connection; + size_t connection_num; if (netplay_full(netplay, new_fd)) { @@ -3158,14 +3491,12 @@ bool netplay_sync_pre_frame(netplay_t *netplay) goto process; } netplay->connections_size = 1; - } else { size_t new_connections_size = netplay->connections_size * 2; struct netplay_connection *new_connections = (struct netplay_connection*) - realloc(netplay->connections, new_connections_size*sizeof(struct netplay_connection)); @@ -3179,7 +3510,6 @@ bool netplay_sync_pre_frame(netplay_t *netplay) netplay->connections_size * sizeof(struct netplay_connection)); netplay->connections = new_connections; netplay->connections_size = new_connections_size; - } } connection = &netplay->connections[connection_num]; @@ -3195,15 +3525,11 @@ bool netplay_sync_pre_frame(netplay_t *netplay) !netplay_init_socket_buffer(&connection->recv_packet_buffer, netplay->packet_buffer_size)) { - if (connection->send_packet_buffer.data) - netplay_deinit_socket_buffer(&connection->send_packet_buffer); + netplay_deinit_socket_buffer(&connection->send_packet_buffer); + netplay_deinit_socket_buffer(&connection->recv_packet_buffer); connection->active = false; socket_close(new_fd); - goto process; } - - netplay_handshake_init_send(netplay, connection); - } } @@ -3345,7 +3671,7 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) if (!core_unserialize(&serial_info)) { - RARCH_ERR("Netplay savestate loading failed: Prepare for desync!\n"); + RARCH_ERR("[Netplay] Netplay savestate loading failed: Prepare for desync!\n"); } while (netplay->replay_frame_count < netplay->run_frame_count) @@ -3549,7 +3875,7 @@ static void print_state(netplay_t *netplay) } msg[sizeof(msg)-1] = '\0'; - RARCH_LOG("[netplay] %s\n", msg); + RARCH_LOG("[Netplay] %s\n", msg); #undef APPEND #undef M @@ -3606,7 +3932,7 @@ void netplay_hangup(netplay_t *netplay, /* Report this disconnection */ if (netplay->is_server) { - if (connection->nick[0]) + if (!string_is_empty(connection->nick)) { snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_NETPLAY_SERVER_NAMED_HANGUP), connection->nick); @@ -3762,7 +4088,7 @@ static bool send_input_frame(netplay_t *netplay, struct delta_frame *dframe, buffer[1] = htonl((bufused-2) * sizeof(uint32_t)); #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Sending input for client %u\n", (unsigned) client_num); + RARCH_LOG("[Netplay] Sending input for client %u\n", (unsigned) client_num); print_state(netplay); #endif @@ -4056,7 +4382,8 @@ bool netplay_cmd_mode(netplay_t *netplay, */ static void announce_play_spectate(netplay_t *netplay, const char *nick, - enum rarch_netplay_connection_mode mode, uint32_t devices) + enum rarch_netplay_connection_mode mode, uint32_t devices, + int32_t ping) { char msg[512]; const char *dmsg = NULL; @@ -4085,6 +4412,7 @@ static void announce_play_spectate(netplay_t *netplay, case NETPLAY_CONNECTION_SLAVE: { char device_str[256]; + char ping_str[32]; uint32_t device; uint32_t one_device = (uint32_t) -1; char *pdevice_str = NULL; @@ -4134,7 +4462,7 @@ static void announce_play_spectate(netplay_t *netplay, sizeof(device_str) - (size_t) (pdevice_str - device_str), "%u, ", - (unsigned) (device+1)); + (unsigned) (device+1)); #ifdef HAVE_CHEEVOS rcheevos_validate_netplay(device + 1); @@ -4161,6 +4489,12 @@ static void announce_play_spectate(netplay_t *netplay, device_str); } + if (ping >= 0) + { + snprintf(ping_str, sizeof(ping_str), " (ping: %i ms)", ping); + strlcat(msg, ping_str, sizeof(msg)); + } + dmsg = msg; break; } @@ -4243,7 +4577,7 @@ static void handle_play_spectate(netplay_t *netplay, /* Announce it */ announce_play_spectate(netplay, connection ? connection->nick : NULL, - NETPLAY_CONNECTION_SPECTATING, 0); + NETPLAY_CONNECTION_SPECTATING, 0, -1); break; } @@ -4345,30 +4679,29 @@ static void handle_play_spectate(netplay_t *netplay, if (!netplay->device_clients[device]) break; } - if (device >= MAX_INPUT_DEVICES && - netplay->config_devices[1] == RETRO_DEVICE_NONE && share_mode) + if (device >= MAX_INPUT_DEVICES) { - /* No device free and no device specifically asked for, but only - * one device, so share it */ - if (netplay->device_share_modes[0]) + if (netplay->config_devices[1] == RETRO_DEVICE_NONE && + netplay->device_share_modes[0] && share_mode) { + /* No device free and no device specifically asked for, but only + * one device, so share it */ device = 0; - share_mode = netplay->device_share_modes[0]; + share_mode = netplay->device_share_modes[0]; + } + else + { + /* No slots free! */ + payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS); + /* FIXME: Message for the server */ + if (connection) + netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t)); break; } } - if (device >= MAX_INPUT_DEVICES) - { - /* No slots free! */ - payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS); - /* FIXME: Message for the server */ - if (connection) - netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t)); - break; - } devices = 1<device_share_modes[device] = share_mode; - } payload[2] = htonl(devices); @@ -4418,12 +4751,241 @@ static void handle_play_spectate(netplay_t *netplay, /* Announce it */ announce_play_spectate(netplay, connection ? connection->nick : NULL, - NETPLAY_CONNECTION_PLAYING, devices); + NETPLAY_CONNECTION_PLAYING, devices, + connection ? connection->ping : -1); break; } } } +static bool chat_check(netplay_t *netplay) +{ + if (!netplay) + return false; + + /* Do nothing if we don't have a nickname. */ + if (string_is_empty(netplay->nick)) + return false; + + /* Do nothing if we are not playing. */ + if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING && + netplay->self_mode != NETPLAY_CONNECTION_SLAVE) + return false; + + /* If we are the server, + check if someone is able to read us. */ + if (netplay->is_server) + { + size_t i; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *conn = &netplay->connections[i]; + if (conn->active && conn->netplay_protocol >= 6 && + (conn->mode == NETPLAY_CONNECTION_PLAYING || + conn->mode == NETPLAY_CONNECTION_SLAVE)) + return true; + } + } + /* Otherwise, just check whether our connection is active + and the server is running protocol 6+. */ + else + { + if (netplay->connections[0].active && + netplay->connections[0].netplay_protocol >= 6) + return true; + } + + return false; +} + +static void relay_chat(netplay_t *netplay, + const char *nick, const char *msg) +{ + size_t i; + size_t msg_len; + char data[NETPLAY_NICK_LEN + NETPLAY_CHAT_MAX_SIZE - 1]; + + msg_len = strlen(msg); + + memcpy(data, nick, NETPLAY_NICK_LEN); + memcpy(data + NETPLAY_NICK_LEN, msg, msg_len); + + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *conn = &netplay->connections[i]; + /* Only playing clients can receive chat. + Protocol 6+ is required. */ + if (conn->active && conn->netplay_protocol >= 6 && + (conn->mode == NETPLAY_CONNECTION_PLAYING || + conn->mode == NETPLAY_CONNECTION_SLAVE)) + netplay_send_raw_cmd(netplay, conn, + NETPLAY_CMD_PLAYER_CHAT, data, NETPLAY_NICK_LEN + msg_len); + } + /* We don't flush. Chat is not time essential. */ +} + +static void show_chat(const char *nick, const char *msg) +{ + char formatted_chat[NETPLAY_CHAT_MAX_SIZE]; + net_driver_state_t *net_st = &networking_driver_st; + + /* Truncate the message if necessary. */ + snprintf(formatted_chat, sizeof(formatted_chat), + "%s: %s", nick, msg); + + RARCH_LOG("[Netplay] %s\n", formatted_chat); + +#ifdef HAVE_GFX_WIDGETS + if (gfx_widgets_ready()) + { + struct netplay_chat_data *data; + + /* Do we have a free slot for this message? + If not, get rid of the oldest message to make room for it. */ + if (!net_st->chat.message_slots) + { + int i; + + for (i = ARRAY_SIZE(net_st->chat.messages) - 2; i >= 0; i--) + { + memcpy(&net_st->chat.messages[i+1].data, + &net_st->chat.messages[i].data, + sizeof(*data)); + } + data = &net_st->chat.messages[0].data; + } + else + { + data = &net_st->chat.messages[--net_st->chat.message_slots].data; + } + + strlcpy(data->nick, nick, sizeof(data->nick)); + strlcpy(data->msg, msg, sizeof(data->msg)); + data->frames = NETPLAY_CHAT_FRAME_TIME; + } + else +#endif + runloop_msg_queue_push(formatted_chat, + 1, NETPLAY_CHAT_FRAME_TIME, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); +} + +static void send_chat(void *userdata, const char *line) +{ + char msg[NETPLAY_CHAT_MAX_SIZE]; + net_driver_state_t *net_st = &networking_driver_st; + netplay_t *netplay = net_st->data; + + /* We perform the same checks, + just in case something has changed. */ + if (!string_is_empty(line) && chat_check(netplay)) + { + /* Truncate line to NETPLAY_CHAT_MAX_SIZE. */ + strlcpy(msg, line, sizeof(msg)); + + /* For servers, we need to relay it ourselves. */ + if (netplay->is_server) + { + relay_chat(netplay, netplay->nick, msg); + show_chat(netplay->nick, msg); + } + /* For clients, we just send it to the server. */ + else + { + netplay_send_raw_cmd(netplay, &netplay->connections[0], + NETPLAY_CMD_PLAYER_CHAT, msg, strlen(msg)); + /* We don't flush. Chat is not time essential. */ + } + } + + menu_input_dialog_end(); + retroarch_menu_running_finished(false); +} + +void netplay_input_chat(netplay_t *netplay) +{ +#ifdef HAVE_MENU + if (chat_check(netplay)) + { + menu_input_ctx_line_t chat_input = {0}; + + retroarch_menu_running(); + + chat_input.label = msg_hash_to_str(MSG_NETPLAY_ENTER_CHAT); + chat_input.label_setting = "no_setting"; + chat_input.cb = send_chat; + + menu_input_dialog_start(&chat_input); + } +#endif +} + +/** + * handle_chat + * + * Handle a received chat message + */ +static bool handle_chat(netplay_t *netplay, + struct netplay_connection *connection, + const char *nick, const char *msg) +{ + if (!connection->active || connection->netplay_protocol < 6 || + string_is_empty(nick) || string_is_empty(msg)) + return false; + + /* Client sent a chat message; + Relay it to the other clients, + including the one who sent it. */ + if (netplay->is_server) + { + /* Only playing clients can send chat. */ + if (connection->mode != NETPLAY_CONNECTION_PLAYING && + connection->mode != NETPLAY_CONNECTION_SLAVE) + return false; + + relay_chat(netplay, nick, msg); + } + + /* If we still got a message even though we are not playing, + ignore it! */ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || + netplay->self_mode == NETPLAY_CONNECTION_SLAVE) + show_chat(nick, msg); + + return true; +} + +static void request_ping(netplay_t *netplay, + struct netplay_connection *connection) +{ + /* Only protocol 6+ supports the ping command. */ + if (!connection->active || connection->netplay_protocol < 6) + return; + + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED) + { + connection->ping_timer = cpu_features_get_time_usec(); + + netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_PING_REQUEST, NULL, 0); + /* We need to get this sent asap. */ + netplay_send_flush(&connection->send_packet_buffer, + connection->fd, false); + + connection->ping_requested = true; + } +} + +static void answer_ping(netplay_t *netplay, + struct netplay_connection *connection) +{ + netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_PING_RESPONSE, NULL, 0); + /* We need to get this sent asap. */ + netplay_send_flush(&connection->send_packet_buffer, + connection->fd, false); +} + #undef RECV #define RECV(buf, sz) \ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \ @@ -4453,7 +5015,7 @@ static bool netplay_get_cmd(netplay_t *netplay, cmd_size = ntohl(cmd_size); #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Received netplay command %X (%u) from %u\n", cmd, cmd_size, + RARCH_LOG("[Netplay] Received netplay command %X (%u) from %u\n", cmd, cmd_size, (unsigned) (connection - netplay->connections)); #endif @@ -4476,7 +5038,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (cmd_size < 2*sizeof(uint32_t)) { - RARCH_ERR("NETPLAY_CMD_INPUT too short, no frame/client number."); + RARCH_ERR("[Netplay] NETPLAY_CMD_INPUT too short, no frame/client number.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4494,7 +5056,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (connection->mode != NETPLAY_CONNECTION_PLAYING && connection->mode != NETPLAY_CONNECTION_SLAVE) { - RARCH_ERR("Netplay input from non-participating player.\n"); + RARCH_ERR("[Netplay] Netplay input from non-participating player.\n"); return netplay_cmd_nak(netplay, connection); } client_num = (uint32_t)(connection - netplay->connections + 1); @@ -4502,13 +5064,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (client_num >= MAX_CLIENTS) { - RARCH_ERR("NETPLAY_CMD_INPUT received data for an unsupported client.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_INPUT received data for an unsupported client.\n"); return netplay_cmd_nak(netplay, connection); } if (!(netplay->connected_players & (1< netplay->read_frame_count[client_num]) { /* Out of order = out of luck */ - RARCH_ERR("Netplay input out of order.\n"); + RARCH_ERR("[Netplay] Netplay input out of order.\n"); return netplay_cmd_nak(netplay, connection); } } @@ -4601,7 +5163,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Received input from %u\n", client_num); + RARCH_LOG("[Netplay] Received input from %u\n", client_num); print_state(netplay); #endif break; @@ -4613,13 +5175,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->is_server) { - RARCH_ERR("NETPLAY_CMD_NOINPUT from a client.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_NOINPUT from a client.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(frame)) { - RARCH_ERR("NETPLAY_CMD_NOINPUT received" + RARCH_ERR("[Netplay] NETPLAY_CMD_NOINPUT received" " an unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4635,14 +5197,14 @@ static bool netplay_get_cmd(netplay_t *netplay, if (frame != netplay->server_frame_count) { - RARCH_ERR("NETPLAY_CMD_NOINPUT for invalid frame.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_NOINPUT for invalid frame.\n"); return netplay_cmd_nak(netplay, connection); } netplay->server_ptr = NEXT_PTR(netplay->server_ptr); netplay->server_frame_count++; #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Received server noinput\n"); + RARCH_LOG("[Netplay] Received server noinput\n"); print_state(netplay); #endif break; @@ -4654,13 +5216,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay->is_server) { - RARCH_ERR("NETPLAY_CMD_SPECTATE from a server.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_SPECTATE from a server.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != 0) { - RARCH_ERR("Unexpected payload in NETPLAY_CMD_SPECTATE.\n"); + RARCH_ERR("[Netplay] Unexpected payload in NETPLAY_CMD_SPECTATE.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4668,7 +5230,7 @@ static bool netplay_get_cmd(netplay_t *netplay, && connection->mode != NETPLAY_CONNECTION_SLAVE) { /* They were confused */ - RARCH_ERR("NETPLAY_CMD_SPECTATE from client not currently playing.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_SPECTATE from client not currently playing.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4685,13 +5247,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay->is_server) { - RARCH_ERR("NETPLAY_CMD_PLAY from a server.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_PLAY from a server.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(payload)) { - RARCH_ERR("Incorrect NETPLAY_CMD_PLAY payload size.\n"); + RARCH_ERR("[Netplay] Incorrect NETPLAY_CMD_PLAY payload size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4721,7 +5283,7 @@ static bool netplay_get_cmd(netplay_t *netplay, connection->mode == NETPLAY_CONNECTION_PLAYING || connection->mode == NETPLAY_CONNECTION_SLAVE) { - RARCH_ERR("NETPLAY_CMD_PLAY from client already playing.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_PLAY from client already playing.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4752,13 +5314,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->is_server) { - RARCH_ERR("NETPLAY_CMD_MODE from client.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_MODE from client.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(payload)) { - RARCH_ERR("Invalid payload size for NETPLAY_CMD_MODE.\n"); + RARCH_ERR("[Netplay] Invalid payload size for NETPLAY_CMD_MODE.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4775,7 +5337,7 @@ static bool netplay_get_cmd(netplay_t *netplay, client_num = mode & 0xFFFF; if (client_num >= MAX_CLIENTS) { - RARCH_ERR("Received NETPLAY_CMD_MODE for a higher player number than we support.\n"); + RARCH_ERR("[Netplay] Received NETPLAY_CMD_MODE for a higher player number than we support.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4790,14 +5352,14 @@ static bool netplay_get_cmd(netplay_t *netplay, { if (frame != netplay->server_frame_count) { - RARCH_ERR("Received mode change out of order.\n"); + RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); } /* Hooray, I get to play now! */ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { - RARCH_ERR("Received player mode change even though I'm already a player.\n"); + RARCH_ERR("[Netplay] Received player mode change even though I'm already a player.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4861,7 +5423,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Make sure it's ready */ if (!netplay_delta_frame_ready(netplay, dframe, frame_count)) { - RARCH_ERR("Received mode change but delta frame isn't ready!\n"); + RARCH_ERR("[Netplay] Received mode change but delta frame isn't ready!\n"); return netplay_cmd_nak(netplay, connection); } } @@ -4879,10 +5441,11 @@ static bool netplay_get_cmd(netplay_t *netplay, } /* Announce it */ - announce_play_spectate(netplay, NULL, NETPLAY_CONNECTION_PLAYING, devices); + announce_play_spectate(netplay, NULL, NETPLAY_CONNECTION_PLAYING, devices, + connection->ping); #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Received mode change self->%X\n", devices); + RARCH_LOG("[Netplay] Received mode change self->%X\n", devices); print_state(netplay); #endif @@ -4892,24 +5455,14 @@ static bool netplay_get_cmd(netplay_t *netplay, /* I'm no longer playing, but I should already know this */ if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING) { - RARCH_ERR("Received mode change to spectator unprompted.\n"); + RARCH_ERR("[Netplay] Received mode change to spectator unprompted.\n"); return netplay_cmd_nak(netplay, connection); } - /* Unmark ourself, in case we were in slave mode */ - netplay->connected_players &= ~(1<client_devices[client_num] = 0; - for (device = 0; device < MAX_INPUT_DEVICES; device++) - netplay->device_clients[device] &= ~(1<spectating\n"); + RARCH_LOG("[Netplay] Received mode change self->spectating\n"); print_state(netplay); #endif - } } @@ -4920,7 +5473,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { if (frame != netplay->server_frame_count) { - RARCH_ERR("Received mode change out of order.\n"); + RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); } @@ -4934,10 +5487,10 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->read_frame_count[client_num] = netplay->server_frame_count; /* Announce it */ - announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices); + announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices, -1); #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Received mode change %u->%u\n", client_num, devices); + RARCH_LOG("[Netplay] Received mode change %u->%u\n", client_num, devices); print_state(netplay); #endif @@ -4950,10 +5503,10 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->device_clients[device] &= ~(1<spectator\n", client_num); + RARCH_LOG("[Netplay] Received mode change %u->spectator\n", client_num); print_state(netplay); #endif @@ -4974,13 +5527,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->is_server) { - RARCH_ERR("NETPLAY_CMD_MODE_REFUSED from client.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_MODE_REFUSED from client.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(reason)) { - RARCH_ERR("Received invalid payload size for NETPLAY_CMD_MODE_REFUSED.\n"); + RARCH_ERR("[Netplay] Received invalid payload size for NETPLAY_CMD_MODE_REFUSED.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5031,7 +5584,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (cmd_size != sizeof(buffer)) { - RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_CRC received unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5120,7 +5673,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (connection->mode != NETPLAY_CONNECTION_PLAYING && connection->mode != NETPLAY_CONNECTION_SLAVE) { - RARCH_ERR("Netplay state load from a spectator.\n"); + RARCH_ERR("[Netplay] Netplay state load from a spectator.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5128,7 +5681,7 @@ static bool netplay_get_cmd(netplay_t *netplay, * two-player situation */ if (netplay->is_server && netplay->connections_size > 1) { - RARCH_ERR("Netplay state load from a client with other clients connected disallowed.\n"); + RARCH_ERR("[Netplay] Netplay state load from a client with other clients connected disallowed.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5148,7 +5701,7 @@ static bool netplay_get_cmd(netplay_t *netplay, (cmd_size < 2*sizeof(uint32_t) || cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t))) || (cmd == NETPLAY_CMD_RESET && cmd_size != sizeof(frame))) { - RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); + RARCH_ERR("[Netplay] CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5170,7 +5723,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (frame != load_frame_count) { - RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); + RARCH_ERR("[Netplay] CMD_LOAD_SAVESTATE loading a state out of order!\n"); return netplay_cmd_nak(netplay, connection); } @@ -5190,7 +5743,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (isize != netplay->state_size) { - RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); + RARCH_ERR("[Netplay] CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5261,7 +5814,7 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->other_frame_count = load_frame_count; #ifdef DEBUG_NETPLAY_STEPS - RARCH_LOG("[netplay] Loading state at %u\n", load_frame_count); + RARCH_LOG("[Netplay] Loading state at %u\n", load_frame_count); print_state(netplay); #endif @@ -5275,7 +5828,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Read in the paused nick */ if (cmd_size != sizeof(nick)) { - RARCH_ERR("NETPLAY_CMD_PAUSE received invalid payload size.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_PAUSE received invalid payload size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5285,10 +5838,6 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->is_server) { - settings_t *settings = config_get_ptr(); - if (!settings->bools.netplay_allow_pausing) - break; - /* We outright ignore pausing from spectators and slaves */ if (connection->mode != NETPLAY_CONNECTION_PLAYING) break; @@ -5311,7 +5860,7 @@ static bool netplay_get_cmd(netplay_t *netplay, connection->paused = true; netplay->remote_paused = true; - RARCH_LOG("%s\n", msg); + RARCH_LOG("[Netplay] %s\n", msg); runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); break; } @@ -5327,13 +5876,13 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->is_server) { /* Only servers can request a stall! */ - RARCH_ERR("Netplay client requested a stall?\n"); + RARCH_ERR("[Netplay] Netplay client requested a stall?\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(frames)) { - RARCH_ERR("NETPLAY_CMD_STALL with incorrect payload size.\n"); + RARCH_ERR("[Netplay] NETPLAY_CMD_STALL with incorrect payload size.\n"); return netplay_cmd_nak(netplay, connection); } @@ -5355,10 +5904,137 @@ static bool netplay_get_cmd(netplay_t *netplay, break; } + case NETPLAY_CMD_PLAYER_CHAT: + { + char nickname[NETPLAY_NICK_LEN]; + char message[NETPLAY_CHAT_MAX_SIZE]; + + /* We do not send the sentinel/null character on chat messages + and we do not allow empty messages. */ + if (netplay->is_server) + { + /* If server, we only receive the message, + without the nickname portion. */ + if (!cmd_size || cmd_size >= sizeof(message)) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_PLAYER_CHAT with incorrect payload size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + strlcpy(nickname, connection->nick, sizeof(nickname)); + } + else + { + /* If client, we receive both the nickname and + the message from the server. */ + if (cmd_size <= sizeof(nickname) || + cmd_size >= sizeof(nickname) + sizeof(message)) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_PLAYER_CHAT with incorrect payload size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(nickname, sizeof(nickname)) + return false; + cmd_size -= sizeof(nickname); + } + + RECV(message, cmd_size) + return false; + message[recvd] = '\0'; + + if (!handle_chat(netplay, connection, + nickname, message)) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_PLAYER_CHAT with invalid message or from an invalid peer.\n"); + return netplay_cmd_nak(netplay, connection); + } + } + break; + + case NETPLAY_CMD_PING_REQUEST: + answer_ping(netplay, connection); + break; + + case NETPLAY_CMD_PING_RESPONSE: + { + /* Only process ping responses if we requested them. */ + if (connection->ping_requested) + { + connection->ping = (int32_t) + ((cpu_features_get_time_usec() - connection->ping_timer) + / 1000); + connection->ping_requested = false; + } + } + break; + + case NETPLAY_CMD_SETTING_ALLOW_PAUSING: + { + uint32_t allow_pausing; + + if (netplay->is_server) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_SETTING_ALLOW_PAUSING from client.\n"); + return netplay_cmd_nak(netplay, connection); + } + + if (cmd_size != sizeof(allow_pausing)) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_SETTING_ALLOW_PAUSING with incorrect payload size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(&allow_pausing, sizeof(allow_pausing)) + return false; + allow_pausing = ntohl(allow_pausing); + + if (allow_pausing != 0 && allow_pausing != 1) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_SETTING_ALLOW_PAUSING with incorrect setting.\n"); + return netplay_cmd_nak(netplay, connection); + } + + netplay->allow_pausing = allow_pausing; + } + break; + + case NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES: + { + int32_t frames[2]; + + if (netplay->is_server) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES from client.\n"); + return netplay_cmd_nak(netplay, connection); + } + + if (cmd_size != sizeof(frames)) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES with incorrect payload size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(frames, sizeof(frames)) + return false; + netplay->input_latency_frames_min = ntohl(frames[0]); + netplay->input_latency_frames_max = ntohl(frames[1]); + } + break; + default: - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); - return netplay_cmd_nak(netplay, connection); + { + unsigned char buf[1024]; + while (cmd_size) + { + RECV(buf, (cmd_size > sizeof(buf)) ? sizeof(buf) : cmd_size) + return false; + cmd_size -= recvd; + } + RARCH_WARN("[Netplay] %s\n", + msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); + } + break; } netplay_recv_flush(&connection->recv_packet_buffer); @@ -5371,9 +6047,9 @@ shrt: /* No more data, reset and try again */ netplay_recv_reset(&connection->recv_packet_buffer); return true; +} #undef RECV -} /** * netplay_poll_net_input @@ -5439,7 +6115,7 @@ int netplay_poll_net_input(netplay_t *netplay, bool block) return -1; RARCH_LOG( - "Network is stalling at frame %u, count %u of %d ...\n", + "[Netplay] Network is stalling at frame %u, count %u of %d ...\n", netplay->run_frame_count, netplay->timeout_cnt, MAX_RETRIES); @@ -5537,6 +6213,9 @@ void netplay_announce_nat_traversal(netplay_t *netplay) if (net_st->nat_traversal_request.status == NAT_TRAVERSAL_STATUS_OPENED) { + netplay->ext_tcp_port = + ntohs(net_st->nat_traversal_request.request.addr.sin_port); + if (!getnameinfo( (struct sockaddr *) &net_st->nat_traversal_request.request.addr, sizeof(net_st->nat_traversal_request.request.addr), @@ -5554,7 +6233,7 @@ void netplay_announce_nat_traversal(netplay_t *netplay) if (dmsg) { - RARCH_LOG("%s\n", dmsg); + RARCH_LOG("[Netplay] %s\n", dmsg); runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } #endif @@ -5579,109 +6258,153 @@ void netplay_deinit_nat_traversal(void) task_push_netplay_nat_close(&net_st->nat_traversal_request); } -static int init_tcp_connection(const struct addrinfo *res, - bool server, - struct sockaddr *other_addr, socklen_t addr_size) +static int init_tcp_connection(netplay_t *netplay, const struct addrinfo *res, + bool is_server, bool is_mitm) { #ifndef HAVE_SOCKET_LEGACY char msg[512]; char host[256], port[6]; #endif const char *dmsg = NULL; - int fd = socket(res->ai_family, - res->ai_socktype, res->ai_protocol); + int fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); if (fd < 0) return -1; -#if defined(IPPROTO_TCP) && defined(TCP_NODELAY) - { - int on = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, - (const char*) &on, sizeof(on)) < 0) - RARCH_WARN("Could not set netplay TCP socket to nodelay. Expect jitter.\n"); - } -#endif + SET_TCP_NODELAY(fd) + SET_FD_CLOEXEC(fd) -#if defined(F_SETFD) && defined(FD_CLOEXEC) - /* Don't let any inherited processes keep open our port */ - if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) - RARCH_WARN("Cannot set Netplay port to close-on-exec. It may fail to reopen if the client disconnects.\n"); -#endif - - if (server) + if (!is_server) { if (!socket_connect(fd, (void*)res, false)) - return fd; + { + /* If we are connecting to a tunnel server, + we must also send our session linking request. */ + if (!netplay->mitm_session_id.magic || socket_send_all_blocking(fd, + &netplay->mitm_session_id, sizeof(netplay->mitm_session_id), true)) + return fd; + } #ifndef HAVE_SOCKET_LEGACY if (!getnameinfo(res->ai_addr, res->ai_addrlen, - host, sizeof(host), port, sizeof(port), - NI_NUMERICHOST | NI_NUMERICSERV)) + host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV)) { snprintf(msg, sizeof(msg), - "Failed to connect to host %s on port %s.", - host, port); + "Failed to connect to host %s on port %s.", + host, port); dmsg = msg; } -#else - dmsg = "Failed to connect to host."; + else #endif + dmsg = "Failed to connect to host."; + } + else if (is_mitm) + { + if (!socket_connect(fd, (void*)res, false)) + { + mitm_id_t new_session = {0}; + mitm_id_t invalid_session = {0}; + + /* To request a new session, + we send the magic with the rest of the ID zeroed. */ + new_session.magic = htonl(MITM_SESSION_MAGIC); + + /* Tunnel server should provide us with our session ID. */ + if (socket_send_all_blocking(fd, + &new_session, sizeof(new_session), true) && + socket_receive_all_blocking(fd, + &netplay->mitm_session_id, sizeof(netplay->mitm_session_id)) && + ntohl(netplay->mitm_session_id.magic) == MITM_SESSION_MAGIC && + memcmp(netplay->mitm_session_id.unique, invalid_session.unique, + sizeof(netplay->mitm_session_id.unique))) + { + /* Initialize data for handling tunneled client connections. */ + netplay->mitm_pending = calloc(1, sizeof(*netplay->mitm_pending)); + if (netplay->mitm_pending) + { + memset(netplay->mitm_pending->fds, -1, + sizeof(netplay->mitm_pending->fds)); + netplay->mitm_pending->addr = res; + return fd; + } + } + + dmsg = "Failed to create a tunnel session."; + } + else + { +#ifndef HAVE_SOCKET_LEGACY + if (!getnameinfo(res->ai_addr, res->ai_addrlen, + host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV)) + { + snprintf(msg, sizeof(msg), + "Failed to connect to relay server %s on port %s.", + host, port); + dmsg = msg; + } + else +#endif + dmsg = "Failed to connect to relay server."; + } } else { -#if defined(HAVE_INET6) && defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) +#if defined(HAVE_INET6) && defined(IPV6_V6ONLY) /* Make sure we accept connections on both IPv6 and IPv4 */ if (res->ai_family == AF_INET6) { int on = 0; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, - (const char*)&on, sizeof(on)) < 0) - RARCH_WARN("Failed to listen on both IPv6 and IPv4\n"); + (const char*) &on, sizeof(on)) < 0) + RARCH_WARN("[Netplay] Failed to listen on both IPv6 and IPv4.\n"); } #endif if (socket_bind(fd, (void*)res)) { - if (!listen(fd, 1024)) + if (!listen(fd, 64)) return fd; } else { #ifndef HAVE_SOCKET_LEGACY if (!getnameinfo(res->ai_addr, res->ai_addrlen, - NULL, 0, port, sizeof(port), NI_NUMERICSERV)) + NULL, 0, port, sizeof(port), NI_NUMERICSERV)) { - snprintf(msg, sizeof(msg), "Failed to bind port %s.", port); + snprintf(msg, sizeof(msg), + "Failed to bind port %s.", + port); dmsg = msg; } -#else - dmsg = "Failed to bind port."; + else #endif + dmsg = "Failed to bind port."; } } socket_close(fd); if (dmsg) - RARCH_ERR("%s\n", dmsg); + RARCH_ERR("[Netplay] %s\n", dmsg); return -1; } -static bool init_tcp_socket(netplay_t *netplay, void *direct_host, - const char *server, uint16_t port) +static bool init_tcp_socket(netplay_t *netplay, + const char *server, const char *mitm, uint16_t port) { struct addrinfo *res; const struct addrinfo *tmp_info; - struct sockaddr_storage sad; + char port_buf[6]; struct addrinfo hints = {0}; + bool is_mitm = !server && mitm; int fd = -1; - if (!direct_host) + if (!server) { - char port_buf[6]; - snprintf(port_buf, sizeof(port_buf), "%hu", port); - if (!server) + if (!is_mitm) { hints.ai_flags = AI_PASSIVE; #ifdef HAVE_INET6 @@ -5691,62 +6414,55 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, hints.ai_family = AF_INET; #endif } - hints.ai_socktype = SOCK_STREAM; - - if (getaddrinfo_retro(server, port_buf, &hints, &res)) + else + { + /* IPv4 only for relay servers. */ + hints.ai_family = AF_INET; + } + } + hints.ai_socktype = SOCK_STREAM; + + snprintf(port_buf, sizeof(port_buf), "%hu", port); + + if (getaddrinfo_retro(is_mitm ? mitm : server, port_buf, + &hints, &res)) + { + if (!server && !is_mitm) { - if (!server) - { #ifdef HAVE_INET6 try_ipv4: - /* Didn't work with IPv6, try IPv4 */ - hints.ai_family = AF_INET; - if (getaddrinfo_retro(server, port_buf, &hints, &res)) + /* Didn't work with IPv6, try IPv4 */ + hints.ai_family = AF_INET; + if (getaddrinfo_retro(server, port_buf, &hints, &res)) #endif - { - RARCH_ERR("Failed to set a hosting address.\n"); - - return false; - } - } - else { - char msg[512]; - snprintf(msg, sizeof(msg), - "Failed to resolve host: %s\n", server); - RARCH_ERR(msg); + RARCH_ERR("[Netplay] Failed to set a hosting address.\n"); return false; } } - - if (!res) - return false; - - /* If we're serving on IPv6, make sure we accept all connections, including - * IPv4 */ -#ifdef HAVE_INET6 - if (!server && res->ai_family == AF_INET6) + else { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) res->ai_addr; -#if defined(_MSC_VER) && _MSC_VER <= 1200 - IN6ADDR_SETANY(sin6); -#else - sin6->sin6_addr = in6addr_any; -#endif + RARCH_ERR("[Netplay] Failed to resolve host: %s\n", is_mitm ? mitm : server); + return false; } + } + + if (!res) + return false; + + /* If we're serving on IPv6, make sure we accept all connections, including + * IPv4 */ +#ifdef HAVE_INET6 + if (!server && !is_mitm && res->ai_family == AF_INET6) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) res->ai_addr; +#if defined(_MSC_VER) && _MSC_VER <= 1200 + IN6ADDR_SETANY(sin6); +#else + sin6->sin6_addr = in6addr_any; #endif } - else - { - /* I'll build my own addrinfo! */ - struct netplay_host *host = (struct netplay_host *)direct_host; - hints.ai_family = host->addr.sa_family; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - hints.ai_addrlen = host->addrlen; - hints.ai_addr = &host->addr; - res = &hints; - } +#endif /* If "localhost" is used, it is important to check every possible * address for IPv4/IPv6. */ @@ -5754,34 +6470,39 @@ try_ipv4: do { - fd = init_tcp_connection( - tmp_info, direct_host || server, - (struct sockaddr *) - memset(&sad, 0, sizeof(sad)), sizeof(sad)); - + fd = init_tcp_connection(netplay, tmp_info, !server, is_mitm); if (fd >= 0) break; } while ((tmp_info = tmp_info->ai_next)); - if (!direct_host) + if (netplay->mitm_pending && netplay->mitm_pending->addr) + netplay->mitm_pending->base_addr = res; + else freeaddrinfo_retro(res); res = NULL; if (fd < 0) { #ifdef HAVE_INET6 - if (!direct_host && !server && hints.ai_family == AF_INET6) + if (!server && !is_mitm && hints.ai_family == AF_INET6) goto try_ipv4; #endif - RARCH_ERR("Failed to set up netplay sockets.\n"); + RARCH_ERR("[Netplay] Failed to set up netplay sockets.\n"); return false; } - if (direct_host || server) + if (!socket_nonblock(fd)) + { + socket_close(fd); + return false; + } + + if (server) { netplay->connections[0].active = true; netplay->connections[0].fd = fd; - netplay->connections[0].addr = sad; + memset(&netplay->connections[0].addr, 0, + sizeof(netplay->connections[0].addr)); } else netplay->listen_fd = fd; @@ -5789,13 +6510,13 @@ try_ipv4: return true; } -static bool init_socket(netplay_t *netplay, void *direct_host, - const char *server, uint16_t port) +static bool init_socket(netplay_t *netplay, + const char *server, const char *mitm, uint16_t port) { if (!network_init()) return false; - if (!init_tcp_socket(netplay, direct_host, server, port)) + if (!init_tcp_socket(netplay, server, mitm, port)) return false; if (netplay->is_server && netplay->nat_traversal) @@ -5971,9 +6692,10 @@ static bool netplay_init_buffers(netplay_t *netplay) /** * netplay_new: - * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. + * @mitm : IP address of the MITM/tunnel server. * @port : Port of server. + * @mitm_session : Session id for MITM/tunnel. * @stateless_mode : Shall we use stateless mode? * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. @@ -5986,26 +6708,31 @@ static bool netplay_init_buffers(netplay_t *netplay) * * Returns: new netplay data. */ -netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, +netplay_t *netplay_new(const char *server, const char *mitm, uint16_t port, + const char *mitm_session, bool stateless_mode, int check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks) { - netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay)); + settings_t *settings = config_get_ptr(); + netplay_t *netplay = calloc(1, sizeof(*netplay)); + if (!netplay) return NULL; netplay->listen_fd = -1; netplay->tcp_port = port; + netplay->ext_tcp_port = port; netplay->cbs = *cb; - netplay->is_server = (direct_host == NULL && server == NULL); + netplay->is_server = !server; netplay->is_connected = false; - netplay->nat_traversal = netplay->is_server ? nat_traversal : false; + netplay->nat_traversal = (!server && !mitm) ? nat_traversal : false; netplay->stateless_mode = stateless_mode; netplay->check_frames = check_frames; netplay->crc_validity_checked = false; netplay->crcs_valid = true; netplay->quirks = quirks; + netplay->simple_rand_next = 1; netplay->self_mode = netplay->is_server ? NETPLAY_CONNECTION_SPECTATING : NETPLAY_CONNECTION_NONE; @@ -6014,26 +6741,74 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, { netplay->connections = NULL; netplay->connections_size = 0; + + netplay->allow_pausing = + settings->bools.netplay_allow_pausing; + netplay->input_latency_frames_min = + settings->uints.netplay_input_latency_frames_min; + if (settings->bools.run_ahead_enabled) + netplay->input_latency_frames_min -= + settings->uints.run_ahead_frames; + netplay->input_latency_frames_max = + netplay->input_latency_frames_min + + settings->uints.netplay_input_latency_frames_range; } else { netplay->connections = &netplay->one_connection; netplay->connections_size = 1; netplay->connections[0].fd = -1; + + if (!string_is_empty(mitm_session)) + { + int flen; + unsigned char *buf; + + buf = unbase64(mitm_session, strlen(mitm_session), &flen); + if (!buf) + goto failure; + if (flen != sizeof(netplay->mitm_session_id.unique)) + { + free(buf); + goto failure; + } + + netplay->mitm_session_id.magic = htonl(MITM_SESSION_MAGIC); + memcpy(netplay->mitm_session_id.unique, buf, flen); + free(buf); + } } - strlcpy(netplay->nick, nick[0] + strlcpy(netplay->nick, !string_is_empty(nick) ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); - if (!init_socket(netplay, direct_host, server, port) || + if (!init_socket(netplay, server, mitm, port) || !netplay_init_buffers(netplay)) goto failure; if (netplay->is_server) { - /* Clients get device info from the server */ unsigned i; + + if (netplay->mitm_session_id.magic) + { + int flen; + char *buf; + net_driver_state_t *net_st = &networking_driver_st; + struct netplay_room *host_room = &net_st->host_room; + + buf = base64(netplay->mitm_session_id.unique, + sizeof(netplay->mitm_session_id.unique), &flen); + if (!buf) + goto failure; + + strlcpy(host_room->mitm_session, buf, + sizeof(host_room->mitm_session)); + free(buf); + } + + /* Clients get device info from the server */ for (i = 0; i < MAX_INPUT_DEVICES; i++) { uint32_t dtype = input_config_get_device(i); @@ -6044,31 +6819,19 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay_key_hton_init(); } if (dtype != RETRO_DEVICE_NONE && !netplay_expected_input_size(netplay, 1<connections[0]); + netplay_handshake_init_send(netplay, &netplay->connections[0], + LOW_NETPLAY_PROTOCOL_VERSION); netplay->connections[0].mode = NETPLAY_CONNECTION_INIT; netplay->self_mode = NETPLAY_CONNECTION_INIT; } - /* FIXME: Not really the right place to do this, - * socket initialization needs to be fixed in general. */ - if (netplay->is_server) - { - if (!socket_nonblock(netplay->listen_fd)) - goto failure; - } - else - { - if (!socket_nonblock(netplay->connections[0].fd)) - goto failure; - } - return netplay; failure: @@ -6089,6 +6852,19 @@ void netplay_free(netplay_t *netplay) if (netplay->listen_fd >= 0) socket_close(netplay->listen_fd); + if (netplay->mitm_pending) + { + for (i = 0; i < NETPLAY_MITM_MAX_PENDING; i++) + { + int fd = netplay->mitm_pending->fds[i]; + if (fd >= 0) + socket_close(fd); + } + + freeaddrinfo_retro(netplay->mitm_pending->base_addr); + free(netplay->mitm_pending); + } + for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; @@ -6356,9 +7132,23 @@ void netplay_toggle_play_spectate(netplay_t *netplay) { case NETPLAY_CONNECTION_PLAYING: case NETPLAY_CONNECTION_SLAVE: - /* Switch to spectator mode immediately */ - netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING); + { + uint32_t device; + + /* Switch to spectator mode immediately */ + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + netplay->self_devices = 0; + + netplay->connected_players &= ~(1<self_client_num); + netplay->client_devices[netplay->self_client_num] = 0; + for (device = 0; device < MAX_INPUT_DEVICES; device++) + netplay->device_clients[device] &= ~(1<self_client_num); + + /* Announce it */ + announce_play_spectate(netplay, NULL, NETPLAY_CONNECTION_SPECTATING, 0, -1); + + netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING); + } break; case NETPLAY_CONNECTION_SPECTATING: /* Switch only after getting permission */ @@ -6651,9 +7441,8 @@ bool netplay_poll( unsigned frames_ahead = (netplay->run_frame_count > netplay->unread_frame_count) ? (netplay->run_frame_count - netplay->unread_frame_count) : 0; - int input_latency_frames_min = settings->uints.netplay_input_latency_frames_min - - (settings->bools.run_ahead_enabled ? settings->uints.run_ahead_frames : 0); - int input_latency_frames_max = input_latency_frames_min + settings->uints.netplay_input_latency_frames_range; + int input_latency_frames_min = netplay->input_latency_frames_min; + int input_latency_frames_max = netplay->input_latency_frames_max; /* Assume we need a couple frames worth of time to actually run the * current frame */ @@ -6871,20 +7660,24 @@ void netplay_post_frame(netplay_t *netplay) } } -bool init_netplay_deferred(const char* server, unsigned port) +bool init_netplay_deferred(const char *server, unsigned port, const char *mitm_session) { - net_driver_state_t *net_st = &networking_driver_st; - if (!string_is_empty(server) && port != 0) - { - strlcpy(net_st->server_address_deferred, server, - sizeof(net_st->server_address_deferred)); - net_st->server_port_deferred = port; - net_st->netplay_client_deferred = true; - } - else - net_st->netplay_client_deferred = false; + net_driver_state_t *net_st = &networking_driver_st; - return net_st->netplay_client_deferred; + if (string_is_empty(server) || !port) + { + net_st->netplay_client_deferred = false; + return false; + } + + strlcpy(net_st->server_address_deferred, server, + sizeof(net_st->server_address_deferred)); + net_st->server_port_deferred = port; + strlcpy(net_st->server_session_deferred, mitm_session, + sizeof(net_st->server_session_deferred)); + net_st->netplay_client_deferred = true; + + return true; } /** @@ -6936,208 +7729,143 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames) } static void netplay_announce_cb(retro_task_t *task, - void *task_data, void *user_data, const char *error) + void *task_data, void *user_data, const char *error) { - net_driver_state_t *net_st = &networking_driver_st; - if (task_data) + char *buf_start, *buf; + size_t remaining; + net_driver_state_t *net_st = &networking_driver_st; + struct netplay_room *host_room = &net_st->host_room; + http_transfer_data_t *data = task_data; + + if (error) + return; + if (!data || !data->data || !data->len) + return; + if (data->status != 200) + return; + + buf_start = malloc(data->len); + if (!buf_start) + return; + memcpy(buf_start, data->data, data->len); + + buf = buf_start; + remaining = data->len; + do { - unsigned i, ip_len, port_len; - http_transfer_data_t *data = (http_transfer_data_t*)task_data; - struct netplay_room *host_room = &net_st->host_room; - struct string_list *lines = NULL; - char *mitm_ip = NULL; - char *mitm_port = NULL; - char *buf = NULL; - char *host_string = NULL; + char *lnbreak, *delim; + char *key, *value; - if (data->len == 0) - return; + lnbreak = (char*) memchr(buf, '\n', remaining); + if (!lnbreak) + break; + *lnbreak++ = '\0'; - buf = (char*)calloc(1, data->len + 1); - - memcpy(buf, data->data, data->len); - - lines = string_split(buf, "\n"); - - if (lines->size == 0) + delim = (char*) strchr(buf, '='); + if (delim) { - string_list_free(lines); - free(buf); - return; - } + *delim++ = '\0'; - memset(host_room, 0, sizeof(*host_room)); + key = buf; + value = delim; - for (i = 0; i < lines->size; i++) - { - const char *line = lines->elems[i].data; - - if (!string_is_empty(line)) + if (!string_is_empty(key) && !string_is_empty(value)) { - struct string_list *kv = string_split(line, "="); - const char *key = NULL; - const char *val = NULL; - - if (!kv) - continue; - - if (kv->size != 2) - { - string_list_free(kv); - continue; - } - - key = kv->elems[0].data; - val = kv->elems[1].data; - if (string_is_equal(key, "id")) - sscanf(val, "%i", &host_room->id); - if (string_is_equal(key, "username")) - strlcpy(host_room->nickname, val, sizeof(host_room->nickname)); - if (string_is_equal(key, "ip")) - strlcpy(host_room->address, val, sizeof(host_room->address)); - if (string_is_equal(key, "mitm_ip")) - { - mitm_ip = strdup(val); - strlcpy(host_room->mitm_address, val, sizeof(host_room->mitm_address)); - } - if (string_is_equal(key, "port")) - sscanf(val, "%i", &host_room->port); - if (string_is_equal(key, "mitm_port")) - { - mitm_port = strdup(val); - sscanf(mitm_port, "%i", &host_room->mitm_port); - } - if (string_is_equal(key, "core_name")) - strlcpy(host_room->corename, val, sizeof(host_room->corename)); - if (string_is_equal(key, "frontend")) - strlcpy(host_room->frontend, val, sizeof(host_room->frontend)); - if (string_is_equal(key, "core_version")) - strlcpy(host_room->coreversion, val, sizeof(host_room->coreversion)); - if (string_is_equal(key, "game_name")) - strlcpy(host_room->gamename, val, - sizeof(host_room->gamename)); - if (string_is_equal(key, "game_crc")) - sscanf(val, "%08d", &host_room->gamecrc); - if (string_is_equal(key, "host_method")) - sscanf(val, "%i", &host_room->host_method); - if (string_is_equal(key, "has_password")) - { - if ( string_is_equal_noncase(val, "true") - || string_is_equal(val, "1")) - host_room->has_password = true; - else - host_room->has_password = false; - } - if (string_is_equal(key, "has_spectate_password")) - { - if ( string_is_equal_noncase(val, "true") - || string_is_equal(val, "1")) - host_room->has_spectate_password = true; - else - host_room->has_spectate_password = false; - } - if (string_is_equal(key, "fixed")) - { - if ( string_is_equal_noncase(val, "true") - || string_is_equal(val, "1")) - host_room->fixed = true; - else - host_room->fixed = false; - } - if (string_is_equal(key, "retroarch_version")) - strlcpy(host_room->retroarch_version, val, - sizeof(host_room->retroarch_version)); - if (string_is_equal(key, "country")) - strlcpy(host_room->country, val, - sizeof(host_room->country)); - - string_list_free(kv); + sscanf(value, "%i", &host_room->id); + else if (string_is_equal(key, "username")) + strlcpy(host_room->nickname, value, sizeof(host_room->nickname)); + else if (string_is_equal(key, "ip")) + strlcpy(host_room->address, value, sizeof(host_room->address)); + else if (string_is_equal(key, "port")) + sscanf(value, "%i", &host_room->port); + else if (string_is_equal(key, "core_name")) + strlcpy(host_room->corename, value, sizeof(host_room->corename)); + else if (string_is_equal(key, "frontend")) + strlcpy(host_room->frontend, value, sizeof(host_room->frontend)); + else if (string_is_equal(key, "core_version")) + strlcpy(host_room->coreversion, value, sizeof(host_room->coreversion)); + else if (string_is_equal(key, "game_name")) + strlcpy(host_room->gamename, value, sizeof(host_room->gamename)); + else if (string_is_equal(key, "game_crc")) + sscanf(value, "%08d", &host_room->gamecrc); + else if (string_is_equal(key, "host_method")) + sscanf(value, "%i", &host_room->host_method); + else if (string_is_equal(key, "has_password")) + host_room->has_password = string_is_equal_noncase(value, "true") || + string_is_equal(value, "1"); + else if (string_is_equal(key, "has_spectate_password")) + host_room->has_spectate_password = string_is_equal_noncase(value, "true") || + string_is_equal(value, "1"); + else if (string_is_equal(key, "retroarch_version")) + strlcpy(host_room->retroarch_version, value, + sizeof(host_room->retroarch_version)); + else if (string_is_equal(key, "country")) + strlcpy(host_room->country, value, sizeof(host_room->country)); } } - if (mitm_ip && mitm_port) - { - ip_len = (unsigned)strlen(mitm_ip); - port_len = (unsigned)strlen(mitm_port); + remaining -= (size_t)lnbreak - (size_t)buf; + buf = lnbreak; + } while (remaining); - /* Enable Netplay client mode */ - if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) - { - command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - net_st->is_mitm = true; - host_room->host_method = NETPLAY_HOST_METHOD_MITM; - } - - netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); - - host_string = (char*)calloc(1, ip_len + port_len + 2); - - memcpy(host_string, mitm_ip, ip_len); - memcpy(host_string + ip_len, "|", 1); - memcpy(host_string + ip_len + 1, mitm_port, port_len); - - /* Enable Netplay */ - command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, (void*)host_string); - command_event(CMD_EVENT_NETPLAY_INIT, (void*)host_string); - - free(host_string); - } + free(buf_start); #ifdef HAVE_DISCORD - if (discord_state_get_ptr()->inited) - { - discord_userdata_t userdata; - userdata.status = DISCORD_PRESENCE_NETPLAY_HOSTING; - command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); - } -#endif - - string_list_free(lines); - free(buf); - - if (mitm_ip) - free(mitm_ip); - if (mitm_port) - free(mitm_port); + if (discord_state_get_ptr()->inited) + { + discord_userdata_t userdata; + userdata.status = DISCORD_PRESENCE_NETPLAY_HOSTING; + command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); } +#endif } -static void netplay_announce(void) +static void netplay_announce(netplay_t *netplay) { - char buf[4600]; + char buf[8192]; + const frontend_ctx_driver_t *frontend_drv; char frontend_architecture[PATH_MAX_LENGTH]; char frontend_architecture_tmp[32]; - const frontend_ctx_driver_t - *frontend_drv = NULL; - char url[2048] = "http://lobby.libretro.com/add/"; + uint32_t content_crc; char *username = NULL; char *corename = NULL; + char *coreversion = NULL; char *gamename = NULL; char *subsystemname = NULL; - char *coreversion = NULL; char *frontend_ident = NULL; + char *mitm_session = NULL; settings_t *settings = config_get_ptr(); - runloop_state_t *runloop_st = runloop_state_get_ptr(); - struct retro_system_info *system = &runloop_st->system.info; - uint32_t content_crc = content_get_crc(); + net_driver_state_t *net_st = &networking_driver_st; + struct netplay_room *host_room = &net_st->host_room; + struct retro_system_info *system = &runloop_state_get_ptr()->system.info; struct string_list *subsystem = path_get_subsystem_list(); +#ifndef NETPLAY_TEST_BUILD + const char *url = "http://lobby.libretro.com/add"; +#else + const char *url = "http://lobbytest.libretro.com/add"; +#endif - frontend_architecture[0] = '\0'; - buf[0] = '\0'; + net_http_urlencode(&username, netplay->nick); - if (subsystem) + net_http_urlencode(&corename, system->library_name); + net_http_urlencode(&coreversion, system->library_version); + + if (subsystem && subsystem->size > 0) { unsigned i; - - for (i = 0; i < subsystem->size; i++) + buf[0] = '\0'; + for (i = 0;;) { - strlcat(buf, path_basename(subsystem->elems[i].data), sizeof(buf)); - if (i < subsystem->size - 1) - strlcat(buf, "|", sizeof(buf)); + strlcat(buf, path_basename(subsystem->elems[i++].data), sizeof(buf)); + if (i >= subsystem->size) + break; + strlcat(buf, "|", sizeof(buf)); } + net_http_urlencode(&gamename, buf); net_http_urlencode(&subsystemname, path_get(RARCH_PATH_SUBSYSTEM)); + content_crc = 0; } else @@ -7148,49 +7876,152 @@ static void netplay_announce(void) !string_is_empty(base) ? base : "N/A"); /* TODO/FIXME - subsystem should be implemented later? */ net_http_urlencode(&subsystemname, "N/A"); + + content_crc = content_get_crc(); } frontend_drv = (const frontend_ctx_driver_t*)frontend_driver_get_cpu_architecture_str( frontend_architecture_tmp, sizeof(frontend_architecture_tmp)); - snprintf(frontend_architecture, + if (frontend_drv) + snprintf(frontend_architecture, sizeof(frontend_architecture), "%s %s", frontend_drv->ident, frontend_architecture_tmp); - -#ifdef HAVE_DISCORD - if (discord_is_ready()) - net_http_urlencode(&username, discord_get_own_username()); else -#endif - net_http_urlencode(&username, settings->paths.username); - net_http_urlencode(&corename, system->library_name); - net_http_urlencode(&coreversion, system->library_version); + strlcpy(frontend_architecture, + "N/A", + sizeof(frontend_architecture)); net_http_urlencode(&frontend_ident, frontend_architecture); - buf[0] = '\0'; + net_http_urlencode(&mitm_session, host_room->mitm_session); - snprintf(buf, sizeof(buf), "username=%s&core_name=%s&core_version=%s&" - "game_name=%s&game_crc=%08lX&port=%d&mitm_server=%s" - "&has_password=%d&has_spectate_password=%d&force_mitm=%d" - "&retroarch_version=%s&frontend=%s&subsystem_name=%s", - username, corename, coreversion, gamename, (unsigned long)content_crc, - settings->uints.netplay_port, - settings->arrays.netplay_mitm_server, - *settings->paths.netplay_password ? 1 : 0, - *settings->paths.netplay_spectate_password ? 1 : 0, - settings->bools.netplay_use_mitm_server, - PACKAGE_VERSION, frontend_architecture, subsystemname); - task_push_http_post_transfer(url, buf, true, NULL, - netplay_announce_cb, NULL); + snprintf(buf, sizeof(buf), + "username=%s&" + "core_name=%s&" + "core_version=%s&" + "game_name=%s&" + "game_crc=%08lX&" + "port=%hu&" + "mitm_server=%s&" + "has_password=%d&" + "has_spectate_password=%d&" + "force_mitm=%d&" + "retroarch_version=%s&" + "frontend=%s&" + "subsystem_name=%s&" + "mitm_session=%s", + username, + corename, + coreversion, + gamename, + (unsigned long)content_crc, + netplay->ext_tcp_port, + host_room->mitm_handle, + !string_is_empty(settings->paths.netplay_password) ? 1 : 0, + !string_is_empty(settings->paths.netplay_spectate_password) ? 1 : 0, + !string_is_empty(host_room->mitm_session) ? 1 : 0, + PACKAGE_VERSION, + frontend_ident, + subsystemname, + mitm_session); free(username); free(corename); + free(coreversion); free(gamename); free(subsystemname); - free(coreversion); free(frontend_ident); + free(mitm_session); + + task_push_http_post_transfer(url, buf, true, NULL, + netplay_announce_cb, NULL); +} + +static void netplay_mitm_query_cb(retro_task_t *task, void *task_data, + void *user_data, const char *error) +{ + char *buf_start, *buf; + size_t remaining; + net_driver_state_t *net_st = &networking_driver_st; + struct netplay_room *host_room = &net_st->host_room; + http_transfer_data_t *data = task_data; + + if (error) + return; + if (!data || !data->data || !data->len) + return; + if (data->status != 200) + return; + + buf_start = malloc(data->len); + if (!buf_start) + return; + memcpy(buf_start, data->data, data->len); + + buf = buf_start; + remaining = data->len; + do + { + char *lnbreak, *delim; + char *key, *value; + + lnbreak = (char*) memchr(buf, '\n', remaining); + if (!lnbreak) + break; + *lnbreak++ = '\0'; + + delim = (char*) strchr(buf, '='); + if (delim) + { + *delim++ = '\0'; + + key = buf; + value = delim; + + if (!string_is_empty(key) && !string_is_empty(value)) + { + if (string_is_equal(key, "tunnel_addr")) + strlcpy(host_room->mitm_address, value, + sizeof(host_room->mitm_address)); + else if (string_is_equal(key, "tunnel_port")) + sscanf(value, "%i", &host_room->mitm_port); + } + } + + remaining -= (size_t)lnbreak - (size_t)buf; + buf = lnbreak; + } while (remaining); + + free(buf_start); +} + +static bool netplay_mitm_query(const char *mitm_name) +{ + char query[512]; + net_driver_state_t *net_st = &networking_driver_st; + struct netplay_room *host_room = &net_st->host_room; +#ifndef NETPLAY_TEST_BUILD + const char *url = "http://lobby.libretro.com/tunnel"; +#else + const char *url = "http://lobbytest.libretro.com/tunnel"; +#endif + + if (string_is_empty(mitm_name)) + return false; + + snprintf(query, sizeof(query), "%s?name=%s", url, mitm_name); + + if (!task_push_http_transfer(query, + true, NULL, netplay_mitm_query_cb, NULL)) + return false; + + /* Make sure we've the tunnel address before continuing. */ + task_queue_wait(NULL, NULL); + + return !string_is_empty(host_room->mitm_address) && + host_room->mitm_port; } int16_t input_state_net(unsigned port, unsigned device, @@ -7251,41 +8082,56 @@ static bool netplay_pre_frame( bool netplay_use_mitm_server, netplay_t *netplay) { - bool sync_stalled = false; - net_driver_state_t *net_st = &networking_driver_st; + bool netplay_force_disconnect = false; + bool sync_stalled = false; + net_driver_state_t *net_st = &networking_driver_st; retro_assert(netplay); - if (netplay_public_announce) + if (netplay->is_server) { - net_st->reannounce++; - if ( - (netplay->is_server || net_st->is_mitm) && - (net_st->reannounce % 300 == 0)) - netplay_announce(); + if (netplay_public_announce) + { + if (++net_st->reannounce % 1200 == 0) + netplay_announce(netplay); + } + /* Make sure that if announcement is turned on mid-game, it gets announced */ + else + { + net_st->reannounce = -1; + } + } + else if (netplay->is_connected) + { + if (++net_st->reping % 180 == 0) + request_ping(netplay, &netplay->connections[0]); } - /* Make sure that if announcement is turned on mid-game, it gets announced */ - else - net_st->reannounce = -1; /* FIXME: This is an ugly way to learn we're not paused anymore */ if (netplay->local_paused) - if (netplay->local_paused != false) - netplay_frontend_paused(netplay, false); + netplay_frontend_paused(netplay, false); /* Are we ready now? */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) netplay_try_init_serialization(netplay); +#ifdef HAVE_NETPLAYDISCOVERY if (netplay->is_server && !netplay_use_mitm_server) { -#ifdef HAVE_NETPLAYDISCOVERY /* Advertise our server */ - netplay_lan_ad_server(netplay); -#endif + if (net_st->lan_ad_server_fd >= 0 || + init_lan_ad_server_socket()) + netplay_lan_ad_server(netplay); } +#endif - sync_stalled = !netplay_sync_pre_frame(netplay); + sync_stalled = !netplay_sync_pre_frame(netplay, &netplay_force_disconnect); + + if (netplay_force_disconnect) + { + netplay_disconnect(netplay); + return true; + } /* If we're disconnected, deinitialize */ if (!netplay->is_server && !netplay->connections[0].active) @@ -7308,6 +8154,7 @@ static bool netplay_pre_frame( void deinit_netplay(void) { + size_t i; net_driver_state_t *net_st = &networking_driver_st; if (net_st->data) @@ -7316,30 +8163,47 @@ void deinit_netplay(void) netplay_deinit_nat_traversal(); netplay_free(net_st->data); + net_st->data = NULL; net_st->netplay_enabled = false; net_st->netplay_is_client = false; - net_st->is_mitm = false; - net_st->data = NULL; + +#ifdef HAVE_NETPLAYDISCOVERY + deinit_lan_ad_server_socket(); +#endif } + + for (i = 0; i < ARRAY_SIZE(net_st->chat.messages); i++) + { + *net_st->chat.messages[i].data.nick = '\0'; + *net_st->chat.messages[i].data.msg = '\0'; + net_st->chat.messages[i].data.frames = 0; + } + core_unset_netplay_callbacks(); } -bool init_netplay(void *direct_host, const char *server, unsigned port) +bool init_netplay(const char *server, unsigned port, const char *mitm_session) { + size_t i; struct retro_callbacks cbs = {0}; uint64_t serialization_quirks = 0; uint64_t quirks = 0; settings_t *settings = config_get_ptr(); net_driver_state_t *net_st = &networking_driver_st; - bool _netplay_is_client = net_st->netplay_is_client; - bool _netplay_enabled = net_st->netplay_enabled; + const char *mitm = NULL; - if (!_netplay_enabled) + if (!net_st->netplay_enabled) + { + net_st->netplay_client_deferred = false; return false; + } core_set_default_callbacks(&cbs); if (!core_set_netplay_callbacks()) + { + net_st->netplay_client_deferred = false; return false; + } /* Map the core's quirks to our quirks */ serialization_quirks = core_serialization_quirks(); @@ -7359,34 +8223,59 @@ bool init_netplay(void *direct_host, const char *server, unsigned port) if (serialization_quirks & NETPLAY_QUIRK_MAP_PLATFORM_DEPENDENT) quirks |= NETPLAY_QUIRK_PLATFORM_DEPENDENT; - if (!_netplay_is_client) + if (!net_st->netplay_is_client) { - runloop_msg_queue_push( - msg_hash_to_str(MSG_WAITING_FOR_CLIENT), - 0, 180, false, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + struct netplay_room *host_room = &net_st->host_room; - if (settings->bools.netplay_public_announce) - netplay_announce(); + memset(host_room, 0, sizeof(*host_room)); + + server = NULL; + + if (settings->bools.netplay_use_mitm_server) + { + const char *mitm_handle = settings->arrays.netplay_mitm_server; + if (netplay_mitm_query(mitm_handle)) + { + /* We want to cache the MITM server handle in order to + prevent sending the wrong one to the lobby server if + we change its config mid-session. */ + strlcpy(host_room->mitm_handle, mitm_handle, + sizeof(host_room->mitm_handle)); + + mitm = host_room->mitm_address; + port = host_room->mitm_port; + } + else + { + RARCH_WARN("[Netplay] Failed to get tunnel information. Switching to direct mode.\n"); + } + } + + if (!port) + port = RARCH_DEFAULT_PORT; + } + else + { + if (net_st->netplay_client_deferred) + { + server = net_st->server_address_deferred; + port = net_st->server_port_deferred; + mitm_session = net_st->server_session_deferred; + } } - net_st->data = (netplay_t*)netplay_new( - _netplay_is_client - ? direct_host - : NULL, - _netplay_is_client - ? (!net_st->netplay_client_deferred - ? server - : net_st->server_address_deferred) - : NULL, - _netplay_is_client ? (!net_st->netplay_client_deferred - ? port - : net_st->server_port_deferred) - : (port != 0 ? port : RARCH_DEFAULT_PORT), +#ifdef HAVE_NETPLAYDISCOVERY + net_st->lan_ad_server_fd = -1; +#endif + + net_st->netplay_client_deferred = false; + + net_st->data = netplay_new( + server, mitm, port, mitm_session, settings->bools.netplay_stateless_mode, settings->ints.netplay_check_frames, &cbs, - settings->bools.netplay_nat_traversal && !settings->bools.netplay_use_mitm_server, + settings->bools.netplay_nat_traversal, #ifdef HAVE_DISCORD discord_get_own_username() ? discord_get_own_username() @@ -7395,21 +8284,39 @@ bool init_netplay(void *direct_host, const char *server, unsigned port) settings->paths.username, quirks); - if (net_st->data) + if (!net_st->data) { - if ( net_st->data->is_server - && !settings->bools.netplay_start_as_spectator) - netplay_toggle_play_spectate(net_st->data); - return true; + RARCH_ERR("[Netplay] %s\n", msg_hash_to_str(MSG_NETPLAY_FAILED)); + runloop_msg_queue_push( + msg_hash_to_str(MSG_NETPLAY_FAILED), + 0, 180, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + return false; } - RARCH_WARN("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED)); + for (i = 0; i < ARRAY_SIZE(net_st->chat.messages); i++) + { + *net_st->chat.messages[i].data.nick = '\0'; + *net_st->chat.messages[i].data.msg = '\0'; + net_st->chat.messages[i].data.frames = 0; + } + net_st->chat.message_slots = ARRAY_SIZE(net_st->chat.messages); - runloop_msg_queue_push( - msg_hash_to_str(MSG_NETPLAY_FAILED), + net_st->reannounce = -1; + net_st->reping = -1; + + if (net_st->data->is_server) + { + runloop_msg_queue_push( + msg_hash_to_str(MSG_WAITING_FOR_CLIENT), 0, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - return false; + + if (!settings->bools.netplay_start_as_spectator) + netplay_toggle_play_spectate(net_st->data); + } + + return true; } /** @@ -7507,12 +8414,18 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) case RARCH_NETPLAY_CTL_PRE_FRAME: ret = netplay_pre_frame( settings->bools.netplay_public_announce, - settings->bools.netplay_use_mitm_server, + netplay->mitm_pending != NULL, netplay); goto done; case RARCH_NETPLAY_CTL_GAME_WATCH: netplay_toggle_play_spectate(netplay); break; + case RARCH_NETPLAY_CTL_PLAYER_CHAT: + netplay_input_chat(netplay); + break; + case RARCH_NETPLAY_CTL_ALLOW_PAUSE: + ret = netplay->allow_pausing; + break; case RARCH_NETPLAY_CTL_PAUSE: if (netplay->local_paused != true) netplay_frontend_paused(netplay, true); @@ -7555,3 +8468,317 @@ done: net_st->in_netplay = false; return ret; } + +/* Netplay Utils */ + +bool netplay_decode_hostname(const char *hostname, + char *address, unsigned *port, char *session, size_t len) +{ + struct string_list *hostname_data; + + if (string_is_empty(hostname)) + return false; + + hostname_data = string_split(hostname, "|"); + if (!hostname_data) + return false; + + if (hostname_data->size >= 1 && + !string_is_empty(hostname_data->elems[0].data)) + strlcpy(address, hostname_data->elems[0].data, len); + + if (hostname_data->size >= 2 && + !string_is_empty(hostname_data->elems[1].data)) + { + unsigned tmp_port = strtoul(hostname_data->elems[1].data, NULL, 10); + if (tmp_port && tmp_port <= 65535) + *port = tmp_port; + } + + if (hostname_data->size >= 3 && + !string_is_empty(hostname_data->elems[2].data)) + strlcpy(session, hostname_data->elems[2].data, len); + + string_list_free(hostname_data); + + return true; +} + +bool netplay_is_lan_address(struct sockaddr_in *addr) +{ + static const uint32_t subnets[] = {0x0A000000, 0xAC100000, 0xC0A80000}; + static const uint32_t masks[] = {0xFF000000, 0xFFF00000, 0xFFFF0000}; + size_t i; + + for (i = 0; i < ARRAY_SIZE(subnets); i++) + { + if ((*(uint32_t *)&addr->sin_addr & masks[i]) == subnets[i]) + return true; + } + + return false; +} + +/* Netplay Widgets */ + +#ifdef HAVE_GFX_WIDGETS +static void gfx_widget_netplay_chat_iterate(void *user_data, + unsigned width, unsigned height, bool fullscreen, + const char *dir_assets, char *font_path, + bool is_threaded) +{ + size_t i; + settings_t *settings = config_get_ptr(); + net_driver_state_t *net_st = &networking_driver_st; +#ifdef HAVE_MENU + bool menu_open = menu_state_get_ptr()->alive; +#endif + bool fade_chat = settings->bools.netplay_fade_chat; + + /* Move the messages to a thread-safe buffer + before drawing them. */ + for (i = 0; i < ARRAY_SIZE(net_st->chat.messages); i++) + { + struct netplay_chat_data *data = + &net_st->chat.messages[i].data; + struct netplay_chat_buffer *buffer = + &net_st->chat.messages[i].buffer; + + /* Don't show chat while in the menu */ +#ifdef HAVE_MENU + if (menu_open) + { + buffer->alpha = 0; + continue; + } +#endif + + /* If we are not fading, set alpha to max. */ + if (!fade_chat) + { + buffer->alpha = 0xFF; + } + else if (data->frames) + { + float alpha_percent = (float) data->frames / + (float) NETPLAY_CHAT_FRAME_TIME; + + buffer->alpha = (uint8_t) float_max( + alpha_percent * 255.0f, 1.0f); + + data->frames--; + } + else + { + buffer->alpha = 0; + continue; + } + + memcpy(buffer->nick, data->nick, + sizeof(buffer->nick)); + memcpy(buffer->msg, data->msg, + sizeof(buffer->msg)); + } +} + +static void gfx_widget_netplay_chat_frame(void *data, void *userdata) +{ + size_t i; + video_frame_info_t *video_info = data; + dispgfx_widget_t *p_dispwidget = userdata; + net_driver_state_t *net_st = &networking_driver_st; + int line_height = + p_dispwidget->gfx_widget_fonts.regular.line_height + + p_dispwidget->simple_widget_padding / 3.0f; + int height = + video_info->height - line_height; + + for (i = 0; i < ARRAY_SIZE(net_st->chat.messages); i++) + { + char formatted_nick[NETPLAY_CHAT_MAX_SIZE]; + char formatted_msg[NETPLAY_CHAT_MAX_SIZE]; + int formatted_nick_len; + int formatted_nick_width; + const char *nick = + net_st->chat.messages[i].buffer.nick; + const char *msg = + net_st->chat.messages[i].buffer.msg; + uint8_t alpha = + net_st->chat.messages[i].buffer.alpha; + + if (!alpha || string_is_empty(nick) || string_is_empty(msg)) + continue; + + /* Truncate the message, if necessary. */ + formatted_nick_len = snprintf(formatted_nick, + sizeof(formatted_nick), "%s: ", nick); + strlcpy(formatted_msg, msg, + sizeof(formatted_msg) - formatted_nick_len); + + formatted_nick_width = font_driver_get_message_width( + p_dispwidget->gfx_widget_fonts.regular.font, + formatted_nick, formatted_nick_len, + 1.0f); + + /* Draw the nickname first. */ + gfx_widgets_draw_text( + &p_dispwidget->gfx_widget_fonts.regular, + formatted_nick, + p_dispwidget->simple_widget_padding, + height, + video_info->width, + video_info->height, + NETPLAY_CHAT_NICKNAME_COLOR | (unsigned) alpha, + TEXT_ALIGN_LEFT, + true); + /* Now draw the message. */ + gfx_widgets_draw_text( + &p_dispwidget->gfx_widget_fonts.regular, + formatted_msg, + p_dispwidget->simple_widget_padding + formatted_nick_width, + height, + video_info->width, + video_info->height, + NETPLAY_CHAT_MESSAGE_COLOR | (unsigned) alpha, + TEXT_ALIGN_LEFT, + true); + + /* Move up */ + height -= line_height; + } +} + +static void gfx_widget_netplay_ping_iterate(void *user_data, + unsigned width, unsigned height, bool fullscreen, + const char *dir_assets, char *font_path, + bool is_threaded) +{ + settings_t *settings = config_get_ptr(); + net_driver_state_t *net_st = &networking_driver_st; + netplay_t *netplay = net_st->data; +#ifdef HAVE_MENU + bool menu_open = menu_state_get_ptr()->alive; +#endif + bool show_ping = settings->bools.netplay_ping_show; + + if (!netplay || !show_ping) + { + net_st->latest_ping = -1; + return; + } + + /* Don't show the ping counter while in the menu */ +#ifdef HAVE_MENU + if (menu_open) + { + net_st->latest_ping = -1; + return; + } +#endif + + if (!netplay->is_server && netplay->is_connected) + net_st->latest_ping = netplay->connections[0].ping; + else + net_st->latest_ping = -1; +} + +static void gfx_widget_netplay_ping_frame(void *data, void *userdata) +{ + video_frame_info_t *video_info = data; + dispgfx_widget_t *p_dispwidget = userdata; + net_driver_state_t *net_st = &networking_driver_st; + int ping = net_st->latest_ping; + + if (ping >= 0) + { + char ping_str[16]; + int ping_len; + int ping_width, total_width; + gfx_display_t *p_disp = video_info->disp_userdata; + + /* Limit ping counter to 999. */ + if (ping > 999) + ping = 999; + + ping_len = snprintf(ping_str, sizeof(ping_str), + "PING: %d", ping); + + ping_width = font_driver_get_message_width( + p_dispwidget->gfx_widget_fonts.regular.font, + ping_str, ping_len, 1.0f); + total_width = ping_width + + p_dispwidget->simple_widget_padding * 2; + + gfx_display_set_alpha(p_dispwidget->backdrop_orig, + DEFAULT_BACKDROP); + gfx_display_draw_quad( + p_disp, + video_info->userdata, + video_info->width, + video_info->height, + video_info->width - total_width, + video_info->height - + p_dispwidget->simple_widget_height, + total_width, + p_dispwidget->simple_widget_height, + video_info->width, + video_info->height, + p_dispwidget->backdrop_orig, + NULL); + gfx_widgets_draw_text( + &p_dispwidget->gfx_widget_fonts.regular, + ping_str, + video_info->width - ping_width - + p_dispwidget->simple_widget_padding, + video_info->height - + p_dispwidget->gfx_widget_fonts.regular.line_centre_offset, + video_info->width, + video_info->height, + 0xFFFFFFFF, + TEXT_ALIGN_LEFT, + true); + } +} + +const gfx_widget_t gfx_widget_netplay_chat = { + NULL, + NULL, + NULL, + NULL, + NULL, + &gfx_widget_netplay_chat_iterate, + &gfx_widget_netplay_chat_frame +}; + +const gfx_widget_t gfx_widget_netplay_ping = { + NULL, + NULL, + NULL, + NULL, + NULL, + &gfx_widget_netplay_ping_iterate, + &gfx_widget_netplay_ping_frame +}; +#endif + +#undef NETPLAY_KEY_NTOH + +#undef MITM_SESSION_MAGIC +#undef MITM_LINK_MAGIC +#undef MITM_PING_MAGIC + +#undef DISCOVERY_QUERY_MAGIC +#undef DISCOVERY_RESPONSE_MAGIC + +#undef NETPLAY_MAGIC +#undef FULL_MAGIC +#undef POKE_MAGIC + +#undef SET_PING + +#undef SET_FD_CLOEXEC +#undef SET_TCP_NODELAY + +#if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) && !defined(_3DS) +#undef HAVE_INET6 +#endif diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 710055697e..aea22eb526 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -2,6 +2,7 @@ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2016-2017 - Gregor Richards + * Copyright (C) 2021-2021 - Roberto V. Rampim * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- @@ -27,12 +28,9 @@ #include "../../msg_hash.h" #include "../../verbosity.h" -#define NETPLAY_PROTOCOL_VERSION 5 - #define RARCH_DEFAULT_PORT 55435 #define RARCH_DEFAULT_NICK "Anonymous" -#define NETPLAY_NICK_LEN 32 #define NETPLAY_PASS_LEN 128 #define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */ @@ -174,7 +172,29 @@ enum netplay_cmd /* CMD_CFG streamlines sending multiple configurations. This acknowledges each one individually */ - NETPLAY_CMD_CFG_ACK = 0x0062 + NETPLAY_CMD_CFG_ACK = 0x0062, + + /* Chat commands */ + + /* Sends a player chat message. + * The server is responsible for formatting/truncating + * the message and relaying it to all playing clients, + * including the one that sent the message. */ + NETPLAY_CMD_PLAYER_CHAT = 0x1000, + + /* Ping commands */ + + /* Sends a ping command to the server/client. + * Intended for estimating the latency between these two peers. */ + NETPLAY_CMD_PING_REQUEST = 0x1100, + NETPLAY_CMD_PING_RESPONSE = 0x1101, + + /* Setting commands */ + + /* These host settings should be honored by the client, + * but they are not enforced. */ + NETPLAY_CMD_SETTING_ALLOW_PAUSING = 0x2000, + NETPLAY_CMD_SETTING_INPUT_LATENCY_FRAMES = 0x2001 }; #define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31) @@ -386,6 +406,20 @@ struct netplay_connection /* Is this connection buffer in use? */ bool active; + + /* Which netplay protocol is this connection running? */ + uint32_t netplay_protocol; + + /* Timer used to estimate a connection's latency */ + retro_time_t ping_timer; + + /* What latency is this connection running on? + * Network latency has limited precision as we estimate it + * once every pre-frame. */ + int32_t ping; + + /* Did we request a ping response? */ + bool ping_requested; }; /* Compression transcoder */ @@ -397,6 +431,26 @@ struct compression_transcoder void *decompression_stream; }; +typedef struct mitm_id +{ + uint32_t magic; + uint8_t unique[12]; +} mitm_id_t; + +#define NETPLAY_MITM_MAX_PENDING 8 +struct netplay_mitm_pending +{ + int fds[NETPLAY_MITM_MAX_PENDING]; + mitm_id_t ids[NETPLAY_MITM_MAX_PENDING]; + retro_time_t timeouts[NETPLAY_MITM_MAX_PENDING]; + + mitm_id_t id_buf; + size_t id_recvd; + + struct addrinfo *base_addr; + const struct addrinfo *addr; +}; + struct netplay { /* When did we start falling behind? */ @@ -530,6 +584,7 @@ struct netplay /* TCP port (only set if serving) */ uint16_t tcp_port; + uint16_t ext_tcp_port; /* The sharing mode for each device */ uint8_t device_share_modes[MAX_INPUT_DEVICES]; @@ -593,6 +648,19 @@ struct netplay /* Are we the connected? */ bool is_connected; + /* MITM session id */ + mitm_id_t mitm_session_id; + + /* MITM connection handler */ + struct netplay_mitm_pending *mitm_pending; + + /* Host settings */ + int32_t input_latency_frames_min; + int32_t input_latency_frames_max; + bool allow_pausing; + + /* Pseudo random seed */ + unsigned long simple_rand_next; }; /*************************************************************** @@ -682,17 +750,6 @@ netplay_input_state_t netplay_input_state_for( uint32_t netplay_expected_input_size(netplay_t *netplay, uint32_t devices); -/*************************************************************** - * NETPLAY-DISCOVERY.C - **************************************************************/ - -/** - * netplay_lan_ad_server - * - * Respond to any LAN ad queries that the netplay server has received. - */ -bool netplay_lan_ad_server(netplay_t *netplay); - /*************************************************************** * NETPLAY-FRONTEND.C **************************************************************/ @@ -715,7 +772,7 @@ void input_poll_net(void); * part of the handshake protocol. */ bool netplay_handshake_init_send(netplay_t *netplay, - struct netplay_connection *connection); + struct netplay_connection *connection, uint32_t protocol); /** * netplay_handshake @@ -750,9 +807,10 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay); /** * netplay_new: - * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. + * @mitm : IP address of the MITM/tunnel server. * @port : Port of server. + * @mitm_session : Session id for MITM/tunnel. * @stateless_mode : Shall we run in stateless mode? * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. @@ -765,8 +823,8 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay); * * Returns: new netplay data. */ -netplay_t *netplay_new(void *direct_host, - const char *server, uint16_t port, +netplay_t *netplay_new(const char *server, const char *mitm, uint16_t port, + const char *mitm_session, bool stateless_mode, int check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, @@ -913,10 +971,11 @@ bool netplay_resolve_input(netplay_t *netplay, /** * netplay_sync_pre_frame * @netplay : pointer to netplay object + * @disconnect : disconnect netplay * * Pre-frame for Netplay synchronization. */ -bool netplay_sync_pre_frame(netplay_t *netplay); +bool netplay_sync_pre_frame(netplay_t *netplay, bool *disconnect); /** * netplay_sync_post_frame diff --git a/network/netplay/netplay_protocol.h b/network/netplay/netplay_protocol.h new file mode 100644 index 0000000000..ad1605b333 --- /dev/null +++ b/network/netplay/netplay_protocol.h @@ -0,0 +1,27 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2016-2017 - Gregor Richards + * Copyright (C) 2021-2021 - Roberto V. Rampim + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef __RARCH_NETPLAY_PROTOCOL_H +#define __RARCH_NETPLAY_PROTOCOL_H + +#define LOW_NETPLAY_PROTOCOL_VERSION 5 +#define HIGH_NETPLAY_PROTOCOL_VERSION 6 + +#define NETPLAY_PROTOCOL_VERSION HIGH_NETPLAY_PROTOCOL_VERSION + +#endif diff --git a/network/netplay/netplay_room_parse.c b/network/netplay/netplay_room_parse.c index 2a461dec17..9422297b3e 100644 --- a/network/netplay/netplay_room_parse.c +++ b/network/netplay/netplay_room_parse.c @@ -189,10 +189,6 @@ static bool netplay_json_object_member(void *ctx, const char *p_value, { p_ctx->cur_member_bool = &net_st->rooms_data->cur->has_spectate_password; } - else if (string_is_equal(p_value, "fixed")) - { - p_ctx->cur_member_bool = &net_st->rooms_data->cur->fixed; - } else if (string_is_equal(p_value, "mitm_ip")) { p_ctx->cur_member_string = net_st->rooms_data->cur->mitm_address; @@ -202,6 +198,11 @@ static bool netplay_json_object_member(void *ctx, const char *p_value, { p_ctx->cur_member_int = &net_st->rooms_data->cur->mitm_port; } + else if (string_is_equal(p_value, "mitm_session")) + { + p_ctx->cur_member_string = net_st->rooms_data->cur->mitm_session; + p_ctx->cur_member_size = sizeof(net_st->rooms_data->cur->mitm_session); + } else if (string_is_equal(p_value, "host_method")) { p_ctx->cur_member_int = &net_st->rooms_data->cur->host_method; diff --git a/retroarch.c b/retroarch.c index 4c31631aba..92f7fc2cd4 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2613,6 +2613,12 @@ bool command_event(enum event_command cmd, void *data) bool accessibility_enable = settings->bools.accessibility_enable; unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed; #endif + +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)) + break; +#endif + boolean = runloop_st->paused; boolean = !boolean; @@ -2639,11 +2645,21 @@ bool command_event(enum event_command cmd, void *data) } break; case CMD_EVENT_UNPAUSE: +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)) + break; +#endif + boolean = false; runloop_st->paused = boolean; runloop_pause_checks(); break; case CMD_EVENT_PAUSE: +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)) + break; +#endif + boolean = true; runloop_st->paused = boolean; runloop_pause_checks(); @@ -2652,7 +2668,12 @@ bool command_event(enum event_command cmd, void *data) #ifdef HAVE_MENU if (menu_st->alive) { +#ifdef HAVE_NETWORKING + bool menu_pause_libretro = settings->bools.menu_pause_libretro && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL); +#else bool menu_pause_libretro = settings->bools.menu_pause_libretro; +#endif if (menu_pause_libretro) command_event(CMD_EVENT_AUDIO_STOP, NULL); else @@ -2660,16 +2681,32 @@ bool command_event(enum event_command cmd, void *data) } else { +#ifdef HAVE_NETWORKING + bool menu_pause_libretro = settings->bools.menu_pause_libretro && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL); +#else bool menu_pause_libretro = settings->bools.menu_pause_libretro; +#endif if (menu_pause_libretro) command_event(CMD_EVENT_AUDIO_START, NULL); } #endif break; #ifdef HAVE_NETWORKING + case CMD_EVENT_NETPLAY_PING_TOGGLE: + settings->bools.netplay_ping_show = + !settings->bools.netplay_ping_show; + break; case CMD_EVENT_NETPLAY_GAME_WATCH: netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL); break; + case CMD_EVENT_NETPLAY_PLAYER_CHAT: + netplay_driver_ctl(RARCH_NETPLAY_CTL_PLAYER_CHAT, NULL); + break; + case CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE: + settings->bools.netplay_fade_chat = + !settings->bools.netplay_fade_chat; + break; case CMD_EVENT_NETPLAY_DEINIT: deinit_netplay(); break; @@ -2679,71 +2716,40 @@ bool command_event(enum event_command cmd, void *data) /* init netplay manually */ case CMD_EVENT_NETPLAY_INIT: { - char *hostname = (char*)data; - char *netplay_server = NULL; - unsigned netplay_port = 0; - - if (p_rarch->connect_host && !hostname) - { - struct string_list *addr_port = string_split(p_rarch->connect_host, "|"); - - if (addr_port && addr_port->size == 2) - { - char *tmp_netplay_server = addr_port->elems[0].data; - char *tmp_netplay_port = addr_port->elems[1].data; - - if ( !string_is_empty(tmp_netplay_server) - && !string_is_empty(tmp_netplay_port)) - { - netplay_port = strtoul(tmp_netplay_port, NULL, 10); - - if (netplay_port && netplay_port <= 0xFFFF) - { - netplay_server = strdup(tmp_netplay_server); - - /* This way we free netplay_server - as well when done. */ - free(p_rarch->connect_host); - - p_rarch->connect_host = netplay_server; - } - } - } - - string_list_free(addr_port); - } - - if (!netplay_server || !netplay_port) - { - netplay_server = settings->paths.netplay_server; - netplay_port = settings->uints.netplay_port; - } + char tmp_netplay_server[256]; + char tmp_netplay_session[sizeof(tmp_netplay_server)]; + char *netplay_server = NULL; + char *netplay_session = NULL; + unsigned netplay_port = 0; command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - if (!init_netplay( - NULL, - hostname - ? hostname - : netplay_server, netplay_port)) + tmp_netplay_server[0] = '\0'; + tmp_netplay_session[0] = '\0'; + if (netplay_decode_hostname(p_rarch->connect_host, + tmp_netplay_server, &netplay_port, tmp_netplay_session, + sizeof(tmp_netplay_server))) { - command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - - if (p_rarch->connect_host) - { - free(p_rarch->connect_host); - p_rarch->connect_host = NULL; - } - - return false; + netplay_server = tmp_netplay_server; + netplay_session = tmp_netplay_session; } - if (p_rarch->connect_host) { free(p_rarch->connect_host); p_rarch->connect_host = NULL; } + if (string_is_empty(netplay_server)) + netplay_server = settings->paths.netplay_server; + if (!netplay_port) + netplay_port = settings->uints.netplay_port; + + if (!init_netplay(netplay_server, netplay_port, netplay_session)) + { + command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); + return false; + } + /* Disable rewind & SRAM autosave if it was enabled * TODO/FIXME: Add a setting for these tweaks */ #ifdef HAVE_REWIND @@ -2757,34 +2763,29 @@ bool command_event(enum event_command cmd, void *data) /* Initialize netplay via lobby when content is loaded */ case CMD_EVENT_NETPLAY_INIT_DIRECT: { - /* buf is expected to be address|port */ - static struct string_list *hostname = NULL; - char *buf = (char *)data; - unsigned netplay_port = settings->uints.netplay_port; - - hostname = string_split(buf, "|"); + char netplay_server[256]; + char netplay_session[sizeof(netplay_server)]; + unsigned netplay_port = 0; command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - RARCH_LOG("[Netplay]: Connecting to %s:%d (direct)\n", - hostname->elems[0].data, !string_is_empty(hostname->elems[1].data) - ? atoi(hostname->elems[1].data) - : netplay_port); + netplay_server[0] = '\0'; + netplay_session[0] = '\0'; + netplay_decode_hostname((char*) data, netplay_server, + &netplay_port, netplay_session, sizeof(netplay_server)); - if (!init_netplay( - NULL, - hostname->elems[0].data, - !string_is_empty(hostname->elems[1].data) - ? atoi(hostname->elems[1].data) - : netplay_port)) + if (!netplay_port) + netplay_port = settings->uints.netplay_port; + + RARCH_LOG("[Netplay]: Connecting to %s|%d (direct)\n", + netplay_server, netplay_port); + + if (!init_netplay(netplay_server, netplay_port, netplay_session)) { command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - string_list_free(hostname); return false; } - string_list_free(hostname); - /* Disable rewind if it was enabled TODO/FIXME: Add a setting for these tweaks */ #ifdef HAVE_REWIND @@ -2798,33 +2799,29 @@ bool command_event(enum event_command cmd, void *data) /* init netplay via lobby when content is not loaded */ case CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED: { - static struct string_list *hostname = NULL; - /* buf is expected to be address|port */ - char *buf = (char *)data; - unsigned netplay_port = settings->uints.netplay_port; - - hostname = string_split(buf, "|"); + char netplay_server[256]; + char netplay_session[sizeof(netplay_server)]; + unsigned netplay_port = 0; command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - RARCH_LOG("[Netplay]: Connecting to %s:%d (deferred)\n", - hostname->elems[0].data, !string_is_empty(hostname->elems[1].data) - ? atoi(hostname->elems[1].data) - : netplay_port); + netplay_server[0] = '\0'; + netplay_session[0] = '\0'; + netplay_decode_hostname((char*) data, netplay_server, + &netplay_port, netplay_session, sizeof(netplay_server)); - if (!init_netplay_deferred( - hostname->elems[0].data, - !string_is_empty(hostname->elems[1].data) - ? atoi(hostname->elems[1].data) - : netplay_port)) + if (!netplay_port) + netplay_port = settings->uints.netplay_port; + + RARCH_LOG("[Netplay]: Connecting to %s|%d (deferred)\n", + netplay_server, netplay_port); + + if (!init_netplay_deferred(netplay_server, netplay_port, netplay_session)) { command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); - string_list_free(hostname); return false; } - string_list_free(hostname); - /* Disable rewind if it was enabled * TODO/FIXME: Add a setting for these tweaks */ #ifdef HAVE_REWIND @@ -2838,8 +2835,8 @@ bool command_event(enum event_command cmd, void *data) case CMD_EVENT_NETPLAY_ENABLE_HOST: { #ifdef HAVE_MENU - bool contentless = false; - bool is_inited = false; + bool contentless = false; + bool is_inited = false; content_get_status(&contentless, &is_inited); @@ -2905,7 +2902,10 @@ bool command_event(enum event_command cmd, void *data) case CMD_EVENT_NETPLAY_HOST_TOGGLE: case CMD_EVENT_NETPLAY_DISCONNECT: case CMD_EVENT_NETPLAY_ENABLE_HOST: + case CMD_EVENT_NETPLAY_PING_TOGGLE: case CMD_EVENT_NETPLAY_GAME_WATCH: + case CMD_EVENT_NETPLAY_PLAYER_CHAT: + case CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE: return false; #endif case CMD_EVENT_FULLSCREEN_TOGGLE: diff --git a/runloop.c b/runloop.c index d19e89df45..3f6a28f9ac 100644 --- a/runloop.c +++ b/runloop.c @@ -3095,7 +3095,13 @@ bool runloop_environment_cb(unsigned cmd, void *data) #ifdef HAVE_MENU menu_opened = menu_state_get_ptr()->alive; if (menu_opened) +#ifdef HAVE_NETWORKING + core_paused = settings->bools.menu_pause_libretro && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL); +#else core_paused = settings->bools.menu_pause_libretro; +#endif + #endif if (core_paused) @@ -6590,8 +6596,15 @@ static enum runloop_state_enum runloop_check_state( action = (enum menu_action)menu_event( settings, ¤t_bits, &trigger_input, display_kb); - focused = pause_nonactive ? is_focused : true; - focused = focused && !uico_st->is_on_foreground; +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)) + focused = true; + else +#endif + { + focused = pause_nonactive ? is_focused : true; + focused = focused && !uico_st->is_on_foreground; + } if (action == old_action) { @@ -6681,7 +6694,12 @@ static enum runloop_state_enum runloop_check_state( if (focused || !runloop_st->idle) { bool runloop_is_inited = runloop_st->is_inited; +#ifdef HAVE_NETWORKING + bool menu_pause_libretro = settings->bools.menu_pause_libretro && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL); +#else bool menu_pause_libretro = settings->bools.menu_pause_libretro; +#endif bool libretro_running = !menu_pause_libretro && runloop_is_inited && (runloop_st->current_core_type != CORE_TYPE_DUMMY); @@ -6824,6 +6842,9 @@ static enum runloop_state_enum runloop_check_state( } #endif +#ifdef HAVE_NETWORKING + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)) +#endif if (pause_nonactive) focused = is_focused; @@ -6857,7 +6878,10 @@ static enum runloop_state_enum runloop_check_state( #ifdef HAVE_NETWORKING /* Check Netplay */ + HOTKEY_CHECK(RARCH_NETPLAY_PING_TOGGLE, CMD_EVENT_NETPLAY_PING_TOGGLE, true, NULL); HOTKEY_CHECK(RARCH_NETPLAY_GAME_WATCH, CMD_EVENT_NETPLAY_GAME_WATCH, true, NULL); + HOTKEY_CHECK(RARCH_NETPLAY_PLAYER_CHAT, CMD_EVENT_NETPLAY_PLAYER_CHAT, true, NULL); + HOTKEY_CHECK(RARCH_NETPLAY_FADE_CHAT_TOGGLE, CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE, true, NULL); #endif /* Check if we have pressed the pause button */ @@ -7302,7 +7326,12 @@ int runloop_iterate(void) unsigned max_users = settings->uints.input_max_users; retro_time_t current_time = cpu_features_get_time_usec(); #ifdef HAVE_MENU +#ifdef HAVE_NETWORKING + bool menu_pause_libretro = settings->bools.menu_pause_libretro && + netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL); +#else bool menu_pause_libretro = settings->bools.menu_pause_libretro; +#endif bool core_paused = runloop_st->paused || (menu_pause_libretro && menu_state_get_ptr()->alive); #else bool core_paused = runloop_st->paused; diff --git a/tasks/task_netplay_lan_scan.c b/tasks/task_netplay_lan_scan.c index 42e96b86c0..fafe0272fb 100644 --- a/tasks/task_netplay_lan_scan.c +++ b/tasks/task_netplay_lan_scan.c @@ -55,7 +55,6 @@ bool task_push_netplay_lan_scan(retro_task_callback_t cb) task->type = TASK_TYPE_BLOCKING; task->handler = task_netplay_lan_scan_handler; task->callback = cb; - task->title = strdup(msg_hash_to_str(MSG_NETPLAY_LAN_SCANNING)); task_queue_push(task); diff --git a/ui/drivers/qt/qt_options.cpp b/ui/drivers/qt/qt_options.cpp index 117ce78472..200ab442b1 100644 --- a/ui/drivers/qt/qt_options.cpp +++ b/ui/drivers/qt/qt_options.cpp @@ -412,12 +412,14 @@ QWidget *NetplayPage::widget() checksLayout->add(MENU_ENUM_LABEL_NETPLAY_PUBLIC_ANNOUNCE); checksLayout->add(MENU_ENUM_LABEL_NETPLAY_START_AS_SPECTATOR); + checksLayout->add(MENU_ENUM_LABEL_NETPLAY_FADE_CHAT); checksLayout->add(MENU_ENUM_LABEL_NETPLAY_ALLOW_PAUSING); checksLayout->add(MENU_ENUM_LABEL_NETWORK_ON_DEMAND_THUMBNAILS); serverForm->add(MENU_ENUM_LABEL_NETPLAY_IP_ADDRESS); serverForm->add(MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT); serverForm->add(MENU_ENUM_LABEL_NETPLAY_MAX_CONNECTIONS); + serverForm->add(MENU_ENUM_LABEL_NETPLAY_MAX_PING); serverForm->add(MENU_ENUM_LABEL_NETPLAY_PASSWORD); serverForm->add(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD); serverForm->add(MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL); @@ -561,6 +563,7 @@ QWidget *NotificationsPage::widget() notificationsGroup->add(MENU_ENUM_LABEL_MEMORY_SHOW); notificationsGroup->add(MENU_ENUM_LABEL_MEMORY_UPDATE_INTERVAL); notificationsGroup->add(MENU_ENUM_LABEL_STATISTICS_SHOW); + notificationsGroup->add(MENU_ENUM_LABEL_NETPLAY_PING_SHOW); notificationsGroup->add(MENU_ENUM_LABEL_VIDEO_FONT_PATH); notificationsGroup->add(MENU_ENUM_LABEL_VIDEO_FONT_SIZE); notificationsGroup->add(MENU_ENUM_LABEL_VIDEO_MESSAGE_POS_X);