From 690c802921c563eb0954ebd02f5bc88d3a8a6bac Mon Sep 17 00:00:00 2001 From: Cthulhu-throwaway <96153783+Cthulhu-throwaway@users.noreply.github.com> Date: Sun, 19 Dec 2021 12:58:01 -0300 Subject: [PATCH] Netplay Stuff (#13375) * Netplay Stuff ## PROTOCOL FALLBACK In order to support older clients a protocol fallback system was introduced. The host will no longer send its header automatically after a TCP connection is established, instead, it awaits for the client to send his before determining which protocol this connection is going to operate on. Netplay has now two protocols, a low protocol and a high protocol; the low protocol is the minimum protocol it supports, while the high protocol is the highest protocol it can operate on. To fully support older clients, a hack was necessary: sending the high protocol in the unused client's header salt field, while keeping the protocol field to the low protocol. Without this hack we would only be able to support older clients if a newer client was the host. Any future system can make use of this system by checking connection->netplay_protocol, which is available for both the client and host. ## NETPLAY CHAT Starting with protocol 6, netplay chat is available through the new NETPLAY_CMD_PLAYER_CHAT command. Limitations of the command code, which causes a disconnection on unknown commands, makes this system not possible on protocol 5. Protocol 5 connections can neither send nor receive chat, but other netplay operations are unaffected. Clients send chat as a string to the server, and it's the server's sole responsability to relay chat messages. As of now, sending chat uses RetroArch's input menu, while the display of on-screen chat uses a widget overlay and RetroArch's notifications as a fallback. If a new overlay and/or input system is desired, no backwards compatibility changes need to be made. Only clients in playing mode (as opposed to spectating mode) can send and receive chat. ## SETTINGS SHARING Some settings are better used when both host and clients share the same configuration. As of protocol 6, the following settings will be shared from host to clients (without altering a client's configuration file): input latency frames and allow pausing. ## NETPLAY TUNNEL/MITM With the current MITM system being defunct (at least as of 1.9.X), a new system was in order to solve most if not all of the problems with the current system. This new system uses a tunneling approach, which is similar to most VPN and tunneling services around. Tunnel commands: RATS[unique id] (RetroArch Tunnel Session) - 16 bytes -> When this command is sent with a zeroed unique id, the tunnel server interprets this as a netplay host wanting to create a new session, in this case, the same command is returned to the host, but now with its unique session id. When a client needs to connect to a host, this command is sent with the unique session id of the host, causing the tunnel server to send a RATL command to the host. RATL[unique id] (RetroArch Tunnel Link) - 16 bytes -> The tunnel server sends this command to the host when a client wants to connect to the host. Once the host receives this command, it establishes a new connection to the tunnel server, sending this command together with the client's unique id through this new connection, causing the tunnel server to link this connection to the connection of the client. RATP (RetroArch Tunnel Ping) - 4 bytes -> The tunnel server sends this command to verify that the host, whom the session belongs to, is still around. The host replies with the same command. A session is closed if the tunnel server can not verify that the host is alive. Operations: Host -> Instead of listening and accepting connections, it connects to the tunnel server, requests a new session and then monitor this connection for new linking requests. Once a request is received, it establishes a new connection to the tunnel server for linking with a client. The tunnel server's address and port are obtained by querying the lobby server. The host will publish its session id together with the rest of its info to the lobby server. Client -> It connects to the tunnel server and then sends the session id of the host it wants to connect to. A host's session id is obtained from the json data sent by the lobby server. Improvements (from current MITM system): No longer a risk of TCP port exhaustion; we only use one port now at the tunnel server. Very little cpu usage. About 95% net I/O bound now. Future backwards compatible with any and all changes to netplay as it no longer runs any netplay logic at MITM servers. No longer operates the host in client mode, which was a source of many of the current problems. Cleaner and more maintainable system and code. Notable functions: netplay_mitm_query -> Grabs the tunnel's address and port from the lobby server. init_tcp_socket -> Handles the creation and operation mode of the TCP socket based on whether it's host, host+MITM or client. handle_mitm_connection -> Creates and completes linking connections and replies to ping commands (only 1 of each per call to not affect performance). ## MISC Ping Limiter: If a client's estimated latency to the server is higher than this value, connection will be dropped just before finishing the netplay handshake. Ping Counter: A ping counter (similar to the FPS one) can be shown in the bottom right corner of the screen, if you are connected to a host. LAN Discovery: Refactored and moved to its own "Refresh Netplay LAN List" button. ## FIXES Many minor fixes to the current netplay implementation are also included. * Remove NETPLAY_TEST_BUILD --- audio/audio_driver.c | 13 +- command.h | 6 + config.def.h | 13 +- config.def.keybinds.h | 63 + configuration.c | 6 + configuration.h | 3 + gfx/gfx_widgets.c | 4 + gfx/gfx_widgets.h | 5 + input/input_defines.h | 3 + intl/msg_hash_lbl.h | 16 + intl/msg_hash_us.h | 66 +- menu/cbs/menu_cbs_ok.c | 337 ++- menu/cbs/menu_cbs_sublabel.c | 28 + menu/drivers/materialui.c | 1 + menu/drivers/ozone.c | 4 + menu/drivers/xmb.c | 4 + menu/menu_displaylist.c | 145 +- menu/menu_driver.c | 9 + menu/menu_setting.c | 50 + msg_hash.h | 12 + network/discord.c | 14 +- network/netplay/netplay.h | 109 +- network/netplay/netplay_frontend.c | 3261 ++++++++++++++++++-------- network/netplay/netplay_private.h | 99 +- network/netplay/netplay_protocol.h | 27 + network/netplay/netplay_room_parse.c | 9 +- retroarch.c | 186 +- runloop.c | 33 +- tasks/task_netplay_lan_scan.c | 1 - ui/drivers/qt/qt_options.cpp | 3 + 30 files changed, 3091 insertions(+), 1439 deletions(-) create mode 100644 network/netplay/netplay_protocol.h 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);