diff --git a/Makefile.common b/Makefile.common
index 8245497b45..2851353841 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1122,11 +1122,14 @@ ifeq ($(HAVE_NETWORKING), 1)
# Netplay
DEFINES += -DHAVE_NETWORK_CMD
- OBJ += network/netplay/netplay_net.o \
- network/netplay/netplay_spectate.o \
- network/netplay/netplay_common.o \
- network/netplay/netplay_discovery.o \
- network/netplay/netplay.o
+ OBJ += network/netplay/netplay_delta.o \
+ network/netplay/netplay_frontend.o \
+ network/netplay/netplay_handshake.o \
+ network/netplay/netplay_init.o \
+ network/netplay/netplay_io.o \
+ network/netplay/netplay_sync.o \
+ network/netplay/netplay_discovery.o \
+ network/netplay/netplay_buf.o
# Retro Achievements (also depends on threads)
diff --git a/command.c b/command.c
index 6eefad0f02..510b3c5ab2 100644
--- a/command.c
+++ b/command.c
@@ -286,6 +286,7 @@ static const struct cmd_map map[] = {
{ "MUTE", RARCH_MUTE },
{ "OSK", RARCH_OSK },
{ "NETPLAY_FLIP", RARCH_NETPLAY_FLIP },
+ { "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH },
{ "SLOWMOTION", RARCH_SLOWMOTION },
{ "VOLUME_UP", RARCH_VOLUME_UP },
{ "VOLUME_DOWN", RARCH_VOLUME_DOWN },
@@ -1227,8 +1228,7 @@ static void command_event_load_auto_state(void)
global_t *global = global_get_ptr();
#ifdef HAVE_NETWORKING
- if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)
- && !settings->netplay.is_spectate)
+ if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
return;
#endif
@@ -2362,14 +2362,21 @@ bool command_event(enum event_command cmd, void *data)
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
#ifdef HAVE_NETWORKING
if (!init_netplay(
- settings->netplay.is_spectate, data, settings->netplay.server,
- settings->netplay.port))
+ data, settings->netplay.server,
+ settings->netplay.port,
+ settings->netplay.password,
+ settings->netplay.spectate_password))
return false;
#endif
break;
case CMD_EVENT_NETPLAY_FLIP_PLAYERS:
#ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, NULL);
+#endif
+ break;
+ case CMD_EVENT_NETPLAY_GAME_WATCH:
+#ifdef HAVE_NETWORKING
+ netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL);
#endif
break;
case CMD_EVENT_FULLSCREEN_TOGGLE:
diff --git a/command.h b/command.h
index 4c47cd5224..3131915d0a 100644
--- a/command.h
+++ b/command.h
@@ -173,6 +173,8 @@ enum event_command
CMD_EVENT_NETPLAY_DEINIT,
/* Flip netplay players. */
CMD_EVENT_NETPLAY_FLIP_PLAYERS,
+ /* Switch between netplay gaming and watching. */
+ CMD_EVENT_NETPLAY_GAME_WATCH,
/* Initializes BSV movie. */
CMD_EVENT_BSV_MOVIE_INIT,
/* Deinitializes BSV movie. */
diff --git a/config.def.h b/config.def.h
index cc7fd22b1f..78ac7d8191 100644
--- a/config.def.h
+++ b/config.def.h
@@ -794,10 +794,15 @@ static const bool pause_nonactive = true;
* It is measured in seconds. A value of 0 disables autosave. */
static const unsigned autosave_interval = 0;
+/* Netplay without savestates/rewind */
+static const bool netplay_stateless_mode = false;
+
/* When being client over netplay, use keybinds for
* user 1 rather than user 2. */
static const bool netplay_client_swap_input = true;
+static const bool netplay_nat_traversal = false;
+
static const unsigned netplay_delay_frames = 16;
static const unsigned netplay_check_frames = 30;
@@ -987,7 +992,8 @@ static const struct retro_keybind retro_keybinds_1[] = {
{ true, RARCH_SCREENSHOT, MENU_ENUM_LABEL_VALUE_INPUT_META_SCREENSHOT, RETROK_F8, NO_BTN, 0, AXIS_NONE },
{ true, RARCH_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE, RETROK_F9, NO_BTN, 0, AXIS_NONE },
{ true, RARCH_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, RETROK_F12, NO_BTN, 0, AXIS_NONE },
- { true, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, RETROK_i, NO_BTN, 0, AXIS_NONE },
+ { true, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, RETROK_UNKNOWN, NO_BTN, 0, AXIS_NONE },
+ { true, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH, RETROK_i, NO_BTN, 0, AXIS_NONE },
{ true, RARCH_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION, RETROK_e, NO_BTN, 0, AXIS_NONE },
{ true, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, RETROK_UNKNOWN, NO_BTN, 0, AXIS_NONE },
{ true, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, RETROK_KP_PLUS, NO_BTN, 0, AXIS_NONE },
diff --git a/configuration.c b/configuration.c
index 0f6ee0a82c..3b3499cfe3 100644
--- a/configuration.c
+++ b/configuration.c
@@ -607,6 +607,8 @@ static int populate_settings_path(settings_t *settings, struct config_path_setti
SETTING_PATH("core_updater_buildbot_assets_url", settings->network.buildbot_assets_url, false, NULL, true);
#ifdef HAVE_NETWORKING
SETTING_PATH("netplay_ip_address", settings->netplay.server, false, NULL, true);
+ SETTING_PATH("netplay_password", settings->netplay.password, false, NULL, true);
+ SETTING_PATH("netplay_spectate_password", settings->netplay.spectate_password, false, NULL, true);
#endif
SETTING_PATH("recording_output_directory",
global->record.output_dir, false, NULL, true);
@@ -716,6 +718,7 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti
SETTING_BOOL("all_users_control_menu", &settings->input.all_users_control_menu, true, all_users_control_menu, false);
SETTING_BOOL("menu_swap_ok_cancel_buttons", &settings->input.menu_swap_ok_cancel_buttons, true, menu_swap_ok_cancel_buttons, false);
#ifdef HAVE_NETWORKING
+ SETTING_BOOL("netplay_stateless_mode", &settings->netplay.stateless_mode, false, netplay_stateless_mode, false);
SETTING_BOOL("netplay_client_swap_input", &settings->netplay.swap_input, true, netplay_client_swap_input, false);
#endif
SETTING_BOOL("input_descriptor_label_show", &settings->input.input_descriptor_label_show, true, input_descriptor_label_show, false);
@@ -822,7 +825,6 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti
SETTING_BOOL("network_remote_enable", &settings->network_remote_enable, false, false /* TODO */, false);
#endif
#ifdef HAVE_NETWORKING
- SETTING_BOOL("netplay_spectator_mode_enable",&settings->netplay.is_spectate, false, false /* TODO */, false);
SETTING_BOOL("netplay_nat_traversal", &settings->netplay.nat_traversal, true, true, false);
#endif
SETTING_BOOL("block_sram_overwrite", &settings->block_sram_overwrite, true, block_sram_overwrite, false);
@@ -946,7 +948,6 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting
SETTING_INT("state_slot", (unsigned*)&settings->state_slot, false, 0 /* TODO */, false);
#ifdef HAVE_NETWORKING
SETTING_INT("netplay_ip_port", &settings->netplay.port, true, RARCH_DEFAULT_PORT, false);
- SETTING_INT("netplay_delay_frames", &settings->netplay.delay_frames, true, netplay_delay_frames, false);
SETTING_INT("netplay_check_frames", &settings->netplay.check_frames, true, netplay_check_frames, false);
#endif
#ifdef HAVE_LANGEXTRA
@@ -1807,13 +1808,6 @@ static bool config_load_file(const char *path, bool set_defaults,
if (!rarch_ctl(RARCH_CTL_IS_FORCE_FULLSCREEN, NULL))
CONFIG_GET_BOOL_BASE(conf, settings, video.fullscreen, "video_fullscreen");
-#ifdef HAVE_NETWORKING
- if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL))
- {
- CONFIG_GET_BOOL_BASE(conf, settings, netplay.is_spectate,
- "netplay_spectator_mode_enable");
- }
-#endif
#ifdef HAVE_NETWORKGAMEPAD
for (i = 0; i < MAX_USERS; i++)
{
@@ -1862,8 +1856,8 @@ static bool config_load_file(const char *path, bool set_defaults,
}
#ifdef HAVE_NETWORKING
- if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL))
- CONFIG_GET_INT_BASE(conf, settings, netplay.delay_frames, "netplay_delay_frames");
+ if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL))
+ CONFIG_GET_BOOL_BASE(conf, settings, netplay.stateless_mode, "netplay_stateless_mode");
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL))
CONFIG_GET_INT_BASE(conf, settings, netplay.check_frames, "netplay_check_frames");
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, NULL))
diff --git a/configuration.h b/configuration.h
index d4cc94cf91..8b77811b82 100644
--- a/configuration.h
+++ b/configuration.h
@@ -401,11 +401,12 @@ typedef struct settings
{
char server[255];
unsigned port;
- unsigned delay_frames;
- unsigned check_frames;
- bool is_spectate;
+ bool stateless_mode;
+ int check_frames;
bool swap_input;
bool nat_traversal;
+ char password[128];
+ char spectate_password[128];
} netplay;
#endif
diff --git a/griffin/griffin.c b/griffin/griffin.c
index ad093012d1..bd80e87312 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -881,11 +881,14 @@ THREAD
NETPLAY
============================================================ */
#ifdef HAVE_NETWORKING
-#include "../network/netplay/netplay_net.c"
-#include "../network/netplay/netplay_spectate.c"
-#include "../network/netplay/netplay_common.c"
+#include "../network/netplay/netplay_delta.c"
+#include "../network/netplay/netplay_frontend.c"
+#include "../network/netplay/netplay_handshake.c"
+#include "../network/netplay/netplay_init.c"
+#include "../network/netplay/netplay_io.c"
+#include "../network/netplay/netplay_sync.c"
#include "../network/netplay/netplay_discovery.c"
-#include "../network/netplay/netplay.c"
+#include "../network/netplay/netplay_buf.c"
#include "../libretro-common/net/net_compat.c"
#include "../libretro-common/net/net_socket.c"
#include "../libretro-common/net/net_http.c"
diff --git a/input/input_config.c b/input/input_config.c
index eb6861afcf..a978d0af36 100644
--- a/input/input_config.c
+++ b/input/input_config.c
@@ -125,7 +125,8 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = {
DECLARE_META_BIND(2, screenshot, RARCH_SCREENSHOT, MENU_ENUM_LABEL_VALUE_INPUT_META_SCREENSHOT),
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, netplay_flip_players, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP),
+ DECLARE_META_BIND(2, netplay_flip_players_1_2, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP),
+ DECLARE_META_BIND(2, netplay_game_watch, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH),
DECLARE_META_BIND(2, slowmotion, RARCH_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION),
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),
diff --git a/input/input_defines.h b/input/input_defines.h
index 9040d6df7b..c626512773 100644
--- a/input/input_defines.h
+++ b/input/input_defines.h
@@ -72,6 +72,7 @@ enum
RARCH_MUTE,
RARCH_OSK,
RARCH_NETPLAY_FLIP,
+ RARCH_NETPLAY_GAME_WATCH,
RARCH_SLOWMOTION,
RARCH_ENABLE_HOTKEY,
RARCH_VOLUME_UP,
diff --git a/intl/msg_hash_chs.c b/intl/msg_hash_chs.c
index d0af2f7528..5afbaa8a9e 100644
--- a/intl/msg_hash_chs.c
+++ b/intl/msg_hash_chs.c
@@ -3543,8 +3543,6 @@ const char *msg_hash_to_str_chs(enum msg_hash_enums msg)
return "Stopping movie record.";
case MSG_NETPLAY_FAILED:
return "Failed to initialize netplay.";
- case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
- return "Movie playback has started. Cannot start netplay.";
case MSG_NO_CONTENT_STARTING_DUMMY_CORE:
return "No content, starting dummy core.";
case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET:
diff --git a/intl/msg_hash_eo.h b/intl/msg_hash_eo.h
index 9d02ebdb47..192e07cdef 100644
--- a/intl/msg_hash_eo.h
+++ b/intl/msg_hash_eo.h
@@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.")
-MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
- "Movie playback has started. Cannot start netplay.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
diff --git a/intl/msg_hash_es.c b/intl/msg_hash_es.c
index c1ca78b1ed..24af507256 100644
--- a/intl/msg_hash_es.c
+++ b/intl/msg_hash_es.c
@@ -1418,8 +1418,6 @@ const char *msg_hash_to_str_es(enum msg_hash_enums msg)
return "Deteniendo grabación de vídeo.";
case MSG_NETPLAY_FAILED:
return "Error al iniciar el juego en red.";
- case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
- return "Se ha iniciado una reproducción. No se puede ejecutar el juego en red.";
case MSG_PAUSED:
return "En pausa.";
case MSG_PROGRAM:
diff --git a/intl/msg_hash_fr.h b/intl/msg_hash_fr.h
index 5bfa10339b..9b1eb68d15 100644
--- a/intl/msg_hash_fr.h
+++ b/intl/msg_hash_fr.h
@@ -1928,8 +1928,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Arrêt de l'enregistrement vidéo.")
MSG_HASH(MSG_NETPLAY_FAILED,
"Échec de l'initialisation du jeu en réseau")
-MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
- "Lecture en cours. Impossible d'activer le jeu en réseau.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h
index db08136d9d..14fc3b3dfc 100644
--- a/intl/msg_hash_ja.h
+++ b/intl/msg_hash_ja.h
@@ -1997,8 +1997,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.")
-MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
- "Movie playback has started. Cannot start netplay.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h
index 1e5e29a070..da4e6a5521 100644
--- a/intl/msg_hash_lbl.h
+++ b/intl/msg_hash_lbl.h
@@ -571,8 +571,6 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
"netplay_check_frames")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT,
"netplay_client_swap_input")
-MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
- "netplay_delay_frames")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DISCONNECT,
"menu_netplay_disconnect")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE,
@@ -589,10 +587,16 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
"netplay_nat_traversal")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NICKNAME,
"netplay_nickname")
+MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PASSWORD,
+ "netplay_password")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS,
"menu_netplay_settings")
+MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
+ "netplay_spectate_password")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
"netplay_spectator_mode_enable")
+MSG_HASH(MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
+ "netplay_stateless_mode")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT,
"netplay_tcp_udp_port")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_LAN_SCAN_SETTINGS,
diff --git a/intl/msg_hash_nl.h b/intl/msg_hash_nl.h
index 29b22cf2b7..aec4814f3f 100644
--- a/intl/msg_hash_nl.h
+++ b/intl/msg_hash_nl.h
@@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.")
-MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
- "Movie playback has started. Cannot start netplay.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
diff --git a/intl/msg_hash_pl.c b/intl/msg_hash_pl.c
index 6a25406182..dc708fa921 100644
--- a/intl/msg_hash_pl.c
+++ b/intl/msg_hash_pl.c
@@ -925,8 +925,6 @@ const char *msg_hash_to_str_pl(enum msg_hash_enums msg)
return "Zatrzymano nagrywanie filmu.";
case MSG_NETPLAY_FAILED:
return "Nie udało się zainicjalizować gry sieciowej.";
- case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
- return "Odtwarzanie filmu w toku. Nie można rozpocząć gry sieciowej.";
case MSG_PAUSED:
return "Wstrzymano.";
case MSG_PROGRAM:
diff --git a/intl/msg_hash_ru.h b/intl/msg_hash_ru.h
index 7d67b620a8..e181db67e1 100644
--- a/intl/msg_hash_ru.h
+++ b/intl/msg_hash_ru.h
@@ -1961,8 +1961,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Запись остановлена.")
MSG_HASH(MSG_NETPLAY_FAILED,
"Ошибка запуска сетевой игры.")
-MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
- "Воспроизведение записи. Невозможно начать сетевую игру.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c
index e364b1723f..aee30281a6 100644
--- a/intl/msg_hash_us.c
+++ b/intl/msg_hash_us.c
@@ -91,6 +91,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
snprintf(s, len,
"Netplay flip users.");
break;
+ case RARCH_NETPLAY_GAME_WATCH:
+ snprintf(s, len,
+ "Netplay toggle play/spectate mode.");
+ break;
case RARCH_SLOWMOTION:
snprintf(s, len,
"Hold for slowmotion.");
@@ -1557,6 +1561,15 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
"Increasing this value will increase \n"
"performance, but introduce more latency.");
break;
+ case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
+ snprintf(s, len,
+ "Whether to run netplay in a mode not requiring\n"
+ "save states. \n"
+ " \n"
+ "If set to true, a very fast network is required,\n"
+ "but no rewinding is performed, so there will be\n"
+ "no netplay jitter.\n");
+ break;
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
snprintf(s, len,
"The frequency in frames with which netplay \n"
@@ -1693,6 +1706,17 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
snprintf(s, len,
"The address of the host to connect to.");
break;
+ case MENU_ENUM_LABEL_NETPLAY_PASSWORD:
+ snprintf(s, len,
+ "The password for connecting to the netplay \n"
+ "host. Used only in host mode.");
+ break;
+ case MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD:
+ snprintf(s, len,
+ "The password for connecting to the netplay \n"
+ "host with only spectator privileges. Used \n"
+ "only in host mode.");
+ break;
case MENU_ENUM_LABEL_STDIN_CMD_ENABLE:
snprintf(s, len,
"Enable stdin command interface.");
@@ -1787,6 +1811,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
snprintf(s, len,
"Netplay flip users.");
break;
+ case MENU_ENUM_LABEL_NETPLAY_GAME_WATCH:
+ snprintf(s, len,
+ "Netplay toggle play/spectate mode.");
+ break;
case MENU_ENUM_LABEL_CHEAT_INDEX_PLUS:
snprintf(s, len,
"Increment cheat index.\n");
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index cb1271d5fd..8bebf08327 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -46,6 +46,66 @@ MSG_HASH(
MSG_WAITING_FOR_CLIENT,
"Waiting for client ..."
)
+MSG_HASH(
+ MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME,
+ "You have left the game"
+ )
+MSG_HASH(
+ MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N,
+ "You have joined as player %d"
+ )
+MSG_HASH(
+ MSG_NETPLAY_IMPLEMENTATIONS_DIFFER,
+ "Implementations differ. Make sure you're using the exact same versions of RetroArch and the core."
+ )
+MSG_HASH(
+ MSG_NETPLAY_ENDIAN_DEPENDENT,
+ "This core does not support inter-architecture netplay between these systems"
+ )
+MSG_HASH(
+ MSG_NETPLAY_PLATFORM_DEPENDENT,
+ "This core does not support inter-architecture netplay"
+ )
+MSG_HASH(
+ MSG_NETPLAY_ENTER_PASSWORD,
+ "Enter netplay server password:"
+ )
+MSG_HASH(
+ MSG_NETPLAY_INCORRECT_PASSWORD,
+ "Incorrect password"
+ )
+MSG_HASH(
+ MSG_NETPLAY_SERVER_NAMED_HANGUP,
+ "\"%s\" has disconnected"
+ )
+MSG_HASH(
+ MSG_NETPLAY_SERVER_HANGUP,
+ "A netplay client has disconnected"
+ )
+MSG_HASH(
+ MSG_NETPLAY_CLIENT_HANGUP,
+ "Netplay disconnected"
+ )
+MSG_HASH(
+ MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED,
+ "You do not have permission to play"
+ )
+MSG_HASH(
+ MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS,
+ "There are no free player slots"
+ )
+MSG_HASH(
+ MSG_NETPLAY_CANNOT_PLAY,
+ "Cannot switch to play mode"
+ )
+MSG_HASH(
+ MSG_NETPLAY_PEER_PAUSED,
+ "Netplay peer \"%s\" paused"
+ )
+MSG_HASH(
+ MSG_NETPLAY_CHANGED_NICK,
+ "Your nickname changed to \"%s\""
+ )
MSG_HASH(
MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT,
"Give hardware-rendered cores their own private context. Avoids having to assume hardware state changes inbetween frames."
@@ -714,6 +774,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE,
"Audio mute toggle")
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP,
"Netplay flip users")
+MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH,
+ "Netplay toggle play/spectate mode")
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OSK,
"On-screen keyboard toggle")
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OVERLAY_NEXT,
@@ -918,12 +980,18 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_MODE,
"Netplay Client Enable")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NICKNAME,
"Username")
+MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD,
+ "Server Password")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS,
"Netplay settings")
+MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_STATELESS_MODE,
+ "Netplay Stateless Mode")
+MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD,
+ "Server Spectate-Only Password")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE,
"Netplay Spectator Enable")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_TCP_UDP_PORT,
- "Netplay TCP/UDP Port")
+ "Netplay TCP Port")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL,
"Netplay NAT Traversal")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_ENABLE,
@@ -1986,8 +2054,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.")
-MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
- "Movie playback has started. Cannot start netplay.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
diff --git a/intl/msg_hash_vn.c b/intl/msg_hash_vn.c
index bf101e0281..410367850c 100644
--- a/intl/msg_hash_vn.c
+++ b/intl/msg_hash_vn.c
@@ -3591,8 +3591,6 @@ const char *msg_hash_to_str_vn(enum msg_hash_enums msg)
return "Stopping movie record.";
case MSG_NETPLAY_FAILED:
return "Failed to initialize netplay.";
- case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
- return "Movie playback has started. Cannot start netplay.";
case MSG_NO_CONTENT_STARTING_DUMMY_CORE:
return "No content, starting dummy core.";
case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET:
diff --git a/libretro-common/include/net/net_socket.h b/libretro-common/include/net/net_socket.h
index d2e73d33e4..3196e128c9 100644
--- a/libretro-common/include/net/net_socket.h
+++ b/libretro-common/include/net/net_socket.h
@@ -69,6 +69,9 @@ int socket_select(int nfds, fd_set *readfs, fd_set *writefds,
int socket_send_all_blocking(int fd, const void *data_, size_t size, bool no_signal);
+ssize_t socket_send_all_nonblocking(int fd, const void *data_, size_t size,
+ bool no_signal);
+
int socket_receive_all_blocking(int fd, void *data_, size_t size);
ssize_t socket_receive_all_nonblocking(int fd, bool *error,
diff --git a/libretro-common/net/net_socket.c b/libretro-common/net/net_socket.c
index 7715e2b41a..6d0cfdf700 100644
--- a/libretro-common/net/net_socket.c
+++ b/libretro-common/net/net_socket.c
@@ -78,7 +78,11 @@ ssize_t socket_receive_all_nonblocking(int fd, bool *error,
return ret;
if (ret == 0)
+ {
+ /* Socket closed */
+ *error = true;
return -1;
+ }
if (isagain(ret))
return 0;
@@ -179,6 +183,36 @@ int socket_send_all_blocking(int fd, const void *data_, size_t size,
return true;
}
+ssize_t socket_send_all_nonblocking(int fd, const void *data_, size_t size,
+ bool no_signal)
+{
+ const uint8_t *data = (const uint8_t*)data_;
+ ssize_t sent = 0;
+
+ while (size)
+ {
+ ssize_t ret = send(fd, (const char*)data, size,
+ no_signal ? MSG_NOSIGNAL : 0);
+ if (ret < 0)
+ {
+ if (isagain(ret))
+ break;
+
+ return -1;
+ }
+ else if (ret == 0)
+ {
+ break;
+ }
+
+ data += ret;
+ size -= ret;
+ sent += ret;
+ }
+
+ return sent;
+}
+
bool socket_bind(int fd, void *data)
{
int yes = 1;
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index 7492b4aa71..e056ff6a32 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -4690,25 +4690,29 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data)
PARSE_ONLY_UINT, false) != -1)
count++;
if (menu_displaylist_parse_settings_enum(menu, info,
- MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
- PARSE_ONLY_UINT, false) != -1)
+ MENU_ENUM_LABEL_NETPLAY_PASSWORD,
+ PARSE_ONLY_STRING, false) != -1)
+ count++;
+ if (menu_displaylist_parse_settings_enum(menu, info,
+ MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
+ PARSE_ONLY_STRING, false) != -1)
+ count++;
+ if (menu_displaylist_parse_settings_enum(menu, info,
+ MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
+ PARSE_ONLY_BOOL, false) != -1)
count++;
if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
- PARSE_ONLY_UINT, false) != -1)
+ PARSE_ONLY_INT, false) != -1)
count++;
if (menu_displaylist_parse_settings_enum(menu, info,
- MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
+ MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
PARSE_ONLY_BOOL, false) != -1)
count++;
if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT,
PARSE_ONLY_BOOL, false) != -1)
count++;
- if (menu_displaylist_parse_settings_enum(menu, info,
- MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
- PARSE_ONLY_BOOL, false) != -1)
- count++;
if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETWORK_CMD_ENABLE,
PARSE_ONLY_BOOL, false) != -1)
diff --git a/menu/menu_setting.c b/menu/menu_setting.c
index fa64c2c968..01c874b680 100644
--- a/menu/menu_setting.c
+++ b/menu/menu_setting.c
@@ -1721,34 +1721,16 @@ void general_write_handler(void *data)
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
#endif
break;
- case MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE:
+ case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
#ifdef HAVE_NETWORKING
- retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
-#endif
- break;
- case MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES:
-#ifdef HAVE_NETWORKING
- {
- bool val = (settings->netplay.delay_frames > 0);
-
- if (val)
- retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL);
- else
- retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL);
- }
+ retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL);
#endif
break;
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
#ifdef HAVE_NETWORKING
- {
- bool val = (settings->netplay.check_frames > 0);
-
- if (val)
- retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
- else
- retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
- }
+ retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
#endif
+ break;
default:
break;
}
@@ -5582,19 +5564,48 @@ static bool setting_append_list(
menu_settings_list_current_add_range(list, list_info, 0, 65535, 1, true, true);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
- CONFIG_UINT(
+ CONFIG_STRING(
list, list_info,
- &settings->netplay.delay_frames,
- MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
- MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES,
- netplay_delay_frames,
+ settings->netplay.password,
+ sizeof(settings->netplay.password),
+ MENU_ENUM_LABEL_NETPLAY_PASSWORD,
+ MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD,
+ "",
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
- menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false);
- settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
+ settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
+
+ CONFIG_STRING(
+ list, list_info,
+ settings->netplay.spectate_password,
+ sizeof(settings->netplay.spectate_password),
+ MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
+ MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD,
+ "",
+ &group_info,
+ &subgroup_info,
+ parent_group,
+ general_write_handler,
+ general_read_handler);
+ settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
+
+ CONFIG_BOOL(
+ list, list_info,
+ &settings->netplay.stateless_mode,
+ MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
+ MENU_ENUM_LABEL_VALUE_NETPLAY_STATELESS_MODE,
+ false,
+ 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_UINT(
list, list_info,
@@ -5607,24 +5618,9 @@ static bool setting_append_list(
parent_group,
general_write_handler,
general_read_handler);
- menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false);
+ menu_settings_list_current_add_range(list, list_info, -600, 600, 1, true, false);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
- CONFIG_BOOL(
- list, list_info,
- &settings->netplay.is_spectate,
- MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
- MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE,
- false,
- 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->netplay.nat_traversal,
diff --git a/msg_hash.h b/msg_hash.h
index 0ca9835b8a..5f080ca84d 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -140,12 +140,28 @@ enum msg_hash_enums
MSG_UNKNOWN = 0,
MSG_SETTING_DISK_IN_TRAY,
MSG_FAILED_TO_SET_DISK,
+ MSG_NETPLAY_FAILED,
MSG_NETPLAY_USERS_HAS_FLIPPED,
MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED,
MSG_CONNECTING_TO_NETPLAY_HOST,
MSG_NETPLAY_LAN_SCAN_COMPLETE,
MSG_NETPLAY_LAN_SCANNING,
MSG_WAITING_FOR_CLIENT,
+ MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME,
+ MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N,
+ MSG_NETPLAY_IMPLEMENTATIONS_DIFFER,
+ MSG_NETPLAY_ENDIAN_DEPENDENT,
+ MSG_NETPLAY_PLATFORM_DEPENDENT,
+ MSG_NETPLAY_ENTER_PASSWORD,
+ MSG_NETPLAY_INCORRECT_PASSWORD,
+ MSG_NETPLAY_SERVER_NAMED_HANGUP,
+ MSG_NETPLAY_SERVER_HANGUP,
+ MSG_NETPLAY_CLIENT_HANGUP,
+ MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED,
+ MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS,
+ MSG_NETPLAY_CANNOT_PLAY,
+ MSG_NETPLAY_PEER_PAUSED,
+ MSG_NETPLAY_CHANGED_NICK,
MSG_AUTODETECT,
MSG_AUDIO_VOLUME,
MSG_LIBRETRO_FRONTEND,
@@ -273,8 +289,6 @@ enum msg_hash_enums
MSG_REWIND_INIT_FAILED,
MSG_REWIND_INIT_FAILED_THREADED_AUDIO,
MSG_LIBRETRO_ABI_BREAK,
- MSG_NETPLAY_FAILED,
- MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
MSG_DETECTED_VIEWPORT_OF,
MSG_RECORDING_TO,
MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING,
@@ -530,6 +544,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE,
MENU_ENUM_LABEL_VALUE_INPUT_META_OSK,
MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH,
MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION,
MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY,
MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP,
@@ -940,6 +955,7 @@ enum msg_hash_enums
MENU_LABEL(UNDO_SAVE_STATE),
MENU_LABEL(NETPLAY_FLIP_PLAYERS),
+ MENU_LABEL(NETPLAY_GAME_WATCH),
MENU_LABEL(CHEAT_INDEX_MINUS),
MENU_LABEL(CHEAT_INDEX_PLUS),
MENU_LABEL(SHADER_NEXT),
@@ -994,6 +1010,7 @@ enum msg_hash_enums
MENU_LABEL(BLUETOOTH_ENABLE),
MENU_LABEL(NETPLAY_CLIENT_SWAP_INPUT),
MENU_LABEL(NETPLAY_DELAY_FRAMES),
+ MENU_LABEL(NETPLAY_STATELESS_MODE),
MENU_LABEL(NETPLAY_CHECK_FRAMES),
MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE),
MENU_LABEL(NETPLAY_TCP_UDP_PORT),
@@ -1001,6 +1018,8 @@ enum msg_hash_enums
MENU_LABEL(SORT_SAVEFILES_ENABLE),
MENU_LABEL(SORT_SAVESTATES_ENABLE),
MENU_LABEL(NETPLAY_IP_ADDRESS),
+ MENU_LABEL(NETPLAY_PASSWORD),
+ MENU_LABEL(NETPLAY_SPECTATE_PASSWORD),
MENU_LABEL(NETPLAY_MODE),
MENU_LABEL(PERFCNT_ENABLE),
MENU_LABEL(OVERLAY_SCALE),
diff --git a/network/netplay/README b/network/netplay/README
index e5a5384163..9617cfd8f7 100644
--- a/network/netplay/README
+++ b/network/netplay/README
@@ -9,11 +9,7 @@ minor constraints:
Furthermore, if the core supports serialization (save states), Netplay allows
for latency and clock drift, providing both host and client with a smooth
-experience.
-
-Note that this documentation is all for (the poorly-named) "net" mode, which is
-the normal mode, and not "spectator" mode, which has its own whole host of
-problems.
+experience, as well as the option of more than two players.
Netplay in RetroArch works by expecting input to come delayed from the network,
then rewinding and re-playing with the delayed input to get a consistent state.
@@ -24,32 +20,45 @@ correct frame.
In terms of the implementation, Netplay is in effect a state buffer
(implemented as a ring of buffers) and some pre- and post-frame behaviors.
-Within the state buffers, there are three locations: self, other and read. Each
-refers to a frame, and a state buffer corresponding to that frame. The state
-buffer contains the savestate for the frame, and the input from both the local
-and remote players.
+Within the state buffers, there are three locations: self, other and unread.
+Each refers to a frame, and a state buffer corresponding to that frame. The
+state buffer contains the savestate for the frame, and the input from both the
+local and remote players.
Self is where the emulator believes itself to be, which may be ahead or behind
of what it's read from the peer. Generally speaking, self progresses at 1 frame
-per frame, except when the network stalls, described later.
+per frame, but both fast-forwarding and stalling occur and are described later.
Other is where it was most recently in perfect sync: i.e., other-1 is the last
frame from which both local and remote input have been actioned. As such, other
is always less than or equal to both self and read. Since the state buffer is a
ring, other is the first frame that it's unsafe to overwrite.
-Read is where it's read up to, which can be slightly ahead of other since it
-can't always immediately act upon new data.
+Unread is the first frame at which not all players' data has been read, which
+can be slightly ahead of other since it can't always immediately act upon new
+data.
-In general, other ≤ read and other ≤ self. In all likelihood, read ≤ self, but
-it is both possible and supported for the remote host to get ahead of the local
-host.
+In general, other ≤ unread and other ≤ self. In all likelihood, unread ≤ self,
+but it is both possible and supported for the remote host to get ahead of the
+local host.
+
+The server has a slightly more complicated job as it can handle multiple
+clients, however it is not vastly more complicated: For each connection which
+is playing (i.e., has a controller), it maintains a per-player unread frame,
+and the global unread frame is the earliest of each player unread frame. The
+server forwards input data: When input data is received from an earlier frame
+than the server's current frame, it forwards it immediately. Otherwise, it
+forwards it when the frame is reached. i.e., during frame n, the server may
+send its own and any number of other players' data for frame n, but will never
+send frame n+1. This is because the server's clock is the arbiter of all
+synchronization-related events, such as flipping players, players joining and
+parting, and saving/loading states.
Pre-frame, Netplay serializes the core's state, polls for local input, and
-polls for input from the other side. If the input from the other side is too
-far behind, it stalls to allow the other side to catch up. To assure that this
-stalling does not block the UI thread, it is implemented similarly to pausing,
-rather than by blocking on the socket.
+polls for input from the network. If the input from the network is too far
+behind (i.e., unread is too far behind self), it stalls to allow the other side
+to catch up. To assure that this stalling does not block the UI thread, it is
+implemented similarly to pausing, rather than by blocking on the socket.
If input has not been received for the other side up to the current frame (the
usual case), the remote input is simulated in a simplistic manner. Each
@@ -64,6 +73,10 @@ other and self > other. If so, it first checks whether its simulated remote
data was correct. If it was, it simply moves other up. If not, it rewinds to
other (by loading the serialized state there) and runs the core in replay mode
with the real data up to the least of self and read, then sets other to that.
+To avoid latency building up, if the input from the network is too far ahead
+(i.e., unread is too far ahead of self), the frame limiter is momentarily
+disabled to catch up. Note that since network latency is expected, the normal
+case is the opposite: unread is behind self.
When in Netplay mode, the callback for receiving input is replaced by
input_state_net. It is the role of input_state_net to combine the true local
@@ -78,6 +91,35 @@ two relevant actions: Reading the data and emulating with the data. The frame
count is only incremented after the latter, so there is a period of time during
which we've actually read self_frame_count+1 frames of local input.
+Clients may come and go, and may start or stop playing even as they're
+connected. A client that is not playing is said to be “spectating”: It receives
+all the same data but sends none. A client may switch from spectating to
+playing by sending the appropriate request, at which point it is allotted a
+player number (see the SPECTATE, PLAY and MODE commands below).
+
+The server may also be in spectator mode, but as the server never sends data
+early (i.e., it only forwards data on the frame it's reached), it must also
+inform all clients of its own current frame even if it has no input. The
+NOINPUT command is provided for that purpose.
+
+The handshake procedure (this part is done by both server and client):
+1) Send connection header
+2) Receive and verify connection header
+3) Send nickname
+4) Receive nickname
+
+For the client:
+5) Send PASSWORD if applicable
+4) Receive INFO
+5) Send INFO
+6) Receive SYNC
+
+For the server:
+5) Receive PASSWORD if applicable
+6) Send INFO
+7) Receive INFO
+8) Send SYNC
+
* Guarantee not actually a guarantee.
@@ -100,32 +142,125 @@ Description:
Negative Acknowledgement. If received, the connection is terminated. Sent
whenever a command is malformed or otherwise not understood.
-Command: INPUT
-Payload:
- {
- frame number: uint32
- joypad input: uint32
- analog 1 input: uint32
- analog 2 input: uint32
- OPTIONAL state CRC: uint32
- }
-Description:
- Input state for each frame. Netplay must send an INPUT command for every
- frame in order to function at all.
-
-Command: FLIP_PLAYERS
-Payload:
- {
- frame number: uint32
- }
-Description:
- Flip players at the requested frame.
-
Command: DISCONNECT
Payload: None
Description:
Gracefully disconnect. Not used.
+Command: INPUT
+Payload:
+ {
+ frame number: uint32
+ is server data: 1 bit
+ player: 31 bits
+ joypad input: uint32
+ analog 1 input: uint32
+ analog 2 input: uint32
+ }
+Description:
+ Input state for each frame. Netplay must send an INPUT command for every
+ frame in order to function at all. Client's player value is ignored. Server
+ indicates which frames are its own input data because INPUT is a
+ synchronization point: No synchronization events from the given frame may
+ arrive after the server's input for the frame.
+
+Command: NOINPUT
+Payload:
+ {
+ frame number: uint32
+ }
+Description:
+ Sent by the server to indicate a frame has passed when the server is not
+ otherwise sending data.
+
+Command: NICK
+Payload:
+ {
+ nickname: char[32]
+ }
+Description:
+ Send nickname. Mandatory handshake command.
+
+Command: PASSWORD
+Payload:
+ {
+ password hash: char[64]
+ }
+Description:
+ Send hashed password to server. Mandatory handshake command for clients if
+ the server demands a password.
+
+Command: INFO
+Payload
+ {
+ core name: char[32]
+ core version: char[32]
+ content CRC: uint32
+ }
+Description:
+ Send core/content info. Mandatory handshake command. Sent by server first,
+ then by client, and must match. Server may send INFO with no payload, in
+ which case the client sends its own info and expects the server to load the
+ appropriate core and content then send a new INFO command. If mutual
+ agreement cannot be achieved, the correct solution is to simply disconnect.
+
+Command: SYNC
+Payload:
+ {
+ frame number: uint32
+ paused?: 1 bit
+ connected players: 31 bits
+ flip frame: uint32
+ controller devices: uint32[16]
+ client nick: char[32]
+ sram: variable
+ }
+Description:
+ Initial state synchronization. Mandatory handshake command from server to
+ client only. Connected players is a bitmap with the lowest bit being player
+ 0. Flip frame is 0 if players aren't flipped. Controller devices are the
+ devices plugged into each controller port, and 16 is really MAX_USERS.
+ Client is forced to have a different nick if multiple clients have the same
+ nick.
+
+Command: SPECTATE
+Payload: None
+Description:
+ Request to enter spectate mode. The client should immediately consider
+ itself to be in spectator mode and send no further input.
+
+Command: PLAY
+Payload: None
+Description:
+ Request to enter player mode. The client must wait for a MODE command
+ before sending input.
+
+Command: MODE
+Payload:
+ {
+ frame number: uint32
+ reserved: 14 bits
+ playing: 1 bit
+ you: 1 bit
+ player number: uint16
+ }
+Description:
+ Inform of a connection mode change (possibly of the receiving client). Only
+ server-to-client. Frame number is the first frame in which player data is
+ expected, or the first frame in which player data is not expected. In the
+ case of new players the frame number must be later than the last frame of
+ the server's own input that has been sent, and in the case of leaving
+ players the frame number must be later than the last frame of the relevant
+ player's input that has been transmitted.
+
+Command: MODE_REFUSED
+Payload:
+ {
+ reason: uint32
+ }
+Description:
+ Inform a client that its request to change modes has been refused.
+
Command: CRC
Payload:
{
@@ -155,9 +290,33 @@ Description:
serialized state is zlib compressed. Otherwise it is uncompressed.
Command: PAUSE
-Payload: None
+Payload:
+ {
+ nickname: char[32]
+ }
+Description:
Indicates that the core is paused. The receiving peer should also pause.
+ The server should pass it on, using the known correct name rather than the
+ provided name.
Command: RESUME
Payload: None
+Description:
Indicates that the core is no longer paused.
+
+Command: CHEATS
+Unused
+
+Command: FLIP_PLAYERS
+Payload:
+ {
+ frame number: uint32
+ }
+Description:
+ Flip players at the requested frame.
+
+Command: CFG
+Unused
+
+Command: CFG_ACK
+Unused
diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c
deleted file mode 100644
index 53ebcffed3..0000000000
--- a/network/netplay/netplay.c
+++ /dev/null
@@ -1,1856 +0,0 @@
-/* RetroArch - A frontend for libretro.
- * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
- * Copyright (C) 2011-2016 - Daniel De Matteis
- * Copyright (C) 2016 - Gregor Richards
- *
- * 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 .
- */
-
-#if defined(_MSC_VER) && !defined(_XBOX)
-#pragma comment(lib, "ws2_32")
-#endif
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "netplay_private.h"
-#include "netplay_discovery.h"
-
-#include "../../autosave.h"
-#include "../../configuration.h"
-#include "../../command.h"
-#include "../../movie.h"
-#include "../../runloop.h"
-
-#define MAX_STALL_TIME_USEC (10*1000*1000)
-#define MAX_RETRIES 16
-#define RETRY_MS 500
-
-#if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY)
-#define HAVE_INET6 1
-#endif
-
-enum
-{
- CMD_OPT_ALLOWED_IN_SPECTATE_MODE = 0x1,
- CMD_OPT_REQUIRE_ACK = 0x2,
- CMD_OPT_HOST_ONLY = 0x4,
- CMD_OPT_CLIENT_ONLY = 0x8,
- CMD_OPT_REQUIRE_SYNC = 0x10
-};
-
-/* Only used before init_netplay */
-static bool netplay_enabled = false;
-static bool netplay_is_client = false;
-
-/* Used while Netplay is running */
-static netplay_t *netplay_data = NULL;
-
-/* Used to avoid recursive netplay calls */
-static bool in_netplay = false;
-
-#ifndef HAVE_SOCKET_LEGACY
-static void announce_nat_traversal(netplay_t *netplay);
-#endif
-
-static int init_tcp_connection(const struct addrinfo *res,
- bool server, bool spectate,
- struct sockaddr *other_addr, socklen_t addr_size)
-{
- bool ret = true;
- int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
-
- if (fd < 0)
- {
- ret = false;
- goto end;
- }
-
-#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
- {
- int flag = 1;
- if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
-#ifdef _WIN32
- (const char*)
-#else
- (const void*)
-#endif
- &flag,
- sizeof(int)) < 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(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 (socket_connect(fd, (void*)res, false) < 0)
- {
- ret = false;
- goto end;
- }
- }
- else
- {
-#if defined(HAVE_INET6) && defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
- /* Make sure we accept connections on both IPv6 and IPv4 */
- int on = 0;
- if (res->ai_family == AF_INET6)
- {
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&on, sizeof(on)) < 0)
- RARCH_WARN("Failed to listen on both IPv6 and IPv4\n");
- }
-#endif
- if ( !socket_bind(fd, (void*)res) ||
- listen(fd, spectate ? MAX_SPECTATORS : 1) < 0)
- {
- ret = false;
- goto end;
- }
- }
-
-end:
- if (!ret && fd >= 0)
- {
- socket_close(fd);
- fd = -1;
- }
-
- return fd;
-}
-
-static bool init_tcp_socket(netplay_t *netplay, void *direct_host,
- const char *server, uint16_t port, bool spectate)
-{
- char port_buf[16];
- bool ret = false;
- const struct addrinfo *tmp_info = NULL;
- struct addrinfo *res = NULL;
- struct addrinfo hints = {0};
-
- port_buf[0] = '\0';
-
- if (!direct_host)
- {
-#ifdef HAVE_INET6
- /* Default to hosting on IPv6 and IPv4 */
- if (!server)
- hints.ai_family = AF_INET6;
-#endif
- hints.ai_socktype = SOCK_STREAM;
- if (!server)
- hints.ai_flags = AI_PASSIVE;
-
- snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
- if (getaddrinfo_retro(server, port_buf, &hints, &res) < 0)
- {
-#ifdef HAVE_INET6
- if (!server)
- {
- /* Didn't work with IPv6, try wildcard */
- hints.ai_family = 0;
- if (getaddrinfo_retro(server, port_buf, &hints, &res) < 0)
- return false;
- }
- else
-#endif
- return false;
- }
-
- if (!res)
- return false;
-
- }
- else
- {
- /* I'll build my own addrinfo! With blackjack and hookers! */
- 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;
-
- }
-
- /* If we're serving on IPv6, make sure we accept all connections, including
- * IPv4 */
-#ifdef HAVE_INET6
- if (!direct_host && !server && res->ai_family == AF_INET6)
- {
- struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) res->ai_addr;
- sin6->sin6_addr = in6addr_any;
- }
-#endif
-
- /* If "localhost" is used, it is important to check every possible
- * address for IPv4/IPv6. */
- tmp_info = res;
-
- while (tmp_info)
- {
- int fd = init_tcp_connection(
- tmp_info,
- direct_host || server,
- netplay->spectate.enabled,
- (struct sockaddr*)&netplay->other_addr,
- sizeof(netplay->other_addr));
-
- if (fd >= 0)
- {
- ret = true;
- netplay->fd = fd;
- break;
- }
-
- tmp_info = tmp_info->ai_next;
- }
-
- if (res && !direct_host)
- freeaddrinfo_retro(res);
-
- if (!ret)
- RARCH_ERR("Failed to set up netplay sockets.\n");
-
- return ret;
-}
-
-static void init_nat_traversal(netplay_t *netplay)
-{
- natt_init();
-
- if (!natt_new(&netplay->nat_traversal_state))
- {
- netplay->nat_traversal = false;
- return;
- }
-
- natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP);
-
-#ifndef HAVE_SOCKET_LEGACY
- if (!netplay->nat_traversal_state.request_outstanding)
- announce_nat_traversal(netplay);
-#endif
-}
-
-static bool init_socket(netplay_t *netplay, void *direct_host, const char *server, uint16_t port)
-{
- if (!network_init())
- return false;
-
- if (!init_tcp_socket(netplay, direct_host, server, port, netplay->spectate.enabled))
- return false;
-
- if (netplay->is_server && netplay->nat_traversal)
- init_nat_traversal(netplay);
-
- return true;
-}
-
-/**
- * hangup:
- *
- * Disconnects an active Netplay connection due to an error
- **/
-static void hangup(netplay_t *netplay)
-{
- if (!netplay)
- return;
- if (!netplay->has_connection)
- return;
-
- RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n");
- runloop_msg_queue_push("Netplay has disconnected. Will continue without connection.", 0, 480, false);
-
- socket_close(netplay->fd);
- netplay->fd = -1;
-
- if (netplay->is_server && !netplay->spectate.enabled)
- {
- /* In server mode, make the socket listen for a new connection */
- if (!init_socket(netplay, NULL, NULL, netplay->tcp_port))
- {
- RARCH_WARN("Failed to reinitialize Netplay.\n");
- runloop_msg_queue_push("Failed to reinitialize Netplay.", 0, 480, false);
- }
- }
-
- netplay->has_connection = false;
-
- /* Reset things that will behave oddly if we get a new connection */
- netplay->remote_paused = false;
- netplay->flip = false;
- netplay->flip_frame = 0;
- netplay->stall = 0;
-}
-
-static bool netplay_info_cb(netplay_t* netplay, unsigned delay_frames)
-{
- return netplay->net_cbs->info_cb(netplay, delay_frames);
-}
-
-/**
- * netplay_should_skip:
- * @netplay : pointer to netplay object
- *
- * If we're fast-forward replaying to resync, check if we
- * should actually show frame.
- *
- * Returns: bool (1) if we should skip this frame, otherwise
- * false (0).
- **/
-static bool netplay_should_skip(netplay_t *netplay)
-{
- if (!netplay)
- return false;
- return netplay->is_replay && netplay->has_connection;
-}
-
-static bool netplay_can_poll(netplay_t *netplay)
-{
- if (!netplay)
- return false;
- return netplay->can_poll;
-}
-
-/**
- * get_self_input_state:
- * @netplay : pointer to netplay object
- *
- * Grab our own input state and send this over the network.
- *
- * Returns: true (1) if successful, otherwise false (0).
- **/
-static bool get_self_input_state(netplay_t *netplay)
-{
- uint32_t state[WORDS_PER_FRAME - 1] = {0, 0, 0};
- struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr];
-
- if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count))
- return false;
-
- if (ptr->have_local)
- {
- /* We've already read this frame! */
- return true;
- }
-
- if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0)
- {
- unsigned i;
- settings_t *settings = config_get_ptr();
-
- /* First frame we always give zero input since relying on
- * input from first frame screws up when we use -F 0. */
- retro_input_state_t cb = netplay->cbs.state_cb;
- for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
- {
- int16_t tmp = cb(settings->netplay.swap_input ?
- 0 : !netplay->port,
- RETRO_DEVICE_JOYPAD, 0, i);
- state[0] |= tmp ? 1 << i : 0;
- }
-
- for (i = 0; i < 2; i++)
- {
- int16_t tmp_x = cb(settings->netplay.swap_input ?
- 0 : !netplay->port,
- RETRO_DEVICE_ANALOG, i, 0);
- int16_t tmp_y = cb(settings->netplay.swap_input ?
- 0 : !netplay->port,
- RETRO_DEVICE_ANALOG, i, 1);
- state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16);
- }
- }
-
- /* Here we construct the payload format:
- * frame {
- * uint32_t frame_number
- * uint32_t RETRO_DEVICE_JOYPAD state (top 16 bits zero)
- * uint32_t ANALOG state[0]
- * uint32_t ANALOG state[1]
- * }
- *
- * payload {
- * cmd (CMD_INPUT)
- * cmd_size (4 words)
- * frame
- * }
- */
- netplay->packet_buffer[0] = htonl(NETPLAY_CMD_INPUT);
- netplay->packet_buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t));
- netplay->packet_buffer[2] = htonl(netplay->self_frame_count);
- netplay->packet_buffer[3] = htonl(state[0]);
- netplay->packet_buffer[4] = htonl(state[1]);
- netplay->packet_buffer[5] = htonl(state[2]);
-
- if (!netplay->spectate.enabled) /* Spectate sends in its own way */
- {
- if (!socket_send_all_blocking(netplay->fd,
- netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
- {
- hangup(netplay);
- return false;
- }
- }
-
- memcpy(ptr->self_state, state, sizeof(state));
- ptr->have_local = true;
- return true;
-}
-
-static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd,
- const void *data, size_t size)
-{
- uint32_t cmdbuf[2];
-
- cmdbuf[0] = htonl(cmd);
- cmdbuf[1] = htonl(size);
-
- if (!socket_send_all_blocking(netplay->fd, cmdbuf, sizeof(cmdbuf), false))
- return false;
-
- if (size > 0)
- if (!socket_send_all_blocking(netplay->fd, data, size, false))
- return false;
-
- return true;
-}
-
-static bool netplay_cmd_nak(netplay_t *netplay)
-{
- return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0);
-}
-
-bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta)
-{
- uint32_t payload[2];
- payload[0] = htonl(delta->frame);
- payload[1] = htonl(delta->crc);
- return netplay_send_raw_cmd(netplay, NETPLAY_CMD_CRC, payload, sizeof(payload));
-}
-
-bool netplay_cmd_request_savestate(netplay_t *netplay)
-{
- if (netplay->savestate_request_outstanding)
- return true;
- netplay->savestate_request_outstanding = true;
- return netplay_send_raw_cmd(netplay, NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0);
-}
-
-static bool netplay_get_cmd(netplay_t *netplay)
-{
- uint32_t cmd;
- uint32_t flip_frame;
- uint32_t cmd_size;
-
- /* FIXME: This depends on delta_frame_ready */
-
- netplay->timeout_cnt = 0;
-
- if (!socket_receive_all_blocking(netplay->fd, &cmd, sizeof(cmd)))
- return false;
-
- cmd = ntohl(cmd);
-
- if (!socket_receive_all_blocking(netplay->fd, &cmd_size, sizeof(cmd)))
- return false;
-
- cmd_size = ntohl(cmd_size);
-
- switch (cmd)
- {
- case NETPLAY_CMD_ACK:
- /* Why are we even bothering? */
- return true;
-
- case NETPLAY_CMD_NAK:
- /* Disconnect now! */
- return false;
-
- case NETPLAY_CMD_INPUT:
- {
- uint32_t buffer[WORDS_PER_FRAME];
- unsigned i;
-
- if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t))
- {
- RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n");
- return netplay_cmd_nak(netplay);
- }
-
- if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer)))
- {
- RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n");
- return netplay_cmd_nak(netplay);
- }
-
- for (i = 0; i < WORDS_PER_FRAME; i++)
- buffer[i] = ntohl(buffer[i]);
-
- if (buffer[0] < netplay->read_frame_count)
- {
- /* We already had this, so ignore the new transmission */
- return true;
- }
- else if (buffer[0] > netplay->read_frame_count)
- {
- /* Out of order = out of luck */
- return netplay_cmd_nak(netplay);
- }
-
- /* The data's good! */
- netplay->buffer[netplay->read_ptr].have_remote = true;
- memcpy(netplay->buffer[netplay->read_ptr].real_input_state,
- buffer + 1, sizeof(buffer) - sizeof(uint32_t));
- netplay->read_ptr = NEXT_PTR(netplay->read_ptr);
- netplay->read_frame_count++;
- return true;
- }
-
- case NETPLAY_CMD_FLIP_PLAYERS:
- if (cmd_size != sizeof(uint32_t))
- {
- RARCH_ERR("CMD_FLIP_PLAYERS received an unexpected command size.\n");
- return netplay_cmd_nak(netplay);
- }
-
- if (!socket_receive_all_blocking(
- netplay->fd, &flip_frame, sizeof(flip_frame)))
- {
- RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n");
- return netplay_cmd_nak(netplay);
- }
-
- flip_frame = ntohl(flip_frame);
-
- if (flip_frame < netplay->read_frame_count)
- {
- RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n");
- return netplay_cmd_nak(netplay);
- }
-
- netplay->flip ^= true;
- netplay->flip_frame = flip_frame;
-
- /* Force a rewind to assure the flip happens: This just prevents us
- * from skipping other past the flip because our prediction was
- * correct */
- if (flip_frame < netplay->self_frame_count)
- netplay->force_rewind = true;
-
- RARCH_LOG("%s.\n", msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED));
- runloop_msg_queue_push(
- msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED), 1, 180, false);
-
- return true;
-
- case NETPLAY_CMD_SPECTATE:
- RARCH_ERR("NETPLAY_CMD_SPECTATE unimplemented.\n");
- return netplay_cmd_nak(netplay);
-
- case NETPLAY_CMD_DISCONNECT:
- hangup(netplay);
- return true;
-
- case NETPLAY_CMD_CRC:
- {
- uint32_t buffer[2];
- size_t tmp_ptr = netplay->self_ptr;
- bool found = false;
-
- if (cmd_size != sizeof(buffer))
- {
- RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n");
- return netplay_cmd_nak(netplay);
- }
-
- if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer)))
- {
- RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n");
- return netplay_cmd_nak(netplay);
- }
-
- buffer[0] = ntohl(buffer[0]);
- buffer[1] = ntohl(buffer[1]);
-
- /* Received a CRC for some frame. If we still have it, check if it
- * matched. This approach could be improved with some quick modular
- * arithmetic. */
- do
- {
- if ( netplay->buffer[tmp_ptr].used
- && netplay->buffer[tmp_ptr].frame == buffer[0])
- {
- found = true;
- break;
- }
-
- tmp_ptr = PREV_PTR(tmp_ptr);
- } while (tmp_ptr != netplay->self_ptr);
-
- if (!found)
- {
- /* Oh well, we got rid of it! */
- return true;
- }
-
- if (buffer[0] <= netplay->other_frame_count)
- {
- /* We've already replayed up to this frame, so we can check it
- * directly */
- uint32_t local_crc = netplay_delta_frame_crc(
- netplay, &netplay->buffer[tmp_ptr]);
-
- if (buffer[1] != local_crc)
- {
- /* Problem! */
- netplay_cmd_request_savestate(netplay);
- }
- }
- else
- {
- /* We'll have to check it when we catch up */
- netplay->buffer[tmp_ptr].crc = buffer[1];
- }
-
- return true;
- }
-
- case NETPLAY_CMD_REQUEST_SAVESTATE:
- /* Delay until next frame so we don't send the savestate after the
- * input */
- netplay->force_send_savestate = true;
- return true;
-
- case NETPLAY_CMD_LOAD_SAVESTATE:
- {
- uint32_t frame;
- uint32_t isize;
- uint32_t rd, wn;
-
- /* Make sure we're ready for it */
- if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
- {
- if (!netplay->is_replay)
- {
- netplay->is_replay = true;
- netplay->replay_ptr = netplay->self_ptr;
- netplay->replay_frame_count = netplay->self_frame_count;
- netplay_wait_and_init_serialization(netplay);
- netplay->is_replay = false;
- }
- else
- {
- netplay_wait_and_init_serialization(netplay);
- }
- }
-
- /* There is a subtlty in whether the load comes before or after the
- * current frame:
- *
- * If it comes before the current frame, then we need to force a
- * rewind to that point.
- *
- * If it comes after the current frame, we need to jump ahead, then
- * (strangely) force a rewind to the frame we're already on, so it
- * gets loaded. This is just to avoid having reloading implemented in
- * too many places. */
- if (cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t))
- {
- RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n");
- return netplay_cmd_nak(netplay);
- }
-
- if (!socket_receive_all_blocking(netplay->fd, &frame, sizeof(frame)))
- {
- RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n");
- return netplay_cmd_nak(netplay);
- }
- frame = ntohl(frame);
-
- if (frame != netplay->read_frame_count)
- {
- RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n");
- return netplay_cmd_nak(netplay);
- }
-
- if (!socket_receive_all_blocking(netplay->fd, &isize, sizeof(isize)))
- {
- RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n");
- return netplay_cmd_nak(netplay);
- }
- isize = ntohl(isize);
-
- if (isize != netplay->state_size)
- {
- RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n");
- return netplay_cmd_nak(netplay);
- }
-
- if (!socket_receive_all_blocking(netplay->fd,
- netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)))
- {
- RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n");
- return netplay_cmd_nak(netplay);
- }
-
- /* And decompress it */
- netplay->decompression_backend->set_in(netplay->decompression_stream,
- netplay->zbuffer, cmd_size - 2*sizeof(uint32_t));
- netplay->decompression_backend->set_out(netplay->decompression_stream,
- (uint8_t*)netplay->buffer[netplay->read_ptr].state, netplay->state_size);
- netplay->decompression_backend->trans(netplay->decompression_stream,
- true, &rd, &wn, NULL);
-
- /* Skip ahead if it's past where we are */
- if (frame > netplay->self_frame_count)
- {
- /* This is squirrely: We need to assure that when we advance the
- * frame in post_frame, THEN we're referring to the frame to
- * load into. If we refer directly to read_ptr, then we'll end
- * up never reading the input for read_frame_count itself, which
- * will make the other side unhappy. */
- netplay->self_ptr = PREV_PTR(netplay->read_ptr);
- netplay->self_frame_count = frame - 1;
- }
-
- /* And force rewind to it */
- netplay->force_rewind = true;
- netplay->savestate_request_outstanding = false;
- netplay->other_ptr = netplay->read_ptr;
- netplay->other_frame_count = frame;
- return true;
- }
-
- case NETPLAY_CMD_PAUSE:
- netplay->remote_paused = true;
- return true;
-
- case NETPLAY_CMD_RESUME:
- netplay->remote_paused = false;
- return true;
-
- default:
- break;
- }
-
- RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED));
- return netplay_cmd_nak(netplay);
-}
-
-static int poll_input(netplay_t *netplay, bool block)
-{
- bool had_input = false;
- int max_fd = netplay->fd + 1;
- struct timeval tv = {0};
- tv.tv_sec = 0;
- tv.tv_usec = block ? (RETRY_MS * 1000) : 0;
-
- do
- {
- fd_set fds;
- /* select() does not take pointer to const struct timeval.
- * Technically possible for select() to modify tmp_tv, so
- * we go paranoia mode. */
- struct timeval tmp_tv = tv;
- had_input = false;
-
- netplay->timeout_cnt++;
-
- FD_ZERO(&fds);
- FD_SET(netplay->fd, &fds);
-
- if (socket_select(max_fd, &fds, NULL, NULL, &tmp_tv) < 0)
- return -1;
-
- if (FD_ISSET(netplay->fd, &fds))
- {
- /* If we're not ready for input, wait until we are.
- * Could fill the TCP buffer, stalling the other side. */
- if (netplay_delta_frame_ready(netplay,
- &netplay->buffer[netplay->read_ptr],
- netplay->read_frame_count))
- {
- had_input = true;
- if (!netplay_get_cmd(netplay))
- return -1;
- }
- }
-
- /* If we were blocked for input, pass if we have this frame's input */
- if (block && netplay->read_frame_count > netplay->self_frame_count)
- break;
-
- /* If we had input, we might have more */
- if (had_input || !block)
- continue;
-
- RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n",
- netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES);
-
- if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused)
- return -1;
- } while (had_input || block);
-
- return 0;
-}
-
-/**
- * netplay_simulate_input:
- * @netplay : pointer to netplay object
- * @sim_ptr : frame index for which to simulate input
- * @resim : are we resimulating, or simulating this frame for the
- * first time?
- *
- * "Simulate" input by assuming it hasn't changed since the last read input.
- */
-void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim)
-{
- size_t prev = PREV_PTR(netplay->read_ptr);
- struct delta_frame *pframe = &netplay->buffer[prev],
- *simframe = &netplay->buffer[sim_ptr];
- if (resim)
- {
- /* In resimulation mode, we only copy the buttons. The reason for this
- * is nonobvious:
- *
- * If we resimulated nothing, then the /duration/ with which any input
- * was pressed would be approximately correct, since the original
- * simulation came in as the input came in, but the /number of times/
- * the input was pressed would be wrong, as there would be an
- * advancing wavefront of real data overtaking the simulated data
- * (which is really just real data offset by some frames).
- *
- * That's acceptable for arrows in most situations, since the amount
- * you move is tied to the duration, but unacceptable for buttons,
- * which will seem to jerkily be pressed numerous times with those
- * wavefronts.
- */
- const uint32_t keep = (1U<simulated_input_state[0] & keep;
- sim_state |= pframe->real_input_state[0] & ~keep;
- simframe->simulated_input_state[0] = sim_state;
- }
- else
- {
- memcpy(simframe->simulated_input_state,
- pframe->real_input_state,
- sizeof(pframe->real_input_state));
- }
-}
-
-
-/**
- * netplay_poll:
- * @netplay : pointer to netplay object
- *
- * Polls network to see if we have anything new. If our
- * network buffer is full, we simply have to block
- * for new input data.
- *
- * Returns: true (1) if successful, otherwise false (0).
- **/
-static bool netplay_poll(void)
-{
- int res;
-
- if (!netplay_data->has_connection)
- return false;
-
- netplay_data->can_poll = false;
-
- get_self_input_state(netplay_data);
-
- /* No network side in spectate mode */
- if (netplay_is_server(netplay_data) && netplay_data->spectate.enabled)
- return true;
-
- /* Read Netplay input, block if we're configured to stall for input every
- * frame */
- if (netplay_data->delay_frames == 0 &&
- netplay_data->read_frame_count <= netplay_data->self_frame_count)
- res = poll_input(netplay_data, true);
- else
- res = poll_input(netplay_data, false);
- if (res == -1)
- {
- hangup(netplay_data);
- return false;
- }
-
- /* Simulate the input if we don't have real input */
- if (!netplay_data->buffer[netplay_data->self_ptr].have_remote)
- netplay_simulate_input(netplay_data, netplay_data->self_ptr, false);
-
- /* Consider stalling */
- switch (netplay_data->stall)
- {
- case RARCH_NETPLAY_STALL_RUNNING_FAST:
- if (netplay_data->read_frame_count >= netplay_data->self_frame_count)
- netplay_data->stall = RARCH_NETPLAY_STALL_NONE;
- break;
-
- default: /* not stalling */
- if (netplay_data->read_frame_count + netplay_data->delay_frames
- <= netplay_data->self_frame_count)
- {
- netplay_data->stall = RARCH_NETPLAY_STALL_RUNNING_FAST;
- netplay_data->stall_time = cpu_features_get_time_usec();
- }
- }
-
- /* If we're stalling, consider disconnection */
- if (netplay_data->stall)
- {
- retro_time_t now = cpu_features_get_time_usec();
-
- /* Don't stall out while they're paused */
- if (netplay_data->remote_paused)
- netplay_data->stall_time = now;
- else if (now - netplay_data->stall_time >= MAX_STALL_TIME_USEC)
- {
- /* Stalled out! */
- hangup(netplay_data);
- return false;
- }
- }
-
- return true;
-}
-
-void input_poll_net(void)
-{
- if (!netplay_should_skip(netplay_data) && netplay_can_poll(netplay_data))
- netplay_poll();
-}
-
-void video_frame_net(const void *data, unsigned width,
- unsigned height, size_t pitch)
-{
- if (!netplay_should_skip(netplay_data))
- netplay_data->cbs.frame_cb(data, width, height, pitch);
-}
-
-void audio_sample_net(int16_t left, int16_t right)
-{
- if (!netplay_should_skip(netplay_data) && !netplay_data->stall)
- netplay_data->cbs.sample_cb(left, right);
-}
-
-size_t audio_sample_batch_net(const int16_t *data, size_t frames)
-{
- if (!netplay_should_skip(netplay_data) && !netplay_data->stall)
- return netplay_data->cbs.sample_batch_cb(data, frames);
- return frames;
-}
-
-/**
- * netplay_is_alive:
- * @netplay : pointer to netplay object
- *
- * Checks if input port/index is controlled by netplay or not.
- *
- * Returns: true (1) if alive, otherwise false (0).
- **/
-static bool netplay_is_alive(void)
-{
- if (!netplay_data)
- return false;
- return netplay_data->has_connection;
-}
-
-static bool netplay_flip_port(netplay_t *netplay, bool port)
-{
- size_t frame = netplay->self_frame_count;
-
- if (netplay->flip_frame == 0)
- return port;
-
- if (netplay->is_replay)
- frame = netplay->replay_frame_count;
-
- return port ^ netplay->flip ^ (frame < netplay->flip_frame);
-}
-
-static int16_t netplay_input_state(netplay_t *netplay,
- bool port, unsigned device,
- unsigned idx, unsigned id)
-{
- size_t ptr = netplay->is_replay ?
- netplay->replay_ptr : netplay->self_ptr;
-
- const uint32_t *curr_input_state = netplay->buffer[ptr].self_state;
-
- if (netplay->port == (netplay_flip_port(netplay, port) ? 1 : 0))
- {
- if (netplay->buffer[ptr].have_remote)
- {
- netplay->buffer[ptr].used_real = true;
- curr_input_state = netplay->buffer[ptr].real_input_state;
- }
- else
- {
- curr_input_state = netplay->buffer[ptr].simulated_input_state;
- }
- }
-
- switch (device)
- {
- case RETRO_DEVICE_JOYPAD:
- return ((1 << id) & curr_input_state[0]) ? 1 : 0;
-
- case RETRO_DEVICE_ANALOG:
- {
- uint32_t state = curr_input_state[1 + idx];
- return (int16_t)(uint16_t)(state >> (id * 16));
- }
-
- default:
- return 0;
- }
-}
-
-int16_t input_state_net(unsigned port, unsigned device,
- unsigned idx, unsigned id)
-{
- if (netplay_is_alive())
- {
- /* Only two players for now. */
- if (port > 1)
- return 0;
-
- return netplay_input_state(netplay_data, port, device, idx, id);
- }
- return netplay_data->cbs.state_cb(port, device, idx, id);
-}
-
-#ifndef HAVE_SOCKET_LEGACY
-/* Custom inet_ntop. Win32 doesn't seem to support this ... */
-void netplay_log_connection(const struct sockaddr_storage *their_addr,
- unsigned slot, const char *nick)
-{
- union
- {
- const struct sockaddr_storage *storage;
- const struct sockaddr_in *v4;
- const struct sockaddr_in6 *v6;
- } u;
- const char *str = NULL;
- char buf_v4[INET_ADDRSTRLEN] = {0};
- char buf_v6[INET6_ADDRSTRLEN] = {0};
- char msg[512];
-
- msg[0] = '\0';
-
- u.storage = their_addr;
-
- switch (their_addr->ss_family)
- {
- case AF_INET:
- {
- struct sockaddr_in in;
-
- memset(&in, 0, sizeof(in));
-
- str = buf_v4;
- in.sin_family = AF_INET;
- memcpy(&in.sin_addr, &u.v4->sin_addr, sizeof(struct in_addr));
-
- getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in),
- buf_v4, sizeof(buf_v4),
- NULL, 0, NI_NUMERICHOST);
- }
- break;
- case AF_INET6:
- {
- struct sockaddr_in6 in;
- memset(&in, 0, sizeof(in));
-
- str = buf_v6;
- in.sin6_family = AF_INET6;
- memcpy(&in.sin6_addr, &u.v6->sin6_addr, sizeof(struct in6_addr));
-
- getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
- buf_v6, sizeof(buf_v6), NULL, 0, NI_NUMERICHOST);
- }
- break;
- default:
- break;
- }
-
- if (str)
- {
- snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM_NAME),
- nick, str);
- runloop_msg_queue_push(msg, 1, 180, false);
- RARCH_LOG("%s\n", msg);
- }
- else
- {
- snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM),
- nick);
- runloop_msg_queue_push(msg, 1, 180, false);
- RARCH_LOG("%s\n", msg);
- }
- RARCH_LOG("%s %u\n", msg_hash_to_str(MSG_CONNECTION_SLOT),
- slot);
-}
-
-#else
-void netplay_log_connection(const struct sockaddr_storage *their_addr,
- unsigned slot, const char *nick)
-{
- char msg[512];
-
- msg[0] = '\0';
-
- snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM),
- nick);
- runloop_msg_queue_push(msg, 1, 180, false);
- RARCH_LOG("%s\n", msg);
- RARCH_LOG("%s %u\n",
- msg_hash_to_str(MSG_CONNECTION_SLOT), slot);
-}
-
-#endif
-
-#ifndef HAVE_SOCKET_LEGACY
-static void announce_nat_traversal(netplay_t *netplay)
-{
- char msg[512], host[PATH_MAX_LENGTH], port[6];
-
- if (netplay->nat_traversal_state.have_inet4)
- {
- if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr,
- sizeof(struct sockaddr_in),
- host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0)
- return;
-
- }
-#ifdef HAVE_INET6
- else if (netplay->nat_traversal_state.have_inet6)
- {
- if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr,
- sizeof(struct sockaddr_in6),
- host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0)
- return;
-
- }
-#endif
- else
- return;
-
- snprintf(msg, sizeof(msg), "%s: %s:%s\n",
- msg_hash_to_str(MSG_PUBLIC_ADDRESS),
- host, port);
- runloop_msg_queue_push(msg, 1, 180, false);
- RARCH_LOG("%s\n", msg);
-}
-#endif
-
-bool netplay_try_init_serialization(netplay_t *netplay)
-{
- retro_ctx_serialize_info_t serial_info;
-
- if (netplay->state_size)
- return true;
-
- if (!netplay_init_serialization(netplay))
- return false;
-
- /* Check if we can actually save */
- serial_info.data_const = NULL;
- serial_info.data = netplay->buffer[netplay->self_ptr].state;
- serial_info.size = netplay->state_size;
-
- if (!core_serialize(&serial_info))
- return false;
-
- /* Once initialized, we no longer exhibit this quirk */
- netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION);
-
- return true;
-}
-
-bool netplay_wait_and_init_serialization(netplay_t *netplay)
-{
- int frame;
-
- if (netplay->state_size)
- return true;
-
- /* Wait a maximum of 60 frames */
- for (frame = 0; frame < 60; frame++) {
- if (netplay_try_init_serialization(netplay))
- return true;
-
-#if defined(HAVE_THREADS)
- autosave_lock();
-#endif
- core_run();
-#if defined(HAVE_THREADS)
- autosave_unlock();
-#endif
- }
-
- return false;
-}
-
-bool netplay_init_serialization(netplay_t *netplay)
-{
- unsigned i;
- retro_ctx_size_info_t info;
-
- if (netplay->state_size)
- return true;
-
- core_serialize_size(&info);
-
- if (!info.size)
- return false;
-
- netplay->state_size = info.size;
-
- for (i = 0; i < netplay->buffer_size; i++)
- {
- netplay->buffer[i].state = calloc(netplay->state_size, 1);
-
- if (!netplay->buffer[i].state)
- {
- netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
- return false;
- }
- }
-
- netplay->zbuffer_size = netplay->state_size * 2;
- netplay->zbuffer = (uint8_t *) calloc(netplay->zbuffer_size, 1);
- if (!netplay->zbuffer)
- {
- netplay->quirks |= NETPLAY_QUIRK_NO_TRANSMISSION;
- netplay->zbuffer_size = 0;
- return false;
- }
-
- return true;
-}
-
-static bool netplay_init_buffers(netplay_t *netplay, unsigned frames)
-{
- if (!netplay)
- return false;
-
- /* * 2 + 1 because:
- * Self sits in the middle,
- * Other is allowed to drift as much as 'frames' frames behind
- * Read is allowed to drift as much as 'frames' frames ahead */
- netplay->buffer_size = frames * 2 + 1;
-
- netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size,
- sizeof(*netplay->buffer));
-
- if (!netplay->buffer)
- return false;
-
- if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION))
- netplay_init_serialization(netplay);
-
- return true;
-}
-
-/**
- * netplay_new:
- * @direct_host : Netplay host discovered from scanning.
- * @server : IP address of server.
- * @port : Port of server.
- * @delay_frames : Amount of delay frames.
- * @check_frames : Frequency with which to check CRCs.
- * @cb : Libretro callbacks.
- * @spectate : If true, enable spectator mode.
- * @nat_traversal : If true, attempt NAT traversal.
- * @nick : Nickname of user.
- * @quirks : Netplay quirks required for this session.
- *
- * Creates a new netplay handle. A NULL host means we're
- * hosting (user 1).
- *
- * Returns: new netplay handle.
- **/
-netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port,
- unsigned delay_frames, unsigned check_frames,
- const struct retro_callbacks *cb, bool spectate, bool nat_traversal,
- const char *nick, uint64_t quirks)
-{
- netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay));
- if (!netplay)
- return NULL;
-
- netplay->fd = -1;
- netplay->tcp_port = port;
- netplay->cbs = *cb;
- netplay->port = server ? 0 : 1;
- netplay->spectate.enabled = spectate;
- netplay->is_server = server == NULL;
- netplay->nat_traversal = netplay->is_server ? nat_traversal : false;
- netplay->delay_frames = delay_frames;
- netplay->check_frames = check_frames;
- netplay->quirks = quirks;
-
- strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick));
-
- if (!netplay_init_buffers(netplay, delay_frames))
- {
- free(netplay);
- return NULL;
- }
-
- if(spectate)
- netplay->net_cbs = netplay_get_cbs_spectate();
- else
- netplay->net_cbs = netplay_get_cbs_net();
-
- if (!init_socket(netplay, direct_host, server, port))
- {
- free(netplay);
- return NULL;
- }
-
- if(!netplay_info_cb(netplay, delay_frames))
- goto error;
-
- return netplay;
-
-error:
- if (netplay->fd >= 0)
- socket_close(netplay->fd);
-
- free(netplay);
- return NULL;
-}
-
-/**
- * netplay_command:
- * @netplay : pointer to netplay object
- * @cmd : command to send
- * @data : data to send as argument
- * @sz : size of data
- * @flags : flags of CMD_OPT_*
- * @command_str : name of action
- * @success_msg : message to display upon success
- *
- * Sends a single netplay command and waits for response.
- */
-bool netplay_command(netplay_t* netplay, enum netplay_cmd cmd,
- void* data, size_t sz,
- uint32_t flags,
- const char* command_str,
- const char* success_msg)
-{
- char m[256];
- const char* msg = NULL;
- bool allowed_spectate = !!(flags & CMD_OPT_ALLOWED_IN_SPECTATE_MODE);
- bool host_only = !!(flags & CMD_OPT_HOST_ONLY);
-
- retro_assert(netplay);
-
- if (netplay->spectate.enabled && !allowed_spectate)
- {
- msg = "Cannot %s in spectate mode.";
- goto error;
- }
-
- if (host_only && netplay->port == 0)
- {
- msg = "Cannot %s as a client.";
- goto error;
- }
-
- if (!netplay_send_raw_cmd(netplay, cmd, data, sz))
- goto error;
-
- runloop_msg_queue_push(success_msg, 1, 180, false);
-
- return true;
-
-error:
- if (msg)
- snprintf(m, sizeof(m), msg, command_str);
- RARCH_WARN("%s\n", m);
- runloop_msg_queue_push(m, 1, 180, false);
- return false;
-}
-
-/**
- * netplay_flip_users:
- * @netplay : pointer to netplay object
- *
- * On regular netplay, flip who controls user 1 and 2.
- **/
-static void netplay_flip_users(netplay_t *netplay)
-{
- /* Must be in the future because we may have
- * already sent this frame's data */
- uint32_t flip_frame = netplay->self_frame_count + 1;
- uint32_t flip_frame_net = htonl(flip_frame);
- bool command = netplay_command(
- netplay, NETPLAY_CMD_FLIP_PLAYERS,
- &flip_frame_net, sizeof flip_frame_net,
- CMD_OPT_HOST_ONLY | CMD_OPT_REQUIRE_SYNC,
- "flip users", "Successfully flipped users.\n");
-
- if(command)
- {
- netplay->flip ^= true;
- netplay->flip_frame = flip_frame;
- }
-}
-
-/**
- * netplay_free:
- * @netplay : pointer to netplay object
- *
- * Frees netplay handle.
- **/
-void netplay_free(netplay_t *netplay)
-{
- unsigned i;
-
- if (netplay->fd >= 0)
- socket_close(netplay->fd);
-
- if (netplay->spectate.enabled)
- {
- for (i = 0; i < MAX_SPECTATORS; i++)
- if (netplay->spectate.fds[i] >= 0)
- socket_close(netplay->spectate.fds[i]);
-
- free(netplay->spectate.input);
- }
-
- if (netplay->nat_traversal)
- natt_free(&netplay->nat_traversal_state);
-
- if (netplay->buffer)
- {
- for (i = 0; i < netplay->buffer_size; i++)
- if (netplay->buffer[i].state)
- free(netplay->buffer[i].state);
-
- free(netplay->buffer);
- }
-
- if (netplay->zbuffer)
- free(netplay->zbuffer);
-
- if (netplay->compression_stream)
- netplay->compression_backend->stream_free(netplay->compression_stream);
-
- if (netplay->addr)
- freeaddrinfo_retro(netplay->addr);
-
- free(netplay);
-}
-
-/**
- * netplay_pre_frame:
- * @netplay : pointer to netplay object
- *
- * Pre-frame for Netplay.
- * Call this before running retro_run().
- *
- * Returns: true (1) if the frontend is cleared to emulate the frame, false (0)
- * if we're stalled or paused
- **/
-bool netplay_pre_frame(netplay_t *netplay)
-{
- retro_assert(netplay && netplay->net_cbs->pre_frame);
-
- /* FIXME: This is an ugly way to learn we're not paused anymore */
- if (netplay->local_paused)
- netplay_frontend_paused(netplay, false);
-
- if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
- {
- /* Are we ready now? */
- netplay_try_init_serialization(netplay);
- }
-
- if (netplay->is_server)
- {
- /* Advertise our server */
- netplay_lan_ad_server(netplay);
-
- /* NAT traversal if applicable */
- if (netplay->nat_traversal &&
- netplay->nat_traversal_state.request_outstanding &&
- !netplay->nat_traversal_state.have_inet4)
- {
- struct timeval tmptv = {0};
- fd_set fds = netplay->nat_traversal_state.fds;
- if (socket_select(netplay->nat_traversal_state.nfds, &fds, NULL, NULL, &tmptv) > 0)
- natt_read(&netplay->nat_traversal_state);
-
-#ifndef HAVE_SOCKET_LEGACY
- if (!netplay->nat_traversal_state.request_outstanding ||
- netplay->nat_traversal_state.have_inet4)
- announce_nat_traversal(netplay);
-#endif
- }
- }
-
- if (!netplay->net_cbs->pre_frame(netplay))
- return false;
-
- return (!netplay->has_connection ||
- (!netplay->stall && !netplay->remote_paused));
-}
-
-/**
- * netplay_post_frame:
- * @netplay : pointer to netplay object
- *
- * Post-frame for Netplay.
- * We check if we have new input and replay from recorded input.
- * Call this after running retro_run().
- **/
-void netplay_post_frame(netplay_t *netplay)
-{
- retro_assert(netplay && netplay->net_cbs->post_frame);
- netplay->net_cbs->post_frame(netplay);
-}
-
-/**
- * netplay_frontend_paused
- * @netplay : pointer to netplay object
- * @paused : true if frontend is paused
- *
- * Inform Netplay of the frontend's pause state (paused or otherwise)
- **/
-void netplay_frontend_paused(netplay_t *netplay, bool paused)
-{
- /* Nothing to do if we already knew this */
- if (netplay->local_paused == paused)
- return;
-
- netplay->local_paused = paused;
- if (netplay->has_connection && !netplay->spectate.enabled)
- netplay_send_raw_cmd(netplay, paused
- ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0);
-}
-
-/**
- * netplay_load_savestate
- * @netplay : pointer to netplay object
- * @serial_info : the savestate being loaded, NULL means
- * "load it yourself"
- * @save : Whether to save the provided serial_info
- * into the frame buffer
- *
- * Inform Netplay of a savestate load and send it to the other side
- **/
-void netplay_load_savestate(netplay_t *netplay,
- retro_ctx_serialize_info_t *serial_info, bool save)
-{
- uint32_t header[4];
- retro_ctx_serialize_info_t tmp_serial_info;
- uint32_t rd, wn;
-
- if (!netplay->has_connection)
- return;
-
- /* Record it in our own buffer */
- if (save || !serial_info)
- {
- if (netplay_delta_frame_ready(netplay,
- &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
- {
- if (!serial_info)
- {
- tmp_serial_info.size = netplay->state_size;
- tmp_serial_info.data = netplay->buffer[netplay->self_ptr].state;
- if (!core_serialize(&tmp_serial_info))
- return;
- tmp_serial_info.data_const = tmp_serial_info.data;
- serial_info = &tmp_serial_info;
- }
- else
- {
- if (serial_info->size <= netplay->state_size)
- {
- memcpy(netplay->buffer[netplay->self_ptr].state,
- serial_info->data_const, serial_info->size);
- }
- }
- }
- else
- {
- /* FIXME: This is a critical failure! */
- return;
- }
- }
-
- /* We need to ignore any intervening data from the other side,
- * and never rewind past this */
- if (netplay->read_frame_count < netplay->self_frame_count)
- {
- netplay->read_ptr = netplay->self_ptr;
- netplay->read_frame_count = netplay->self_frame_count;
- }
- if (netplay->other_frame_count < netplay->self_frame_count)
- {
- netplay->other_ptr = netplay->self_ptr;
- netplay->other_frame_count = netplay->self_frame_count;
- }
-
- /* If we can't send it to the peer, loading a state was a bad idea */
- if (netplay->quirks & (
- NETPLAY_QUIRK_NO_SAVESTATES
- | NETPLAY_QUIRK_NO_TRANSMISSION))
- return;
-
- /* Compress it */
- netplay->compression_backend->set_in(netplay->compression_stream,
- (const uint8_t*)serial_info->data_const, serial_info->size);
- netplay->compression_backend->set_out(netplay->compression_stream,
- netplay->zbuffer, netplay->zbuffer_size);
- if (!netplay->compression_backend->trans(netplay->compression_stream,
- true, &rd, &wn, NULL))
- {
- hangup(netplay);
- return;
- }
-
- /* And send it to the peer */
- header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE);
- header[1] = htonl(wn + 2*sizeof(uint32_t));
- header[2] = htonl(netplay->self_frame_count);
- header[3] = htonl(serial_info->size);
-
- if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false))
- {
- hangup(netplay);
- return;
- }
-
- if (!socket_send_all_blocking(netplay->fd,
- netplay->zbuffer, wn, false))
- {
- hangup(netplay);
- return;
- }
-}
-
-/**
- * netplay_disconnect
- * @netplay : pointer to netplay object
- *
- * Disconnect netplay.
- *
- * Returns: true (1) if successful. At present, cannot fail.
- **/
-bool netplay_disconnect(netplay_t *netplay)
-{
- if (!netplay || !netplay->has_connection)
- return true;
- hangup(netplay);
- return true;
-}
-
-void deinit_netplay(void)
-{
- if (netplay_data)
- netplay_free(netplay_data);
- netplay_data = NULL;
- core_unset_netplay_callbacks();
-}
-
-/**
- * init_netplay:
- *
- * Initializes netplay.
- *
- * If netplay is already initialized, will return false (0).
- *
- * Returns: true (1) if successful, otherwise false (0).
- **/
-
-bool init_netplay(bool is_spectate, void *direct_host, const char *server, unsigned port)
-{
- struct retro_callbacks cbs = {0};
- settings_t *settings = config_get_ptr();
- uint64_t serialization_quirks = 0;
- uint64_t quirks = 0;
-
- if (!netplay_enabled)
- return false;
-
- if (bsv_movie_ctl(BSV_MOVIE_CTL_START_PLAYBACK, NULL))
- {
- RARCH_WARN("%s\n",
- msg_hash_to_str(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED));
- return false;
- }
-
- core_set_default_callbacks(&cbs);
- if (!core_set_netplay_callbacks())
- return false;
-
- /* Map the core's quirks to our quirks */
- serialization_quirks = core_serialization_quirks();
- if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD))
- {
- /* Quirks we don't support! Just disable everything. */
- quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
- }
- if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_SAVESTATES)
- quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
- if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_TRANSMISSION)
- quirks |= NETPLAY_QUIRK_NO_TRANSMISSION;
- if (serialization_quirks & NETPLAY_QUIRK_MAP_INITIALIZATION)
- quirks |= NETPLAY_QUIRK_INITIALIZATION;
- if (serialization_quirks & NETPLAY_QUIRK_MAP_ENDIAN_DEPENDENT)
- quirks |= NETPLAY_QUIRK_ENDIAN_DEPENDENT;
- if (serialization_quirks & NETPLAY_QUIRK_MAP_PLATFORM_DEPENDENT)
- quirks |= NETPLAY_QUIRK_PLATFORM_DEPENDENT;
-
- if (netplay_is_client)
- {
- RARCH_LOG("%s\n", msg_hash_to_str(MSG_CONNECTING_TO_NETPLAY_HOST));
- }
- else
- {
- RARCH_LOG("%s\n", msg_hash_to_str(MSG_WAITING_FOR_CLIENT));
- runloop_msg_queue_push(
- msg_hash_to_str(MSG_WAITING_FOR_CLIENT),
- 0, 180, false);
- }
-
- netplay_data = (netplay_t*)netplay_new(
- netplay_is_client ? direct_host : NULL,
- netplay_is_client ? server : NULL,
- port ? port : RARCH_DEFAULT_PORT,
- settings->netplay.delay_frames, settings->netplay.check_frames, &cbs,
- is_spectate, settings->netplay.nat_traversal, settings->username,
- quirks);
-
- if (netplay_data)
- return true;
-
- RARCH_WARN("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED));
-
- runloop_msg_queue_push(
- msg_hash_to_str(MSG_NETPLAY_FAILED),
- 0, 180, false);
- return false;
-}
-
-bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
-{
- bool ret = true;
-
- if (in_netplay)
- return true;
- in_netplay = true;
-
- if (!netplay_data)
- {
- switch (state)
- {
- case RARCH_NETPLAY_CTL_ENABLE_SERVER:
- netplay_enabled = true;
- netplay_is_client = false;
- goto done;
-
- case RARCH_NETPLAY_CTL_ENABLE_CLIENT:
- netplay_enabled = true;
- netplay_is_client = true;
- break;
-
- case RARCH_NETPLAY_CTL_DISABLE:
- netplay_enabled = false;
- goto done;
-
- case RARCH_NETPLAY_CTL_IS_ENABLED:
- ret = netplay_enabled;
- goto done;
-
- case RARCH_NETPLAY_CTL_IS_DATA_INITED:
- ret = false;
- goto done;
-
- default:
- goto done;
- }
- }
-
- switch (state)
- {
- case RARCH_NETPLAY_CTL_ENABLE_SERVER:
- case RARCH_NETPLAY_CTL_ENABLE_CLIENT:
- case RARCH_NETPLAY_CTL_IS_DATA_INITED:
- goto done;
- case RARCH_NETPLAY_CTL_DISABLE:
- netplay_enabled = false;
- deinit_netplay();
- goto done;
- case RARCH_NETPLAY_CTL_IS_ENABLED:
- goto done;
- case RARCH_NETPLAY_CTL_POST_FRAME:
- netplay_post_frame(netplay_data);
- break;
- case RARCH_NETPLAY_CTL_PRE_FRAME:
- ret = netplay_pre_frame(netplay_data);
- goto done;
- case RARCH_NETPLAY_CTL_FLIP_PLAYERS:
- {
- bool *state = (bool*)data;
- if (*state)
- netplay_flip_users(netplay_data);
- }
- break;
- case RARCH_NETPLAY_CTL_PAUSE:
- netplay_frontend_paused(netplay_data, true);
- break;
- case RARCH_NETPLAY_CTL_UNPAUSE:
- netplay_frontend_paused(netplay_data, false);
- break;
- case RARCH_NETPLAY_CTL_LOAD_SAVESTATE:
- netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true);
- break;
- case RARCH_NETPLAY_CTL_DISCONNECT:
- ret = netplay_disconnect(netplay_data);
- goto done;
- default:
- case RARCH_NETPLAY_CTL_NONE:
- ret = false;
- }
-
-done:
- in_netplay = false;
- return ret;
-}
diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h
index 69fce43df8..3d87d620da 100644
--- a/network/netplay/netplay.h
+++ b/network/netplay/netplay.h
@@ -33,6 +33,7 @@ enum rarch_netplay_ctl_state
{
RARCH_NETPLAY_CTL_NONE = 0,
RARCH_NETPLAY_CTL_FLIP_PLAYERS,
+ RARCH_NETPLAY_CTL_GAME_WATCH,
RARCH_NETPLAY_CTL_POST_FRAME,
RARCH_NETPLAY_CTL_PRE_FRAME,
RARCH_NETPLAY_CTL_ENABLE_SERVER,
@@ -46,80 +47,6 @@ enum rarch_netplay_ctl_state
RARCH_NETPLAY_CTL_DISCONNECT
};
-enum netplay_cmd
-{
- /* Basic commands */
-
- /* Acknowlegement response */
- NETPLAY_CMD_ACK = 0x0000,
-
- /* Failed acknowlegement response */
- NETPLAY_CMD_NAK = 0x0001,
-
- /* Input data */
- NETPLAY_CMD_INPUT = 0x0002,
-
- /* Misc. commands */
-
- /* Swap inputs between player 1 and player 2 */
- NETPLAY_CMD_FLIP_PLAYERS = 0x0003,
-
- /* Toggle spectate/join mode */
- NETPLAY_CMD_SPECTATE = 0x0004,
-
- /* Gracefully disconnects from host */
- NETPLAY_CMD_DISCONNECT = 0x0005,
-
- /* Sends multiple config requests over,
- * See enum netplay_cmd_cfg */
- NETPLAY_CMD_CFG = 0x0006,
-
- /* CMD_CFG streamlines sending multiple
- configurations. This acknowledges
- each one individually */
- NETPLAY_CMD_CFG_ACK = 0x0007,
-
- /* Loading and synchronization */
-
- /* Send the CRC hash of a frame's state */
- NETPLAY_CMD_CRC = 0x0010,
-
- /* Request a savestate */
- NETPLAY_CMD_REQUEST_SAVESTATE = 0x0011,
-
- /* Send a savestate for the client to load */
- NETPLAY_CMD_LOAD_SAVESTATE = 0x0012,
-
- /* Sends over cheats enabled on client */
- NETPLAY_CMD_CHEATS = 0x0013,
-
- /* Controlling game playback */
-
- /* Pauses the game, takes no arguments */
- NETPLAY_CMD_PAUSE = 0x0030,
-
- /* Resumes the game, takes no arguments */
- NETPLAY_CMD_RESUME = 0x0031
-};
-
-/* These are the configurations sent by NETPLAY_CMD_CFG. */
-enum netplay_cmd_cfg
-{
- /* Nickname */
- NETPLAY_CFG_NICK = 0x0001,
-
- /* input.netplay_client_swap_input */
- NETPLAY_CFG_SWAP_INPUT = 0x0002,
-
- /* netplay.delay_frames */
- NETPLAY_CFG_DELAY_FRAMES = 0x0004,
-
- /* For more than 2 players */
- NETPLAY_CFG_PLAYER_SLOT = 0x0008
-};
-
-void input_poll_net(void);
-
int16_t input_state_net(unsigned port, unsigned device,
unsigned idx, unsigned id);
@@ -130,93 +57,13 @@ void audio_sample_net(int16_t left, int16_t right);
size_t audio_sample_batch_net(const int16_t *data, size_t frames);
-/**
- * netplay_new:
- * @direct_host : Netplay host discovered from scanning.
- * @server : IP address of server.
- * @port : Port of server.
- * @delay_frames : Amount of delay frames.
- * @check_frames : Frequency with which to check CRCs.
- * @cb : Libretro callbacks.
- * @spectate : If true, enable spectator mode.
- * @nat_traversal : If true, attempt NAT traversal.
- * @nick : Nickname of user.
- * @quirks : Netplay quirks.
- *
- * Creates a new netplay handle. A NULL host means we're
- * hosting (user 1).
- *
- * Returns: new netplay handle.
- **/
-netplay_t *netplay_new(void *direct_host, const char *server,
- uint16_t port, unsigned delay_frames, unsigned check_frames,
- const struct retro_callbacks *cb, bool spectate, bool nat_traversal,
- const char *nick, uint64_t quirks);
-
-/**
- * netplay_free:
- * @netplay : pointer to netplay object
- *
- * Frees netplay handle.
- **/
-void netplay_free(netplay_t *handle);
-
-/**
- * netplay_pre_frame:
- * @netplay : pointer to netplay object
- *
- * Pre-frame for Netplay.
- * Call this before running retro_run().
- *
- * Returns: true (1) if the frontend is clear to emulate the frame, false (0)
- * if we're stalled or paused
- **/
-bool netplay_pre_frame(netplay_t *handle);
-
-/**
- * netplay_post_frame:
- * @netplay : pointer to netplay object
- *
- * Post-frame for Netplay.
- * We check if we have new input and replay from recorded input.
- * Call this after running retro_run().
- **/
-void netplay_post_frame(netplay_t *handle);
-
-/**
- * netplay_frontend_paused
- * @netplay : pointer to netplay object
- * @paused : true if frontend is paused
- *
- * Inform Netplay of the frontend's pause state (paused or otherwise)
- **/
-void netplay_frontend_paused(netplay_t *netplay, bool paused);
-
-/**
- * netplay_load_savestate
- * @netplay : pointer to netplay object
- * @serial_info : the savestate being loaded, NULL means "load it yourself"
- * @save : whether to save the provided serial_info into the frame buffer
- *
- * Inform Netplay of a savestate load and send it to the other side
- **/
-void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save);
-
-/**
- * netplay_disconnect
- * @netplay : pointer to netplay object
- *
- * Disconnect netplay.
- *
- * Returns: true (1) if successful. At present, cannot fail.
- **/
-bool netplay_disconnect(netplay_t *netplay);
-
/**
* init_netplay
- * @is_spectate : true if running in spectate mode
+ * @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
+ * @play_password : Password required to play (server only)
+ * @spectate_password : Password required to connect (server only)
*
* Initializes netplay.
*
@@ -224,7 +71,8 @@ bool netplay_disconnect(netplay_t *netplay);
*
* Returns: true (1) if successful, otherwise false (0).
**/
-bool init_netplay(bool is_spectate, void *direct_host, const char *server, unsigned port);
+bool init_netplay(void *direct_host, const char *server, unsigned port,
+ const char *play_password, const char *spectate_password);
void deinit_netplay(void);
diff --git a/network/netplay/netplay_buf.c b/network/netplay/netplay_buf.c
new file mode 100644
index 0000000000..6e18939eee
--- /dev/null
+++ b/network/netplay/netplay_buf.c
@@ -0,0 +1,357 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#include
+
+#include
+#include
+
+#include "netplay_private.h"
+
+static size_t buf_used(struct socket_buffer *sbuf)
+{
+ if (sbuf->end < sbuf->start)
+ {
+ size_t newend = sbuf->end;
+ while (newend < sbuf->start) newend += sbuf->bufsz;
+ return newend - sbuf->start;
+ }
+
+ return sbuf->end - sbuf->start;
+}
+
+static size_t buf_unread(struct socket_buffer *sbuf)
+{
+ if (sbuf->end < sbuf->read)
+ {
+ size_t newend = sbuf->end;
+ while (newend < sbuf->read) newend += sbuf->bufsz;
+ return newend - sbuf->read;
+ }
+
+ return sbuf->end - sbuf->read;
+}
+
+static size_t buf_remaining(struct socket_buffer *sbuf)
+{
+ return sbuf->bufsz - buf_used(sbuf) - 1;
+}
+
+/**
+ * netplay_init_socket_buffer
+ *
+ * Initialize a new socket buffer.
+ */
+bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size)
+{
+ sbuf->data = malloc(size);
+ if (sbuf->data == NULL)
+ return false;
+ sbuf->bufsz = size;
+ sbuf->start = sbuf->read = sbuf->end = 0;
+ return true;
+}
+
+/**
+ * netplay_resize_socket_buffer
+ *
+ * Resize the given socket_buffer's buffer to the requested size.
+ */
+bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize)
+{
+ unsigned char *newdata = malloc(newsize);
+ if (newdata == NULL)
+ return false;
+
+ /* Copy in the old data */
+ if (sbuf->end < sbuf->start)
+ {
+ memcpy(newdata, sbuf->data + sbuf->start, sbuf->bufsz - sbuf->start);
+ memcpy(newdata + sbuf->bufsz - sbuf->start, sbuf->data, sbuf->end);
+ }
+ else if (sbuf->end > sbuf->start)
+ {
+ memcpy(newdata, sbuf->data + sbuf->start, sbuf->end - sbuf->start);
+ }
+
+ /* Adjust our read offset */
+ if (sbuf->read < sbuf->start)
+ sbuf->read += sbuf->bufsz - sbuf->start;
+ else
+ sbuf->read -= sbuf->start;
+
+ /* Adjust start and end */
+ sbuf->end = buf_used(sbuf);
+ sbuf->start = 0;
+
+ /* Free the old one and replace it with the new one */
+ free(sbuf->data);
+ sbuf->data = newdata;
+ sbuf->bufsz = newsize;
+ return true;
+}
+
+/**
+ * netplay_deinit_socket_buffer
+ *
+ * Free a socket buffer.
+ */
+void netplay_deinit_socket_buffer(struct socket_buffer *sbuf)
+{
+ if (sbuf->data)
+ free(sbuf->data);
+}
+
+void netplay_clear_socket_buffer(struct socket_buffer *sbuf)
+{
+ sbuf->start = sbuf->read = sbuf->end = 0;
+}
+
+/**
+ * netplay_send
+ *
+ * Queue the given data for sending.
+ */
+bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf,
+ size_t len)
+{
+ if (buf_remaining(sbuf) < len)
+ {
+ /* Need to force a blocking send */
+ if (!netplay_send_flush(sbuf, sockfd, true))
+ return false;
+ }
+
+ if (buf_remaining(sbuf) < len)
+ {
+ /* 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))
+ return false;
+ return true;
+ }
+
+ /* Copy it into our buffer */
+ if (sbuf->bufsz - sbuf->end < len)
+ {
+ /* Half at a time */
+ size_t chunka = sbuf->bufsz - sbuf->end,
+ chunkb = len - chunka;
+ memcpy(sbuf->data + sbuf->end, buf, chunka);
+ memcpy(sbuf->data, (const unsigned char *) buf + chunka, chunkb);
+ sbuf->end = chunkb;
+
+ }
+ else
+ {
+ /* Straight in */
+ memcpy(sbuf->data + sbuf->end, buf, len);
+ sbuf->end += len;
+
+ }
+
+ /* Flush what we can immediately */
+ return netplay_send_flush(sbuf, sockfd, false);
+}
+
+/**
+ * netplay_send_flush
+ *
+ * Flush unsent data in the given socket buffer, blocking to do so if
+ * requested.
+ *
+ * Returns false only on socket failures, true otherwise.
+ */
+bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block)
+{
+ ssize_t sent;
+
+ if (buf_used(sbuf) == 0)
+ return true;
+
+ if (sbuf->end > sbuf->start)
+ {
+ /* Usual case: Everything's in order */
+ if (block)
+ {
+ if (!socket_send_all_blocking(sockfd, sbuf->data + sbuf->start, buf_used(sbuf), false))
+ return false;
+ sbuf->start = sbuf->end = 0;
+
+ }
+ else
+ {
+ sent = socket_send_all_nonblocking(sockfd, sbuf->data + sbuf->start, buf_used(sbuf), false);
+ if (sent < 0)
+ return false;
+ sbuf->start += sent;
+
+ if (sbuf->start == sbuf->end)
+ sbuf->start = sbuf->end = 0;
+
+ }
+
+ }
+ else
+ {
+ /* Unusual case: Buffer overlaps break */
+ if (block)
+ {
+ if (!socket_send_all_blocking(sockfd, sbuf->data + sbuf->start, sbuf->bufsz - sbuf->start, false))
+ return false;
+ sbuf->start = 0;
+ return netplay_send_flush(sbuf, sockfd, true);
+
+ }
+ else
+ {
+ sent = socket_send_all_nonblocking(sockfd, sbuf->data + sbuf->start, sbuf->bufsz - sbuf->start, false);
+ if (sent < 0)
+ return false;
+ sbuf->start += sent;
+
+ if (sbuf->start >= sbuf->bufsz)
+ {
+ sbuf->start = 0;
+ return netplay_send_flush(sbuf, sockfd, false);
+
+ }
+
+ }
+
+ }
+
+ return true;
+}
+
+/**
+ * netplay_recv
+ *
+ * Receive buffered or fresh data.
+ *
+ * Returns number of bytes returned, which may be short or 0, or -1 on error.
+ */
+ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf,
+ size_t len, bool block)
+{
+ bool error;
+ ssize_t recvd;
+
+ /* Receive whatever we can into the buffer */
+ if (sbuf->end >= sbuf->start)
+ {
+ error = false;
+ recvd = socket_receive_all_nonblocking(sockfd, &error,
+ sbuf->data + sbuf->end, sbuf->bufsz - sbuf->end -
+ ((sbuf->start == 0) ? 1 : 0));
+ if (recvd < 0 || error)
+ return -1;
+ sbuf->end += recvd;
+ if (sbuf->end >= sbuf->bufsz)
+ {
+ sbuf->end = 0;
+ error = false;
+ recvd = socket_receive_all_nonblocking(sockfd, &error, sbuf->data, sbuf->start - 1);
+ if (recvd < 0 || error)
+ return -1;
+ sbuf->end += recvd;
+
+ }
+
+ }
+ else
+ {
+ error = false;
+ recvd = socket_receive_all_nonblocking(sockfd, &error, sbuf->data + sbuf->end, sbuf->start - sbuf->end - 1);
+ if (recvd < 0 || error)
+ return -1;
+ sbuf->end += recvd;
+
+ }
+
+ /* Now copy it into the reader */
+ if (sbuf->end >= sbuf->read || (sbuf->bufsz - sbuf->read) >= len)
+ {
+ size_t unread = buf_unread(sbuf);
+ if (len <= unread)
+ {
+ memcpy(buf, sbuf->data + sbuf->read, len);
+ sbuf->read += len;
+ if (sbuf->read >= sbuf->bufsz)
+ sbuf->read = 0;
+ recvd = len;
+
+ }
+ else
+ {
+ memcpy(buf, sbuf->data + sbuf->read, unread);
+ sbuf->read += unread;
+ if (sbuf->read >= sbuf->bufsz)
+ sbuf->read = 0;
+ recvd = unread;
+
+ }
+
+ }
+ else
+ {
+ /* Our read goes around the edge */
+ size_t chunka = sbuf->bufsz - sbuf->read,
+ pchunklen = len - chunka,
+ chunkb = (pchunklen >= sbuf->end) ? sbuf->end : pchunklen;
+ memcpy(buf, sbuf->data + sbuf->read, chunka);
+ memcpy((unsigned char *) buf + chunka, sbuf->data, chunkb);
+ sbuf->read = chunkb;
+ recvd = chunka + chunkb;
+
+ }
+
+ /* Perhaps block for more data */
+ if (block)
+ {
+ sbuf->start = sbuf->read;
+ if (recvd < len)
+ {
+ if (!socket_receive_all_blocking(sockfd, (unsigned char *) buf + recvd, len - recvd))
+ return -1;
+ recvd = len;
+
+ }
+ }
+
+ return recvd;
+}
+
+/**
+ * netplay_recv_reset
+ *
+ * Reset our recv buffer so that future netplay_recvs will read the same data
+ * again.
+ */
+void netplay_recv_reset(struct socket_buffer *sbuf)
+{
+ sbuf->read = sbuf->start;
+}
+
+/**
+ * netplay_recv_flush
+ *
+ * Flush our recv buffer, so a future netplay_recv_reset will reset to this
+ * point.
+ */
+void netplay_recv_flush(struct socket_buffer *sbuf)
+{
+ sbuf->start = sbuf->read;
+}
diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c
deleted file mode 100644
index 73e1632886..0000000000
--- a/network/netplay/netplay_common.c
+++ /dev/null
@@ -1,435 +0,0 @@
-/* RetroArch - A frontend for libretro.
- * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
- * Copyright (C) 2011-2016 - Daniel De Matteis
- * Copyright (C) 2016 - Gregor Richards
- *
- * 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 .
- */
-
-#include
-
-#include "netplay_private.h"
-#include
-#include
-
-#include
-
-#include "../../movie.h"
-#include "../../msg_hash.h"
-#include "../../content.h"
-#include "../../runloop.h"
-#include "../../version.h"
-
-bool netplay_get_nickname(netplay_t *netplay, int fd)
-{
- uint8_t nick_size;
-
- if (!socket_receive_all_blocking(fd, &nick_size, sizeof(nick_size)))
- {
- RARCH_ERR("%s\n",
- msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_SIZE_FROM_HOST));
- return false;
- }
-
- if (nick_size >= sizeof(netplay->other_nick))
- {
- RARCH_ERR("%s\n",
- msg_hash_to_str(MSG_INVALID_NICKNAME_SIZE));
- return false;
- }
-
- if (!socket_receive_all_blocking(fd, netplay->other_nick, nick_size))
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME));
- return false;
- }
-
- return true;
-}
-bool netplay_send_nickname(netplay_t *netplay, int fd)
-{
- uint8_t nick_size = strlen(netplay->nick);
-
- if (!socket_send_all_blocking(fd, &nick_size, sizeof(nick_size), false))
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_SIZE));
- return false;
- }
-
- if (!socket_send_all_blocking(fd, netplay->nick, nick_size, false))
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME));
- return false;
- }
-
- return true;
-}
-
-/**
- * netplay_impl_magic:
- *
- * Not really a hash, but should be enough to differentiate
- * implementations from each other.
- *
- * Subtle differences in the implementation will not be possible to spot.
- * The alternative would have been checking serialization sizes, but it
- * was troublesome for cross platform compat.
- **/
-uint32_t netplay_impl_magic(void)
-{
- size_t i, len;
- retro_ctx_api_info_t api_info;
- unsigned api;
- uint32_t res = 0;
- rarch_system_info_t *info = NULL;
- const char *lib = NULL;
- const char *ver = PACKAGE_VERSION;
-
- core_api_version(&api_info);
-
- api = api_info.version;
-
- runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
-
- res |= api;
-
- if (info)
- {
- lib = info->info.library_name;
-
- len = strlen(lib);
- for (i = 0; i < len; i++)
- res ^= lib[i] << (i & 0xf);
-
- lib = info->info.library_version;
- len = strlen(lib);
-
- for (i = 0; i < len; i++)
- res ^= lib[i] << (i & 0xf);
- }
-
- len = strlen(ver);
- for (i = 0; i < len; i++)
- res ^= ver[i] << ((i & 0xf) + 16);
-
- res ^= NETPLAY_PROTOCOL_VERSION << 24;
-
- return res;
-}
-
-/**
- * netplay_platform_magic
- *
- * Just enough info to tell us if our platforms mismatch: Endianness and a
- * couple of type sizes.
- *
- * Format:
- * bit 31: Reserved
- * bit 30: 1 for big endian
- * bits 29-15: sizeof(size_t)
- * bits 14-0: sizeof(long)
- */
-static uint32_t netplay_platform_magic(void)
-{
- uint32_t ret =
- ((1 == htonl(1)) << 30)
- |(sizeof(size_t) << 15)
- |(sizeof(long));
- return ret;
-}
-
-/**
- * netplay_endian_mismatch
- *
- * Do the platform magics mismatch on endianness?
- */
-static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb)
-{
- uint32_t ebit = (1<<30);
- return (pma & ebit) != (pmb & ebit);
-}
-
-bool netplay_handshake(netplay_t *netplay)
-{
- size_t i;
- uint32_t local_pmagic, remote_pmagic;
- unsigned sram_size, remote_sram_size;
- retro_ctx_memory_info_t mem_info;
- char msg[512];
- uint32_t *content_crc_ptr = NULL;
- void *sram = NULL;
- uint32_t header[5] = {0};
- bool is_server = netplay->is_server;
- int compression = 0;
-
- msg[0] = '\0';
-
- mem_info.id = RETRO_MEMORY_SAVE_RAM;
-
- core_get_memory(&mem_info);
- content_get_crc(&content_crc_ptr);
-
- local_pmagic = netplay_platform_magic();
-
- header[0] = htonl(*content_crc_ptr);
- header[1] = htonl(netplay_impl_magic());
- header[2] = htonl(mem_info.size);
- header[3] = htonl(local_pmagic);
- header[4] = htonl(NETPLAY_COMPRESSION_SUPPORTED);
-
- if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false))
- return false;
-
- if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header)))
- {
- strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT), sizeof(msg));
- goto error;
- }
-
- if (*content_crc_ptr != ntohl(header[0]))
- {
- strlcpy(msg, msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER), sizeof(msg));
- goto error;
- }
-
- if (netplay_impl_magic() != ntohl(header[1]))
- {
- strlcpy(msg, "Implementations differ. Make sure you're using exact same "
- "libretro implementations and RetroArch version.", sizeof(msg));
- goto error;
- }
-
- /* Some cores only report the correct sram size late, so we can't actually
- * error out if the sram size seems wrong. */
- sram_size = mem_info.size;
- remote_sram_size = ntohl(header[2]);
- if (sram_size != 0 && remote_sram_size != 0 && sram_size != remote_sram_size)
- {
- RARCH_WARN("Content SRAM sizes do not correspond.\n");
- }
-
- /* We only care about platform magic if our core is quirky */
- remote_pmagic = ntohl(header[3]);
- if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) &&
- netplay_endian_mismatch(local_pmagic, remote_pmagic))
- {
- RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n");
- strlcpy(msg, "This core does not support inter-architecture netplay "
- "between these systems.", sizeof(msg));
- goto error;
- }
- if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) &&
- (local_pmagic != remote_pmagic))
- {
- RARCH_ERR("Platform mismatch with a platform-sensitive core.\n");
- strlcpy(msg, "This core does not support inter-architecture netplay.",
- sizeof(msg));
- goto error;
- }
-
- /* Clear any existing compression */
- if (netplay->compression_stream)
- netplay->compression_backend->stream_free(netplay->compression_stream);
- if (netplay->decompression_stream)
- netplay->decompression_backend->stream_free(netplay->decompression_stream);
-
- /* Check what compression is supported */
- compression = ntohl(header[4]);
- compression &= NETPLAY_COMPRESSION_SUPPORTED;
- if (compression & NETPLAY_COMPRESSION_ZLIB)
- {
- netplay->compression_backend = trans_stream_get_zlib_deflate_backend();
- if (!netplay->compression_backend)
- netplay->compression_backend = trans_stream_get_pipe_backend();
- }
- else
- {
- netplay->compression_backend = trans_stream_get_pipe_backend();
- }
- netplay->decompression_backend = netplay->compression_backend->reverse;
-
- /* Allocate our compression stream */
- netplay->compression_stream = netplay->compression_backend->stream_new();
- netplay->decompression_stream = netplay->decompression_backend->stream_new();
- if (!netplay->compression_stream || !netplay->decompression_stream)
- {
- RARCH_ERR("Failed to allocate compression transcoder!\n");
- return false;
- }
-
- /* Client sends nickname first, server replies with nickname */
- if (!is_server)
- {
- if (!netplay_send_nickname(netplay, netplay->fd))
- {
- strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_HOST),
- sizeof(msg));
- goto error;
- }
- }
-
- if (!netplay_get_nickname(netplay, netplay->fd))
- {
- if (is_server)
- strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT),
- sizeof(msg));
- else
- strlcpy(msg,
- msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST),
- sizeof(msg));
- goto error;
- }
-
- if (is_server)
- {
- if (!netplay_send_nickname(netplay, netplay->fd))
- {
- RARCH_ERR("%s\n",
- msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT));
- return false;
- }
- }
-
- /* Server sends SRAM, client receives */
- if (is_server)
- {
- /* Send SRAM data to the client */
- sram = mem_info.data;
-
- if (!socket_send_all_blocking(netplay->fd, sram, sram_size, false))
- {
- RARCH_ERR("%s\n",
- msg_hash_to_str(MSG_FAILED_TO_SEND_SRAM_DATA_TO_CLIENT));
- return false;
- }
-
- }
- else
- {
- /* Get SRAM data from User 1. */
- if (sram_size != 0 && sram_size == remote_sram_size)
- {
- sram = mem_info.data;
-
- if (!socket_receive_all_blocking(netplay->fd, sram, sram_size))
- {
- RARCH_ERR("%s\n",
- msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
- return false;
- }
-
- }
- else if (remote_sram_size != 0)
- {
- /* We can't load this, but we still need to get rid of the data */
- uint32_t quickbuf;
- while (remote_sram_size > 0)
- {
- if (!socket_receive_all_blocking(netplay->fd, &quickbuf, (remote_sram_size > sizeof(uint32_t)) ? sizeof(uint32_t) : remote_sram_size))
- {
- RARCH_ERR("%s\n",
- msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
- return false;
- }
- if (remote_sram_size > sizeof(uint32_t))
- remote_sram_size -= sizeof(uint32_t);
- else
- remote_sram_size = 0;
- }
-
- }
-
- }
-
- /* Reset our frame count so it's consistent with the server */
- netplay->self_frame_count = netplay->other_frame_count = 0;
- netplay->read_frame_count = 1;
- for (i = 0; i < netplay->buffer_size; i++)
- {
- netplay->buffer[i].used = false;
- if (i == netplay->self_ptr)
- {
- netplay_delta_frame_ready(netplay, &netplay->buffer[i], 0);
- netplay->buffer[i].have_remote = true;
- netplay->other_ptr = i;
- netplay->read_ptr = NEXT_PTR(i);
- }
- else
- {
- netplay->buffer[i].used = false;
- }
- }
-
- if (is_server)
- {
- netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick);
- }
- else
- {
- snprintf(msg, sizeof(msg), "%s: \"%s\"",
- msg_hash_to_str(MSG_CONNECTED_TO),
- netplay->other_nick);
- RARCH_LOG("%s\n", msg);
- runloop_msg_queue_push(msg, 1, 180, false);
- }
-
- return true;
-
-error:
- if (msg[0])
- {
- RARCH_ERR("%s\n", msg);
- runloop_msg_queue_push(msg, 1, 180, false);
- }
- return false;
-}
-
-bool netplay_is_server(netplay_t* netplay)
-{
- if (!netplay)
- return false;
- return netplay->is_server;
-}
-
-bool netplay_is_spectate(netplay_t* netplay)
-{
- if (!netplay)
- return false;
- return netplay->spectate.enabled;
-}
-
-bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame)
-{
- void *remember_state;
- if (delta->used)
- {
- if (delta->frame == frame) return true;
- if (netplay->other_frame_count <= delta->frame)
- {
- /* We haven't even replayed this frame yet, so we can't overwrite it! */
- return false;
- }
- }
- remember_state = delta->state;
- memset(delta, 0, sizeof(struct delta_frame));
- delta->used = true;
- delta->frame = frame;
- delta->state = remember_state;
- return true;
-}
-
-uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
-{
- if (!netplay->state_size)
- return 0;
- return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size);
-}
diff --git a/network/netplay/netplay_delta.c b/network/netplay/netplay_delta.c
new file mode 100644
index 0000000000..894478107b
--- /dev/null
+++ b/network/netplay/netplay_delta.c
@@ -0,0 +1,66 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2016 - Daniel De Matteis
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#include
+#include
+
+#include
+#include
+
+#include "netplay_private.h"
+
+/**
+ * netplay_delta_frame_ready
+ *
+ * Prepares, if possible, a delta frame for input, and reports whether it is
+ * ready.
+ *
+ * Returns: True if the delta frame is ready for input at the given frame,
+ * false otherwise.
+ */
+bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta,
+ uint32_t frame)
+{
+ void *remember_state;
+ if (delta->used)
+ {
+ if (delta->frame == frame) return true;
+ if (netplay->other_frame_count <= delta->frame)
+ {
+ /* We haven't even replayed this frame yet, so we can't overwrite it! */
+ return false;
+ }
+ }
+ remember_state = delta->state;
+ memset(delta, 0, sizeof(struct delta_frame));
+ delta->used = true;
+ delta->frame = frame;
+ delta->state = remember_state;
+ return true;
+}
+
+/**
+ * netplay_delta_frame_crc
+ *
+ * Get the CRC for the serialization of this frame.
+ */
+uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
+{
+ if (!netplay->state_size)
+ return 0;
+ return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size);
+}
diff --git a/network/netplay/netplay_discovery.c b/network/netplay/netplay_discovery.c
index b360db50b4..34780a6c17 100644
--- a/network/netplay/netplay_discovery.c
+++ b/network/netplay/netplay_discovery.c
@@ -32,6 +32,7 @@
*/
#include
+#include
#include
#include
@@ -191,6 +192,11 @@ error:
return false;
}
+/**
+ * netplay_lan_ad_server
+ *
+ * Respond to any LAN ad queries that the netplay server has received.
+ */
bool netplay_lan_ad_server(netplay_t *netplay)
{
fd_set fds;
diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c
new file mode 100644
index 0000000000..854d0db73f
--- /dev/null
+++ b/network/netplay/netplay_frontend.c
@@ -0,0 +1,1019 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2016 - Daniel De Matteis
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include "netplay_private.h"
+
+#include "../../configuration.h"
+#include "../../input/input_driver.h"
+#include "../../runloop.h"
+
+/* Only used before init_netplay */
+static bool netplay_enabled = false;
+static bool netplay_is_client = false;
+
+/* Used while Netplay is running */
+static netplay_t *netplay_data = NULL;
+
+/* Used to avoid recursive netplay calls */
+static bool in_netplay = false;
+
+/**
+ * netplay_is_alive:
+ * @netplay : pointer to netplay object
+ *
+ * Checks if input port/index is controlled by netplay or not.
+ *
+ * Returns: true (1) if alive, otherwise false (0).
+ **/
+static bool netplay_is_alive(void)
+{
+ if (!netplay_data)
+ return false;
+ return !!netplay_data->connected_players;
+}
+
+/**
+ * netplay_should_skip:
+ * @netplay : pointer to netplay object
+ *
+ * If we're fast-forward replaying to resync, check if we
+ * should actually show frame.
+ *
+ * Returns: bool (1) if we should skip this frame, otherwise
+ * false (0).
+ **/
+static bool netplay_should_skip(netplay_t *netplay)
+{
+ if (!netplay)
+ return false;
+ return netplay->is_replay && (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED);
+}
+
+/**
+ * netplay_can_poll
+ *
+ * Just a frontend for netplay->can_poll that handles netplay==NULL
+ */
+static bool netplay_can_poll(netplay_t *netplay)
+{
+ if (!netplay)
+ return false;
+ return netplay->can_poll;
+}
+
+/**
+ * get_self_input_state:
+ * @netplay : pointer to netplay object
+ *
+ * Grab our own input state and send this frame's input state (self and remote)
+ * over the network
+ *
+ * Returns: true (1) if successful, otherwise false (0).
+ */
+static bool get_self_input_state(netplay_t *netplay)
+{
+ uint32_t state[WORDS_PER_INPUT] = {0, 0, 0};
+ struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr];
+ size_t i;
+
+ if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count))
+ return false;
+
+ if (ptr->have_local)
+ {
+ /* We've already read this frame! */
+ return true;
+ }
+
+ if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0)
+ {
+ settings_t *settings = config_get_ptr();
+
+ /* First frame we always give zero input since relying on
+ * input from first frame screws up when we use -F 0. */
+ retro_input_state_t cb = netplay->cbs.state_cb;
+ for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
+ {
+ int16_t tmp = cb(0,
+ RETRO_DEVICE_JOYPAD, 0, i);
+ state[0] |= tmp ? 1 << i : 0;
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ int16_t tmp_x = cb(0,
+ RETRO_DEVICE_ANALOG, i, 0);
+ int16_t tmp_y = cb(0,
+ RETRO_DEVICE_ANALOG, i, 1);
+ state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16);
+ }
+ }
+
+ memcpy(ptr->self_state, state, sizeof(state));
+ ptr->have_local = true;
+
+ /* If we're playing, copy it in as real input */
+ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
+ {
+ memcpy(ptr->real_input_state[netplay->self_player], state,
+ sizeof(state));
+ ptr->have_real[netplay->self_player] = true;
+ }
+
+ /* And send this input to our peers */
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED)
+ netplay_send_cur_input(netplay, &netplay->connections[i]);
+ }
+
+ return true;
+}
+
+static uint32_t netplay_max_ahead(netplay_t *netplay)
+{
+ uint32_t max_ahead;
+
+ /* Figure out how many frames we're allowed to be ahead: Ideally we need to be
+ * able to run our entire stall worth of frames in one real frame. In
+ * practice, we'll allow a couple jitter frames. (FIXME: hard coded
+ * as three 60FPS frames) */
+ if (netplay_data->frame_run_time_avg)
+ max_ahead = 50000 / netplay_data->frame_run_time_avg;
+ else
+ max_ahead = NETPLAY_MAX_STALL_FRAMES;
+ if (max_ahead > NETPLAY_MAX_STALL_FRAMES)
+ max_ahead = NETPLAY_MAX_STALL_FRAMES;
+ if (max_ahead < 2)
+ max_ahead = 2;
+
+ return max_ahead;
+}
+
+/**
+ * netplay_poll:
+ * @netplay : pointer to netplay object
+ *
+ * Polls network to see if we have anything new. If our
+ * network buffer is full, we simply have to block
+ * for new input data.
+ *
+ * Returns: true (1) if successful, otherwise false (0).
+ **/
+static bool netplay_poll(void)
+{
+ int res;
+ uint32_t player;
+ size_t i;
+
+ netplay_data->can_poll = false;
+
+ get_self_input_state(netplay_data);
+
+ /* If we're not connected, we're done */
+ if (netplay_data->self_mode == NETPLAY_CONNECTION_NONE)
+ return true;
+
+ /* Read Netplay input, block if we're configured to stall for input every
+ * frame */
+ if (netplay_data->stateless_mode &&
+ netplay_data->unread_frame_count <= netplay_data->self_frame_count)
+ res = netplay_poll_net_input(netplay_data, true);
+ else
+ res = netplay_poll_net_input(netplay_data, false);
+ if (res == -1)
+ {
+ /* Catastrophe! */
+ for (i = 0; i < netplay_data->connections_size; i++)
+ netplay_hangup(netplay_data, &netplay_data->connections[i]);
+ return false;
+ }
+
+ /* Simulate the input if we don't have real input */
+ netplay_simulate_input(netplay_data, netplay_data->self_ptr, false);
+
+ /* Consider stalling */
+ switch (netplay_data->stall)
+ {
+ case NETPLAY_STALL_RUNNING_FAST:
+ {
+ uint32_t max_ahead = netplay_max_ahead(netplay_data);
+ netplay_update_unread_ptr(netplay_data);
+ if (netplay_data->unread_frame_count + max_ahead - 2
+ > netplay_data->self_frame_count)
+ {
+ netplay_data->stall = NETPLAY_STALL_NONE;
+ for (i = 0; i < netplay_data->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay_data->connections[i];
+ if (connection->active && connection->stall)
+ connection->stall = NETPLAY_STALL_NONE;
+ }
+ }
+ break;
+ }
+
+ case NETPLAY_STALL_NO_CONNECTION:
+ /* We certainly haven't fixed this */
+ break;
+
+ default: /* not stalling */
+ {
+ uint32_t max_ahead = netplay_max_ahead(netplay_data);
+
+ /* Are we too far ahead? */
+ netplay_update_unread_ptr(netplay_data);
+ if (netplay_data->unread_frame_count + max_ahead
+ <= netplay_data->self_frame_count)
+ {
+ netplay_data->stall = NETPLAY_STALL_RUNNING_FAST;
+ netplay_data->stall_time = cpu_features_get_time_usec();
+
+ /* Figure out who to blame */
+ if (netplay_data->is_server)
+ {
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if (!(netplay_data->connected_players & (1<read_frame_count[player] > netplay_data->unread_frame_count) continue;
+ for (i = 0; i < netplay_data->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay_data->connections[i];
+ if (connection->active &&
+ connection->mode == NETPLAY_CONNECTION_PLAYING &&
+ connection->player == player)
+ {
+ connection->stall = NETPLAY_STALL_RUNNING_FAST;
+ connection->stall_time = netplay_data->stall_time;
+ break;
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ /* If we're stalling, consider disconnection */
+ if (netplay_data->stall)
+ {
+ retro_time_t now = cpu_features_get_time_usec();
+
+ /* Don't stall out while they're paused */
+ if (netplay_data->remote_paused)
+ netplay_data->stall_time = now;
+ else if (now - netplay_data->stall_time >=
+ (netplay_data->is_server ? MAX_SERVER_STALL_TIME_USEC :
+ MAX_CLIENT_STALL_TIME_USEC))
+ {
+ /* Stalled out! */
+ if (netplay_data->is_server)
+ {
+ for (i = 0; i < netplay_data->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay_data->connections[i];
+ if (connection->active &&
+ connection->mode == NETPLAY_CONNECTION_PLAYING &&
+ connection->stall &&
+ now - connection->stall_time >= MAX_SERVER_STALL_TIME_USEC)
+ {
+ netplay_hangup(netplay_data, connection);
+ }
+ }
+ }
+ else
+ {
+ netplay_hangup(netplay_data, &netplay_data->connections[0]);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * input_poll_net
+ *
+ * Poll the network if necessary.
+ */
+void input_poll_net(void)
+{
+ if (!netplay_should_skip(netplay_data) && netplay_can_poll(netplay_data))
+ netplay_poll();
+}
+
+/* Netplay polling callbacks */
+void video_frame_net(const void *data, unsigned width,
+ unsigned height, size_t pitch)
+{
+ if (!netplay_should_skip(netplay_data))
+ netplay_data->cbs.frame_cb(data, width, height, pitch);
+}
+
+void audio_sample_net(int16_t left, int16_t right)
+{
+ if (!netplay_should_skip(netplay_data) && !netplay_data->stall)
+ netplay_data->cbs.sample_cb(left, right);
+}
+
+size_t audio_sample_batch_net(const int16_t *data, size_t frames)
+{
+ if (!netplay_should_skip(netplay_data) && !netplay_data->stall)
+ return netplay_data->cbs.sample_batch_cb(data, frames);
+ return frames;
+}
+
+static int16_t netplay_input_state(netplay_t *netplay,
+ unsigned port, unsigned device,
+ unsigned idx, unsigned id)
+{
+ size_t ptr = netplay->is_replay ?
+ netplay->replay_ptr : netplay->self_ptr;
+
+ const uint32_t *curr_input_state = NULL;
+
+ if (port <= 1)
+ {
+ /* Possibly flip the port */
+ if (netplay_flip_port(netplay))
+ port ^= 1;
+ }
+ else if (port >= MAX_USERS)
+ {
+ return 0;
+ }
+
+ if (port > netplay->player_max)
+ netplay->player_max = port;
+
+ if (netplay->buffer[ptr].have_real[port])
+ {
+ netplay->buffer[ptr].used_real[port] = true;
+ curr_input_state = netplay->buffer[ptr].real_input_state[port];
+ }
+ else
+ {
+ curr_input_state = netplay->buffer[ptr].simulated_input_state[port];
+ }
+
+ switch (device)
+ {
+ case RETRO_DEVICE_JOYPAD:
+ return ((1 << id) & curr_input_state[0]) ? 1 : 0;
+
+ case RETRO_DEVICE_ANALOG:
+ {
+ uint32_t state = curr_input_state[1 + idx];
+ return (int16_t)(uint16_t)(state >> (id * 16));
+ }
+
+ default:
+ return 0;
+ }
+}
+
+int16_t input_state_net(unsigned port, unsigned device,
+ unsigned idx, unsigned id)
+{
+ if (netplay_is_alive())
+ return netplay_input_state(netplay_data, port, device, idx, id);
+ return netplay_data->cbs.state_cb(port, device, idx, id);
+}
+/* ^^^ Netplay polling callbacks */
+
+/**
+ * netplay_command:
+ * @netplay : pointer to netplay object
+ * @cmd : command to send
+ * @data : data to send as argument
+ * @sz : size of data
+ * @command_str : name of action
+ * @success_msg : message to display upon success
+ *
+ * Sends a single netplay command and waits for response. Only actually used
+ * for player flipping. FIXME: Should probably just be removed.
+ */
+bool netplay_command(netplay_t* netplay, struct netplay_connection *connection,
+ enum netplay_cmd cmd, void* data, size_t sz, const char* command_str,
+ const char* success_msg)
+{
+ char m[256];
+ const char* msg = NULL;
+
+ retro_assert(netplay);
+
+ if (!netplay_send_raw_cmd(netplay, connection, cmd, data, sz))
+ goto error;
+
+ runloop_msg_queue_push(success_msg, 1, 180, false);
+
+ return true;
+
+error:
+ if (msg)
+ snprintf(m, sizeof(m), msg, command_str);
+ RARCH_WARN("%s\n", m);
+ runloop_msg_queue_push(m, 1, 180, false);
+ return false;
+}
+
+/**
+ * netplay_flip_users:
+ * @netplay : pointer to netplay object
+ *
+ * Flip who controls user 1 and 2.
+ */
+static void netplay_flip_users(netplay_t *netplay)
+{
+ /* Must be in the future because we may have
+ * already sent this frame's data */
+ uint32_t flip_frame = netplay->self_frame_count + 1;
+ uint32_t flip_frame_net = htonl(flip_frame);
+ size_t i;
+
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED)
+ {
+ netplay_command(netplay, connection, NETPLAY_CMD_FLIP_PLAYERS,
+ &flip_frame_net, sizeof flip_frame_net, "flip users",
+ "Successfully flipped users.\n");
+ }
+ }
+
+ netplay->flip ^= true;
+ netplay->flip_frame = flip_frame;
+}
+
+/**
+ * netplay_frontend_paused
+ * @netplay : pointer to netplay object
+ * @paused : true if frontend is paused
+ *
+ * Inform Netplay of the frontend's pause state (paused or otherwise)
+ */
+static void netplay_frontend_paused(netplay_t *netplay, bool paused)
+{
+ size_t i;
+ uint32_t paused_ct;
+
+ /* Nothing to do if we already knew this */
+ if (netplay->local_paused == paused)
+ return;
+
+ netplay->local_paused = paused;
+
+ /* Communicating this is a bit odd: If exactly one other connection is
+ * paused, then we must tell them that we're unpaused, as from their
+ * perspective we are. If more than one other connection is paused, then our
+ * status as proxy means we are NOT unpaused to either of them. */
+ paused_ct = 0;
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active && connection->paused)
+ paused_ct++;
+ }
+ if (paused_ct > 1)
+ return;
+
+ /* Send our unpaused status. Must send manually because we must immediately
+ * flush the buffer: If we're paused, we won't be polled. */
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED)
+ {
+ if (paused)
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_PAUSE,
+ netplay->nick, NETPLAY_NICK_LEN);
+ else
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_RESUME,
+ NULL, 0);
+
+ /* We're not going to be polled, so we need to flush this command now */
+ netplay_send_flush(&connection->send_packet_buffer, connection->fd, true);
+ }
+ }
+}
+
+/**
+ * netplay_pre_frame:
+ * @netplay : pointer to netplay object
+ *
+ * Pre-frame for Netplay.
+ * Call this before running retro_run().
+ *
+ * Returns: true (1) if the frontend is cleared to emulate the frame, false (0)
+ * if we're stalled or paused
+ **/
+bool netplay_pre_frame(netplay_t *netplay)
+{
+ bool sync_stalled;
+
+ retro_assert(netplay);
+
+ /* FIXME: This is an ugly way to learn we're not paused anymore */
+ if (netplay->local_paused)
+ netplay_frontend_paused(netplay, false);
+
+ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
+ {
+ /* Are we ready now? */
+ netplay_try_init_serialization(netplay);
+ }
+
+ if (netplay->is_server)
+ {
+ /* Advertise our server */
+ netplay_lan_ad_server(netplay);
+
+ /* NAT traversal if applicable */
+ if (netplay->nat_traversal &&
+ netplay->nat_traversal_state.request_outstanding &&
+ !netplay->nat_traversal_state.have_inet4)
+ {
+ struct timeval tmptv = {0};
+ fd_set fds = netplay->nat_traversal_state.fds;
+ if (socket_select(netplay->nat_traversal_state.nfds, &fds, NULL, NULL, &tmptv) > 0)
+ natt_read(&netplay->nat_traversal_state);
+
+#ifndef HAVE_SOCKET_LEGACY
+ if (!netplay->nat_traversal_state.request_outstanding ||
+ netplay->nat_traversal_state.have_inet4)
+ netplay_announce_nat_traversal(netplay);
+#endif
+ }
+ }
+
+ sync_stalled = !netplay_sync_pre_frame(netplay);
+
+ if (sync_stalled ||
+ (netplay->connected_players &&
+ (netplay->stall || netplay->remote_paused)))
+ {
+ /* We may have received data even if we're stalled, so run post-frame
+ * sync */
+ netplay_sync_post_frame(netplay, true);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * netplay_post_frame:
+ * @netplay : pointer to netplay object
+ *
+ * Post-frame for Netplay.
+ * We check if we have new input and replay from recorded input.
+ * Call this after running retro_run().
+ **/
+void netplay_post_frame(netplay_t *netplay)
+{
+ size_t i;
+ retro_assert(netplay);
+ netplay_update_unread_ptr(netplay);
+ netplay_sync_post_frame(netplay, false);
+
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active &&
+ !netplay_send_flush(&connection->send_packet_buffer, connection->fd,
+ false))
+ netplay_hangup(netplay, &netplay->connections[0]);
+ }
+}
+
+/**
+ * netplay_send_savestate
+ * @netplay : pointer to netplay object
+ * @serial_info : the savestate being loaded
+ * @cx : compression type
+ * @z : compression backend to use
+ *
+ * Send a loaded savestate to those connected peers using the given compression
+ * scheme.
+ */
+void netplay_send_savestate(netplay_t *netplay,
+ retro_ctx_serialize_info_t *serial_info, uint32_t cx,
+ struct compression_transcoder *z)
+{
+ uint32_t header[4];
+ uint32_t rd, wn;
+ size_t i;
+
+ /* Compress it */
+ z->compression_backend->set_in(z->compression_stream,
+ (const uint8_t*)serial_info->data_const, serial_info->size);
+ z->compression_backend->set_out(z->compression_stream,
+ netplay->zbuffer, netplay->zbuffer_size);
+ if (!z->compression_backend->trans(z->compression_stream, true, &rd,
+ &wn, NULL))
+ {
+ /* Catastrophe! */
+ for (i = 0; i < netplay->connections_size; i++)
+ netplay_hangup(netplay, &netplay->connections[i]);
+ return;
+ }
+
+ /* Send it to relevant peers */
+ header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE);
+ header[1] = htonl(wn + 2*sizeof(uint32_t));
+ header[2] = htonl(netplay->self_frame_count);
+ header[3] = htonl(serial_info->size);
+
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (!connection->active ||
+ connection->mode < NETPLAY_CONNECTION_CONNECTED ||
+ connection->compression_supported != cx) continue;
+
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd, header,
+ sizeof(header)) ||
+ !netplay_send(&connection->send_packet_buffer, connection->fd,
+ netplay->zbuffer, wn))
+ netplay_hangup(netplay, connection);
+ }
+}
+
+/**
+ * netplay_load_savestate
+ * @netplay : pointer to netplay object
+ * @serial_info : the savestate being loaded, NULL means
+ * "load it yourself"
+ * @save : Whether to save the provided serial_info
+ * into the frame buffer
+ *
+ * Inform Netplay of a savestate load and send it to the other side
+ **/
+void netplay_load_savestate(netplay_t *netplay,
+ retro_ctx_serialize_info_t *serial_info, bool save)
+{
+ retro_ctx_serialize_info_t tmp_serial_info;
+
+ /* Record it in our own buffer */
+ if (save || !serial_info)
+ {
+ if (netplay_delta_frame_ready(netplay,
+ &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
+ {
+ if (!serial_info)
+ {
+ tmp_serial_info.size = netplay->state_size;
+ tmp_serial_info.data = netplay->buffer[netplay->self_ptr].state;
+ if (!core_serialize(&tmp_serial_info))
+ return;
+ tmp_serial_info.data_const = tmp_serial_info.data;
+ serial_info = &tmp_serial_info;
+ }
+ else
+ {
+ if (serial_info->size <= netplay->state_size)
+ {
+ memcpy(netplay->buffer[netplay->self_ptr].state,
+ serial_info->data_const, serial_info->size);
+ }
+ }
+ }
+ else
+ {
+ /* FIXME: This is a critical failure! */
+ return;
+ }
+ }
+
+ /* We need to ignore any intervening data from the other side,
+ * and never rewind past this */
+ netplay_update_unread_ptr(netplay);
+ if (netplay->unread_frame_count < netplay->self_frame_count)
+ {
+ uint32_t player;
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if (!(netplay->connected_players & (1<read_frame_count[player] < netplay->self_frame_count)
+ {
+ netplay->read_ptr[player] = netplay->self_ptr;
+ netplay->read_frame_count[player] = netplay->self_frame_count;
+ }
+ }
+ if (netplay->server_frame_count < netplay->self_frame_count)
+ {
+ netplay->server_ptr = netplay->self_ptr;
+ netplay->server_frame_count = netplay->self_frame_count;
+ }
+ netplay_update_unread_ptr(netplay);
+ }
+ if (netplay->other_frame_count < netplay->self_frame_count)
+ {
+ netplay->other_ptr = netplay->self_ptr;
+ netplay->other_frame_count = netplay->self_frame_count;
+ }
+
+ /* If we can't send it to the peer, loading a state was a bad idea */
+ if (netplay->quirks & (
+ NETPLAY_QUIRK_NO_SAVESTATES
+ | NETPLAY_QUIRK_NO_TRANSMISSION))
+ return;
+
+ /* Send this to every peer */
+ if (netplay->compress_nil.compression_backend)
+ netplay_send_savestate(netplay, serial_info, 0, &netplay->compress_nil);
+ if (netplay->compress_zlib.compression_backend)
+ netplay_send_savestate(netplay, serial_info, NETPLAY_COMPRESSION_ZLIB,
+ &netplay->compress_zlib);
+}
+
+/**
+ * netplay_toggle_play_spectate
+ *
+ * Toggle between play mode and spectate mode
+ */
+static void netplay_toggle_play_spectate(netplay_t *netplay)
+{
+ if (netplay->is_server)
+ {
+ /* FIXME: Duplication */
+ uint32_t payload[2];
+ char msg[512];
+ const char *dmsg;
+ payload[0] = htonl(netplay->self_frame_count);
+ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
+ {
+ /* Mark us as no longer playing */
+ payload[1] = htonl(netplay->self_player);
+ netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;
+
+ dmsg = msg_hash_to_str(MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME);
+
+ }
+ else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING)
+ {
+ uint32_t player;
+
+ /* Take a player number */
+ for (player = 0; player < MAX_USERS; player++)
+ if (!(netplay->connected_players & (1<self_mode = NETPLAY_CONNECTION_PLAYING;
+ netplay->self_player = player;
+
+ dmsg = msg;
+ msg[sizeof(msg)-1] = '\0';
+ snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N), player+1);
+ }
+
+ RARCH_LOG("%s\n", dmsg);
+ runloop_msg_queue_push(dmsg, 1, 180, false);
+
+ netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload));
+
+ }
+ else
+ {
+ uint32_t cmd;
+ size_t i;
+
+ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
+ {
+ /* Switch to spectator mode immediately */
+ netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;
+ cmd = NETPLAY_CMD_SPECTATE;
+ }
+ else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING)
+ {
+ /* Switch only after getting permission */
+ cmd = NETPLAY_CMD_PLAY;
+ }
+ else return;
+
+ netplay_send_raw_cmd_all(netplay, NULL, cmd, NULL, 0);
+ }
+}
+
+/**
+ * netplay_disconnect
+ * @netplay : pointer to netplay object
+ *
+ * Disconnect netplay.
+ *
+ * Returns: true (1) if successful. At present, cannot fail.
+ **/
+bool netplay_disconnect(netplay_t *netplay)
+{
+ size_t i;
+ if (!netplay)
+ return true;
+ for (i = 0; i < netplay->connections_size; i++)
+ netplay_hangup(netplay, &netplay->connections[i]);
+ return true;
+}
+
+void deinit_netplay(void)
+{
+ if (netplay_data)
+ netplay_free(netplay_data);
+ netplay_data = NULL;
+ core_unset_netplay_callbacks();
+}
+
+/**
+ * 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
+ * @play_password : Password required to play (server only)
+ * @spectate_password : Password required to connect (server only)
+ *
+ * Initializes netplay.
+ *
+ * If netplay is already initialized, will return false (0).
+ *
+ * Returns: true (1) if successful, otherwise false (0).
+ **/
+bool init_netplay(void *direct_host, const char *server, unsigned port,
+ const char *play_password, const char *spectate_password)
+{
+ struct retro_callbacks cbs = {0};
+ settings_t *settings = config_get_ptr();
+ uint64_t serialization_quirks = 0;
+ uint64_t quirks = 0;
+
+ if (!netplay_enabled)
+ return false;
+
+ core_set_default_callbacks(&cbs);
+ if (!core_set_netplay_callbacks())
+ return false;
+
+ /* Map the core's quirks to our quirks */
+ serialization_quirks = core_serialization_quirks();
+ if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD))
+ {
+ /* Quirks we don't support! Just disable everything. */
+ quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
+ }
+ if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_SAVESTATES)
+ quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
+ if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_TRANSMISSION)
+ quirks |= NETPLAY_QUIRK_NO_TRANSMISSION;
+ if (serialization_quirks & NETPLAY_QUIRK_MAP_INITIALIZATION)
+ quirks |= NETPLAY_QUIRK_INITIALIZATION;
+ if (serialization_quirks & NETPLAY_QUIRK_MAP_ENDIAN_DEPENDENT)
+ quirks |= NETPLAY_QUIRK_ENDIAN_DEPENDENT;
+ if (serialization_quirks & NETPLAY_QUIRK_MAP_PLATFORM_DEPENDENT)
+ quirks |= NETPLAY_QUIRK_PLATFORM_DEPENDENT;
+
+ if (netplay_is_client)
+ {
+ RARCH_LOG("%s\n", msg_hash_to_str(MSG_CONNECTING_TO_NETPLAY_HOST));
+ }
+ else
+ {
+ RARCH_LOG("%s\n", msg_hash_to_str(MSG_WAITING_FOR_CLIENT));
+ runloop_msg_queue_push(
+ msg_hash_to_str(MSG_WAITING_FOR_CLIENT),
+ 0, 180, false);
+ }
+
+ netplay_data = (netplay_t*)netplay_new(
+ netplay_is_client ? direct_host : NULL,
+ netplay_is_client ? server : NULL,
+ port ? port : RARCH_DEFAULT_PORT,
+ play_password, spectate_password,
+ settings->netplay.stateless_mode, settings->netplay.check_frames, &cbs,
+ settings->netplay.nat_traversal, settings->username,
+ quirks);
+
+ if (netplay_data)
+ return true;
+
+ RARCH_WARN("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED));
+
+ runloop_msg_queue_push(
+ msg_hash_to_str(MSG_NETPLAY_FAILED),
+ 0, 180, false);
+ return false;
+}
+
+/**
+ * netplay_driver_ctl
+ *
+ * Frontend access to Netplay functionality
+ */
+bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
+{
+ bool ret = true;
+
+ if (in_netplay)
+ return true;
+ in_netplay = true;
+
+ if (!netplay_data)
+ {
+ switch (state)
+ {
+ case RARCH_NETPLAY_CTL_ENABLE_SERVER:
+ netplay_enabled = true;
+ netplay_is_client = false;
+ goto done;
+
+ case RARCH_NETPLAY_CTL_ENABLE_CLIENT:
+ netplay_enabled = true;
+ netplay_is_client = true;
+ break;
+
+ case RARCH_NETPLAY_CTL_DISABLE:
+ netplay_enabled = false;
+ goto done;
+
+ case RARCH_NETPLAY_CTL_IS_ENABLED:
+ ret = netplay_enabled;
+ goto done;
+
+ case RARCH_NETPLAY_CTL_IS_DATA_INITED:
+ ret = false;
+ goto done;
+
+ default:
+ goto done;
+ }
+ }
+
+ switch (state)
+ {
+ case RARCH_NETPLAY_CTL_ENABLE_SERVER:
+ case RARCH_NETPLAY_CTL_ENABLE_CLIENT:
+ case RARCH_NETPLAY_CTL_IS_DATA_INITED:
+ goto done;
+ case RARCH_NETPLAY_CTL_DISABLE:
+ ret = false;
+ goto done;
+ case RARCH_NETPLAY_CTL_IS_ENABLED:
+ goto done;
+ case RARCH_NETPLAY_CTL_POST_FRAME:
+ netplay_post_frame(netplay_data);
+ break;
+ case RARCH_NETPLAY_CTL_PRE_FRAME:
+ ret = netplay_pre_frame(netplay_data);
+ goto done;
+ case RARCH_NETPLAY_CTL_FLIP_PLAYERS:
+ {
+ bool *state = (bool*)data;
+ if (netplay_data->is_server && *state)
+ netplay_flip_users(netplay_data);
+ }
+ break;
+ case RARCH_NETPLAY_CTL_GAME_WATCH:
+ netplay_toggle_play_spectate(netplay_data);
+ break;
+ case RARCH_NETPLAY_CTL_PAUSE:
+ netplay_frontend_paused(netplay_data, true);
+ break;
+ case RARCH_NETPLAY_CTL_UNPAUSE:
+ netplay_frontend_paused(netplay_data, false);
+ break;
+ case RARCH_NETPLAY_CTL_LOAD_SAVESTATE:
+ netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true);
+ break;
+ case RARCH_NETPLAY_CTL_DISCONNECT:
+ ret = netplay_disconnect(netplay_data);
+ goto done;
+ default:
+ case RARCH_NETPLAY_CTL_NONE:
+ ret = false;
+ }
+
+done:
+ in_netplay = false;
+ return ret;
+}
diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c
new file mode 100644
index 0000000000..7b57aa49e0
--- /dev/null
+++ b/network/netplay/netplay_handshake.c
@@ -0,0 +1,1024 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2016 - Daniel De Matteis
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "netplay_private.h"
+
+#include "../../configuration.h"
+#include "../../content.h"
+#include "../../retroarch.h"
+#include "../../runloop.h"
+#include "../../version.h"
+#include "../../menu/widgets/menu_input_dialog.h"
+
+#ifndef HAVE_SOCKET_LEGACY
+/* Custom inet_ntop. Win32 doesn't seem to support this ... */
+void netplay_log_connection(const struct sockaddr_storage *their_addr,
+ unsigned slot, const char *nick)
+{
+ union
+ {
+ const struct sockaddr_storage *storage;
+ const struct sockaddr_in *v4;
+ const struct sockaddr_in6 *v6;
+ } u;
+ const char *str = NULL;
+ char buf_v4[INET_ADDRSTRLEN] = {0};
+ char buf_v6[INET6_ADDRSTRLEN] = {0};
+ char msg[512];
+
+ msg[0] = '\0';
+
+ u.storage = their_addr;
+
+ switch (their_addr->ss_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in in;
+
+ memset(&in, 0, sizeof(in));
+
+ str = buf_v4;
+ in.sin_family = AF_INET;
+ memcpy(&in.sin_addr, &u.v4->sin_addr, sizeof(struct in_addr));
+
+ getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in),
+ buf_v4, sizeof(buf_v4),
+ NULL, 0, NI_NUMERICHOST);
+ }
+ break;
+ case AF_INET6:
+ {
+ struct sockaddr_in6 in;
+ memset(&in, 0, sizeof(in));
+
+ str = buf_v6;
+ in.sin6_family = AF_INET6;
+ memcpy(&in.sin6_addr, &u.v6->sin6_addr, sizeof(struct in6_addr));
+
+ getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
+ buf_v6, sizeof(buf_v6), NULL, 0, NI_NUMERICHOST);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (str)
+ {
+ snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM_NAME),
+ nick, str);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ RARCH_LOG("%s\n", msg);
+ }
+ else
+ {
+ snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM),
+ nick);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ RARCH_LOG("%s\n", msg);
+ }
+ RARCH_LOG("%s %u\n", msg_hash_to_str(MSG_CONNECTION_SLOT),
+ slot);
+}
+
+#else
+void netplay_log_connection(const struct sockaddr_storage *their_addr,
+ unsigned slot, const char *nick)
+{
+ char msg[512];
+
+ msg[0] = '\0';
+
+ snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM),
+ nick);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ RARCH_LOG("%s\n", msg);
+ RARCH_LOG("%s %u\n",
+ msg_hash_to_str(MSG_CONNECTION_SLOT), slot);
+}
+
+#endif
+
+/**
+ * netplay_impl_magic:
+ *
+ * A pseudo-hash of the RetroArch and Netplay version, so only compatible
+ * versions play together.
+ */
+uint32_t netplay_impl_magic(void)
+{
+ size_t i, len;
+ uint32_t res = 0;
+ const char *ver = PACKAGE_VERSION;
+
+ len = strlen(ver);
+ for (i = 0; i < len; i++)
+ res ^= ver[i] << (i & 0xf);
+
+ res ^= NETPLAY_PROTOCOL_VERSION << (i & 0xf);
+
+ return res;
+}
+
+/**
+ * netplay_platform_magic
+ *
+ * Just enough info to tell us if our platforms mismatch: Endianness and a
+ * couple of type sizes.
+ *
+ * Format:
+ * bit 31: Reserved
+ * bit 30: 1 for big endian
+ * bits 29-15: sizeof(size_t)
+ * bits 14-0: sizeof(long)
+ */
+static uint32_t netplay_platform_magic(void)
+{
+ uint32_t ret =
+ ((1 == htonl(1)) << 30)
+ |(sizeof(size_t) << 15)
+ |(sizeof(long));
+ return ret;
+}
+
+/**
+ * netplay_endian_mismatch
+ *
+ * Do the platform magics mismatch on endianness?
+ */
+static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb)
+{
+ uint32_t ebit = (1<<30);
+ return (pma & ebit) != (pmb & ebit);
+}
+
+static unsigned long simple_rand_next = 1;
+
+static int simple_rand()
+{
+ simple_rand_next = simple_rand_next * 1103515245 + 12345;
+ return((unsigned)(simple_rand_next/65536) % 32768);
+}
+
+static void simple_srand(unsigned int seed) {
+ simple_rand_next = seed;
+}
+
+static uint32_t simple_rand_uint32()
+{
+ uint32_t parts[3];
+ parts[0] = simple_rand();
+ parts[1] = simple_rand();
+ parts[2] = simple_rand();
+ return ((parts[0] << 30) +
+ (parts[1] << 15) +
+ parts[2]);
+}
+
+/**
+ * netplay_handshake_init_send
+ *
+ * Initialize our handshake and send the first part of the handshake protocol.
+ */
+bool netplay_handshake_init_send(netplay_t *netplay,
+ struct netplay_connection *connection)
+{
+ uint32_t header[4] = {0};
+
+ header[0] = htonl(netplay_impl_magic());
+ header[1] = htonl(netplay_platform_magic());
+ header[2] = htonl(NETPLAY_COMPRESSION_SUPPORTED);
+ if (netplay->is_server && (netplay->play_password[0] || netplay->spectate_password[0]))
+ {
+ /* Demand a password */
+ if (simple_rand_next == 1)
+ simple_srand(time(NULL));
+ connection->salt = simple_rand_uint32();
+ if (connection->salt == 0) connection->salt = 1;
+ header[3] = htonl(connection->salt);
+ }
+ else
+ {
+ header[3] = htonl(0);
+ }
+
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd, header,
+ sizeof(header)) ||
+ !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false))
+ return false;
+
+ return true;
+}
+
+struct nick_buf_s
+{
+ uint32_t cmd[2];
+ char nick[NETPLAY_NICK_LEN];
+};
+
+struct password_buf_s
+{
+ uint32_t cmd[2];
+ char password[NETPLAY_PASS_HASH_LEN];
+};
+
+struct info_buf_s
+{
+ uint32_t cmd[2];
+ char core_name[NETPLAY_NICK_LEN];
+ char core_version[NETPLAY_NICK_LEN];
+ uint32_t content_crc;
+};
+
+#define RECV(buf, sz) \
+ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \
+ if (recvd >= 0 && recvd < (sz)) \
+ { \
+ netplay_recv_reset(&connection->recv_packet_buffer); \
+ return true; \
+ } \
+ else if (recvd < 0)
+
+static netplay_t *handshake_password_netplay;
+
+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 */
+ uint32_t cmd[2];
+ netplay_t *netplay = handshake_password_netplay;
+ struct netplay_connection *connection = &netplay->connections[0];
+
+ snprintf(password, sizeof(password), "%08X", connection->salt);
+ strlcpy(password + 8, line, sizeof(password)-8);
+
+ password_buf.cmd[0] = htonl(NETPLAY_CMD_PASSWORD);
+ password_buf.cmd[1] = htonl(sizeof(password_buf.password));
+ sha256_hash(password_buf.password, (uint8_t *) password, strlen(password));
+
+ netplay_send(&connection->send_packet_buffer, connection->fd, &password_buf, sizeof(password_buf)) &&
+ netplay_send_flush(&connection->send_packet_buffer, connection->fd, false);
+
+ menu_input_dialog_end();
+ rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL);
+}
+
+/**
+ * netplay_handshake_init
+ *
+ * Data receiver for the initial part of the handshake, i.e., waiting for the
+ * netplay header.
+ */
+bool netplay_handshake_init(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ uint32_t header[4] = {0};
+ ssize_t recvd;
+ const char *dmsg;
+ struct nick_buf_s nick_buf;
+ uint32_t local_pmagic, remote_pmagic;
+ uint32_t compression;
+ struct compression_transcoder *ctrans;
+
+ dmsg = NULL;
+
+ RECV(header, sizeof(header))
+ {
+ dmsg = msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT);
+ goto error;
+ }
+
+ if (netplay_impl_magic() != ntohl(header[0]))
+ {
+ dmsg = msg_hash_to_str(MSG_NETPLAY_IMPLEMENTATIONS_DIFFER);
+ goto error;
+ }
+
+ /* We only care about platform magic if our core is quirky */
+ local_pmagic = netplay_platform_magic();
+ remote_pmagic = ntohl(header[1]);
+ if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) &&
+ netplay_endian_mismatch(local_pmagic, remote_pmagic))
+ {
+ RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n");
+ dmsg = msg_hash_to_str(MSG_NETPLAY_ENDIAN_DEPENDENT);
+ goto error;
+ }
+ if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) &&
+ (local_pmagic != remote_pmagic))
+ {
+ RARCH_ERR("Platform mismatch with a platform-sensitive core.\n");
+ dmsg = msg_hash_to_str(MSG_NETPLAY_PLATFORM_DEPENDENT);
+ goto error;
+ }
+
+ /* Check what compression is supported */
+ compression = ntohl(header[2]);
+ compression &= NETPLAY_COMPRESSION_SUPPORTED;
+ if (compression & NETPLAY_COMPRESSION_ZLIB)
+ {
+ ctrans = &netplay->compress_zlib;
+ if (!ctrans->compression_backend)
+ {
+ ctrans->compression_backend =
+ trans_stream_get_zlib_deflate_backend();
+ if (!ctrans->compression_backend)
+ ctrans->compression_backend = trans_stream_get_pipe_backend();
+ }
+ connection->compression_supported = NETPLAY_COMPRESSION_ZLIB;
+ }
+ else
+ {
+ ctrans = &netplay->compress_nil;
+ if (!ctrans->compression_backend)
+ {
+ ctrans->compression_backend =
+ trans_stream_get_pipe_backend();
+ }
+ connection->compression_supported = 0;
+ }
+ if (!ctrans->decompression_backend)
+ ctrans->decompression_backend = ctrans->compression_backend->reverse;
+
+ /* Allocate our compression stream */
+ if (!ctrans->compression_stream)
+ {
+ ctrans->compression_stream = ctrans->compression_backend->stream_new();
+ ctrans->decompression_stream = ctrans->decompression_backend->stream_new();
+ }
+ if (!ctrans->compression_stream || !ctrans->decompression_stream)
+ {
+ RARCH_ERR("Failed to allocate compression transcoder!\n");
+ return false;
+ }
+
+ /* If a password is demanded, ask for it */
+ if (!netplay->is_server && (connection->salt = ntohl(header[3])))
+ {
+ menu_input_ctx_line_t line;
+ rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL);
+ memset(&line, 0, sizeof(line));
+ handshake_password_netplay = netplay;
+ line.label = msg_hash_to_str(MSG_NETPLAY_ENTER_PASSWORD);
+ line.label_setting = "no_setting";
+ line.cb = handshake_password;
+ menu_input_dialog_start(&line);
+ }
+
+ /* 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;
+
+ /* Move on to the next mode */
+ connection->mode = NETPLAY_CONNECTION_PRE_NICK;
+ *had_input = true;
+ netplay_recv_flush(&connection->recv_packet_buffer);
+ return true;
+
+error:
+ if (dmsg)
+ {
+ RARCH_ERR("%s\n", dmsg);
+ runloop_msg_queue_push(dmsg, 1, 180, false);
+ }
+ return false;
+}
+
+static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connection *connection)
+{
+ size_t i;
+ char msg[512];
+
+ if (netplay->is_server)
+ {
+ netplay_log_connection(&connection->addr, connection - netplay->connections, connection->nick);
+
+ /* Send them the savestate */
+ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)))
+ {
+ netplay->force_send_savestate = true;
+ }
+ }
+ else
+ {
+ snprintf(msg, sizeof(msg), "%s: \"%s\"",
+ msg_hash_to_str(MSG_CONNECTED_TO),
+ connection->nick);
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ }
+
+ /* Unstall if we were waiting for this */
+ if (netplay->stall == NETPLAY_STALL_NO_CONNECTION)
+ netplay->stall = NETPLAY_STALL_NONE;
+}
+
+/**
+ * netplay_handshake_info
+ *
+ * Send an INFO command.
+ */
+bool netplay_handshake_info(netplay_t *netplay, struct netplay_connection *connection)
+{
+ struct info_buf_s info_buf;
+ rarch_system_info_t *core_info;
+ uint32_t *content_crc_ptr;
+
+ 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));
+
+ /* Get our core info */
+ core_info = NULL;
+ runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &core_info);
+ if (core_info)
+ {
+ strlcpy(info_buf.core_name, core_info->info.library_name, sizeof(info_buf.core_name));
+ strlcpy(info_buf.core_version, core_info->info.library_version, sizeof(info_buf.core_version));
+ }
+ else
+ {
+ strlcpy(info_buf.core_name, "UNKNOWN", sizeof(info_buf.core_name));
+ strlcpy(info_buf.core_version, "UNKNOWN", sizeof(info_buf.core_version));
+ }
+
+ /* Get our content CRC */
+ content_crc_ptr = NULL;
+ content_get_crc(&content_crc_ptr);
+ if (content_crc_ptr)
+ info_buf.content_crc = htonl(*content_crc_ptr);
+
+ /* Send it off and wait for info back */
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd,
+ &info_buf, sizeof(info_buf)) ||
+ !netplay_send_flush(&connection->send_packet_buffer, connection->fd,
+ false))
+ return false;
+
+ connection->mode = NETPLAY_CONNECTION_PRE_INFO;
+ return true;
+}
+
+/**
+ * netplay_handshake_sync
+ *
+ * Send a SYNC command.
+ */
+bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *connection)
+{
+ /* If we're the server, now we send sync info */
+ uint32_t cmd[5];
+ uint32_t connected_players;
+ settings_t *settings = config_get_ptr();
+ size_t i;
+ uint32_t device;
+ retro_ctx_memory_info_t mem_info;
+ size_t nicklen, nickmangle;
+ int matchct;
+ bool nick_matched;
+
+ mem_info.id = RETRO_MEMORY_SAVE_RAM;
+ core_get_memory(&mem_info);
+
+ /* Send basic sync info */
+ cmd[0] = htonl(NETPLAY_CMD_SYNC);
+ cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) +
+ NETPLAY_NICK_LEN + mem_info.size);
+ cmd[2] = htonl(netplay->self_frame_count);
+ connected_players = netplay->connected_players;
+ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
+ connected_players |= 1<self_player;
+ if (netplay->local_paused || netplay->remote_paused)
+ connected_players |= NETPLAY_CMD_SYNC_BIT_PAUSED;
+ cmd[3] = htonl(connected_players);
+ if (netplay->flip)
+ cmd[4] = htonl(netplay->flip_frame);
+ else
+ cmd[4] = htonl(0);
+
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd,
+ sizeof(cmd)))
+ return false;
+
+ /* Now send the device info */
+ for (i = 0; i < MAX_USERS; i++)
+ {
+ device = htonl(settings->input.libretro_device[i]);
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd,
+ &device, sizeof(device)))
+ return false;
+ }
+
+ /* Now see if we need to mangle their nick */
+ nicklen = strlen(connection->nick);
+ if (nicklen > NETPLAY_NICK_LEN - 5)
+ nickmangle = NETPLAY_NICK_LEN - 5;
+ else
+ nickmangle = nicklen;
+ matchct = 1;
+ do {
+ nick_matched = false;
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *sc = &netplay->connections[i];
+ if (sc == connection) continue;
+ if (sc->active &&
+ sc->mode >= NETPLAY_CONNECTION_CONNECTED &&
+ !strncmp(connection->nick, sc->nick, NETPLAY_NICK_LEN))
+ {
+ nick_matched = true;
+ break;
+ }
+ }
+ if (!strncmp(connection->nick, netplay->nick, NETPLAY_NICK_LEN))
+ nick_matched = true;
+
+ if (nick_matched)
+ {
+ /* Somebody has this nick, make a new one! */
+ snprintf(connection->nick + nickmangle, NETPLAY_NICK_LEN - nickmangle, " (%d)", ++matchct);
+ connection->nick[NETPLAY_NICK_LEN - 1] = '\0';
+ }
+ } while (nick_matched);
+
+ /* Send the nick */
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd,
+ connection->nick, NETPLAY_NICK_LEN))
+ return false;
+
+ /* And finally, the SRAM */
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd,
+ mem_info.data, mem_info.size) ||
+ !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);
+
+ return true;
+}
+
+/**
+ * netplay_handshake_pre_nick
+ *
+ * Data receiver for the second stage of handshake, receiving the other side's
+ * nickname.
+ */
+bool netplay_handshake_pre_nick(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ struct nick_buf_s nick_buf;
+ ssize_t recvd;
+ char msg[512];
+
+ msg[0] = '\0';
+
+ RECV(&nick_buf, sizeof(nick_buf));
+
+ /* Expecting only a nick command */
+ if (recvd < 0 ||
+ ntohl(nick_buf.cmd[0]) != NETPLAY_CMD_NICK ||
+ ntohl(nick_buf.cmd[1]) != sizeof(nick_buf.nick))
+ {
+ if (netplay->is_server)
+ strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT),
+ sizeof(msg));
+ else
+ strlcpy(msg,
+ msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST),
+ sizeof(msg));
+ RARCH_ERR("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ return false;
+ }
+
+ strlcpy(connection->nick, nick_buf.nick,
+ (sizeof(connection->nick) < sizeof(nick_buf.nick)) ?
+ sizeof(connection->nick) : sizeof(nick_buf.nick));
+
+ if (netplay->is_server)
+ {
+ if (netplay->play_password[0] || netplay->spectate_password[0])
+ {
+ /* There's a password, so just put them in PRE_PASSWORD mode */
+ connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD;
+ }
+ else
+ {
+ connection->can_play = true;
+ if (!netplay_handshake_info(netplay, connection))
+ return false;
+ connection->mode = NETPLAY_CONNECTION_PRE_INFO;
+ }
+
+ }
+ else
+ {
+ /* Client needs to wait for INFO */
+ connection->mode = NETPLAY_CONNECTION_PRE_INFO;
+
+ }
+
+ *had_input = true;
+ netplay_recv_flush(&connection->recv_packet_buffer);
+ return true;
+}
+
+/**
+ * netplay_handshake_pre_password
+ *
+ * Data receiver for the third, optional stage of server handshake, receiving
+ * the password and sending core/content info.
+ */
+bool netplay_handshake_pre_password(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ struct password_buf_s password_buf, corr_password_buf;
+ char password[8+NETPLAY_PASS_LEN]; /* 8 for salt */
+ ssize_t recvd;
+ char msg[512];
+ bool correct;
+
+ msg[0] = '\0';
+
+ RECV(&password_buf, sizeof(password_buf));
+
+ /* Expecting only a password command */
+ if (recvd < 0 ||
+ ntohl(password_buf.cmd[0]) != NETPLAY_CMD_PASSWORD ||
+ ntohl(password_buf.cmd[1]) != sizeof(password_buf.password))
+ {
+ if (netplay->is_server)
+ strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT),
+ sizeof(msg));
+ else
+ strlcpy(msg,
+ msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST),
+ sizeof(msg));
+ RARCH_ERR("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ return false;
+ }
+
+ /* Calculate the correct password hash(es) and compare */
+ correct = false;
+ snprintf(password, sizeof(password), "%08X", connection->salt);
+ if (netplay->play_password[0])
+ {
+ strlcpy(password + 8, netplay->play_password, sizeof(password)-8);
+ sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password));
+ if (!memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password)))
+ {
+ correct = true;
+ connection->can_play = true;
+ }
+ }
+ if (netplay->spectate_password[0])
+ {
+ strlcpy(password + 8, netplay->spectate_password, sizeof(password)-8);
+ sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password));
+ if (!memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password)))
+ correct = true;
+ }
+
+ /* Just disconnect if it was wrong */
+ if (!correct)
+ return false;
+
+ /* Otherwise, exchange info */
+ if (!netplay_handshake_info(netplay, connection))
+ return false;
+
+ *had_input = true;
+ connection->mode = NETPLAY_CONNECTION_PRE_INFO;
+ netplay_recv_flush(&connection->recv_packet_buffer);
+ return true;
+}
+
+/**
+ * netplay_handshake_pre_info
+ *
+ * Data receiver for the third stage of server handshake, receiving
+ * the password.
+ */
+bool netplay_handshake_pre_info(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ struct info_buf_s info_buf;
+ uint32_t cmd_size;
+ ssize_t recvd;
+ rarch_system_info_t *core_info;
+ uint32_t *content_crc_ptr;
+ const char *dmsg = NULL;
+
+ RECV(&info_buf, sizeof(info_buf));
+
+ if (recvd < 0 ||
+ ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO)
+ {
+ RARCH_ERR("Failed to receive netplay info.\n");
+ return false;
+ }
+
+ cmd_size = ntohl(info_buf.cmd[1]);
+ if (cmd_size != sizeof(info_buf) - 2*sizeof(uint32_t))
+ {
+ /* Either the host doesn't have anything loaded, or this is just screwy */
+ if (cmd_size != 0)
+ {
+ /* Huh? */
+ RARCH_ERR("Invalid NETPLAY_CMD_INFO payload size.\n");
+ return false;
+ }
+
+ /* Send our info and hope they load it! */
+ if (!netplay_handshake_info(netplay, connection))
+ return false;
+
+ *had_input = true;
+ netplay_recv_flush(&connection->recv_packet_buffer);
+ return true;
+ }
+
+ /* Check the core info */
+ core_info = NULL;
+ runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &core_info);
+ if (core_info)
+ {
+ if (strncmp(info_buf.core_name, core_info->info.library_name, sizeof(info_buf.core_name)) ||
+ strncmp(info_buf.core_version, core_info->info.library_version, sizeof(info_buf.core_version)))
+ {
+ dmsg = msg_hash_to_str(MSG_NETPLAY_IMPLEMENTATIONS_DIFFER);
+ goto error;
+ }
+ }
+
+ /* Check the content CRC */
+ content_crc_ptr = NULL;
+ content_get_crc(&content_crc_ptr);
+ if (content_crc_ptr)
+ {
+ if (ntohl(info_buf.content_crc) != *content_crc_ptr)
+ {
+ dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER);
+ goto error;
+ }
+ }
+
+ /* Now switch to the right mode */
+ if (netplay->is_server)
+ {
+ if (!netplay_handshake_sync(netplay, connection))
+ return false;
+
+ }
+ else
+ {
+ if (!netplay_handshake_info(netplay, connection))
+ return false;
+ connection->mode = NETPLAY_CONNECTION_PRE_SYNC;
+ }
+
+ *had_input = true;
+ netplay_recv_flush(&connection->recv_packet_buffer);
+ return true;
+
+error:
+ if (dmsg)
+ {
+ RARCH_ERR("%s\n", dmsg);
+ runloop_msg_queue_push(dmsg, 1, 180, false);
+ }
+ return false;
+}
+/**
+ * netplay_handshake_pre_sync
+ *
+ * Data receiver for the client's third handshake stage, receiving the
+ * synchronization information.
+ */
+bool netplay_handshake_pre_sync(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ uint32_t cmd[2];
+ uint32_t new_frame_count, connected_players, flip_frame;
+ uint32_t device;
+ uint32_t local_sram_size, remote_sram_size;
+ size_t i;
+ ssize_t recvd;
+ settings_t *settings = config_get_ptr();
+ retro_ctx_controller_info_t pad;
+ char new_nick[NETPLAY_NICK_LEN];
+ retro_ctx_memory_info_t mem_info;
+
+ RECV(cmd, sizeof(cmd))
+ {
+ const char *msg = msg_hash_to_str(MSG_NETPLAY_INCORRECT_PASSWORD);
+ RARCH_ERR("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ return false;
+ }
+
+ /* Only expecting a sync command */
+ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC ||
+ ntohl(cmd[1]) < 3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) +
+ NETPLAY_NICK_LEN)
+ {
+ RARCH_ERR("%s\n",
+ msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
+ return false;
+ }
+
+ /* Get the frame count */
+ RECV(&new_frame_count, sizeof(new_frame_count))
+ return false;
+ new_frame_count = ntohl(new_frame_count);
+
+ /* Get the connected players and pause mode */
+ RECV(&connected_players, sizeof(connected_players))
+ return false;
+ connected_players = ntohl(connected_players);
+ if (connected_players & NETPLAY_CMD_SYNC_BIT_PAUSED)
+ {
+ netplay->remote_paused = true;
+ connected_players ^= NETPLAY_CMD_SYNC_BIT_PAUSED;
+ }
+ netplay->connected_players = connected_players;
+
+ /* And the flip state */
+ RECV(&flip_frame, sizeof(flip_frame))
+ return false;
+ flip_frame = ntohl(flip_frame);
+ netplay->flip = !!flip_frame;
+ netplay->flip_frame = flip_frame;
+
+ /* Set our frame counters as requested */
+ netplay->self_frame_count = netplay->other_frame_count =
+ netplay->unread_frame_count = netplay->server_frame_count =
+ new_frame_count;
+ for (i = 0; i < netplay->buffer_size; i++)
+ {
+ struct delta_frame *ptr = &netplay->buffer[i];
+ ptr->used = false;
+
+ if (i == netplay->self_ptr)
+ {
+ /* Clear out any current data but still use this frame */
+ netplay_delta_frame_ready(netplay, ptr, 0);
+ ptr->frame = new_frame_count;
+ ptr->have_local = true;
+ netplay->other_ptr = netplay->unread_ptr = netplay->server_ptr = i;
+
+ }
+ }
+ for (i = 0; i < MAX_USERS; i++)
+ {
+ if (connected_players & (1<read_ptr[i] = netplay->self_ptr;
+ netplay->read_frame_count[i] = netplay->self_frame_count;
+ }
+ }
+
+ /* Get and set each pad */
+ for (i = 0; i < MAX_USERS; i++)
+ {
+ RECV(&device, sizeof(device))
+ return false;
+ pad.port = i;
+ pad.device = ntohl(device);
+ core_set_controller_port_device(&pad);
+ }
+
+ /* Get our nick */
+ RECV(new_nick, NETPLAY_NICK_LEN)
+ return false;
+ if (strncmp(netplay->nick, new_nick, NETPLAY_NICK_LEN))
+ {
+ char msg[512];
+ strlcpy(netplay->nick, new_nick, NETPLAY_NICK_LEN);
+ snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_NETPLAY_CHANGED_NICK), netplay->nick);
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ }
+
+ /* Now check the SRAM */
+ mem_info.id = RETRO_MEMORY_SAVE_RAM;
+ core_get_memory(&mem_info);
+
+ local_sram_size = mem_info.size;
+ remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t) -
+ MAX_USERS*sizeof(uint32_t) - NETPLAY_NICK_LEN;
+
+ if (local_sram_size != 0 && local_sram_size == remote_sram_size)
+ {
+ RECV(mem_info.data, local_sram_size)
+ {
+ RARCH_ERR("%s\n",
+ msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
+ return false;
+ }
+
+ }
+ else if (remote_sram_size != 0)
+ {
+ /* We can't load this, but we still need to get rid of the data */
+ uint32_t quickbuf;
+ while (remote_sram_size > 0)
+ {
+ RECV(&quickbuf, (remote_sram_size > sizeof(uint32_t)) ? sizeof(uint32_t) : remote_sram_size)
+ {
+ RARCH_ERR("%s\n",
+ msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
+ return false;
+ }
+ if (remote_sram_size > sizeof(uint32_t))
+ remote_sram_size -= sizeof(uint32_t);
+ else
+ remote_sram_size = 0;
+ }
+
+ }
+
+ /* We're ready! */
+ *had_input = true;
+ netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;
+ connection->mode = NETPLAY_CONNECTION_PLAYING;
+ netplay_handshake_ready(netplay, connection);
+ netplay_recv_flush(&connection->recv_packet_buffer);
+
+ /* Ask to go to player mode */
+ return netplay_cmd_mode(netplay, connection, NETPLAY_CONNECTION_PLAYING);
+}
+
+/**
+ * netplay_handshake
+ *
+ * Data receiver for all handshake states.
+ */
+bool netplay_handshake(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ bool ret;
+
+ switch (connection->mode)
+ {
+ case NETPLAY_CONNECTION_NONE:
+ /* Huh?! */
+ return false;
+ case NETPLAY_CONNECTION_INIT:
+ ret = netplay_handshake_init(netplay, connection, had_input);
+ break;
+ case NETPLAY_CONNECTION_PRE_NICK:
+ ret = netplay_handshake_pre_nick(netplay, connection, had_input);
+ break;
+ case NETPLAY_CONNECTION_PRE_PASSWORD:
+ ret = netplay_handshake_pre_password(netplay, connection, had_input);
+ break;
+ case NETPLAY_CONNECTION_PRE_INFO:
+ ret = netplay_handshake_pre_info(netplay, connection, had_input);
+ break;
+ case NETPLAY_CONNECTION_PRE_SYNC:
+ ret = netplay_handshake_pre_sync(netplay, connection, had_input);
+ break;
+ default:
+ return false;
+ }
+
+ if (connection->mode >= NETPLAY_CONNECTION_CONNECTED &&
+ !netplay_send_cur_input(netplay, connection))
+ return false;
+
+ return ret;
+}
diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c
new file mode 100644
index 0000000000..63609a10a4
--- /dev/null
+++ b/network/netplay/netplay_init.c
@@ -0,0 +1,558 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2016 - Daniel De Matteis
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#if defined(_MSC_VER) && !defined(_XBOX)
+#pragma comment(lib, "ws2_32")
+#endif
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "netplay_private.h"
+
+#include "netplay_discovery.h"
+
+#include "../../autosave.h"
+#include "../../runloop.h"
+
+#if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY)
+#define HAVE_INET6 1
+#endif
+
+static int init_tcp_connection(const struct addrinfo *res,
+ bool server,
+ struct sockaddr *other_addr, socklen_t addr_size)
+{
+ bool ret = true;
+ int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+
+ if (fd < 0)
+ {
+ ret = false;
+ goto end;
+ }
+
+#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
+ {
+ int flag = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
+#ifdef _WIN32
+ (const char*)
+#else
+ (const void*)
+#endif
+ &flag,
+ sizeof(int)) < 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(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 (socket_connect(fd, (void*)res, false) < 0)
+ {
+ ret = false;
+ goto end;
+ }
+ }
+ else
+ {
+#if defined(HAVE_INET6) && defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
+ /* Make sure we accept connections on both IPv6 and IPv4 */
+ int on = 0;
+ if (res->ai_family == AF_INET6)
+ {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&on, sizeof(on)) < 0)
+ RARCH_WARN("Failed to listen on both IPv6 and IPv4\n");
+ }
+#endif
+ if ( !socket_bind(fd, (void*)res) ||
+ listen(fd, 1024) < 0)
+ {
+ ret = false;
+ goto end;
+ }
+ }
+
+end:
+ if (!ret && fd >= 0)
+ {
+ socket_close(fd);
+ fd = -1;
+ }
+
+ return fd;
+}
+
+static bool init_tcp_socket(netplay_t *netplay, void *direct_host,
+ const char *server, uint16_t port)
+{
+ char port_buf[16];
+ bool ret = false;
+ const struct addrinfo *tmp_info = NULL;
+ struct addrinfo *res = NULL;
+ struct addrinfo hints = {0};
+
+ port_buf[0] = '\0';
+
+ if (!direct_host)
+ {
+#ifdef HAVE_INET6
+ /* Default to hosting on IPv6 and IPv4 */
+ if (!server)
+ hints.ai_family = AF_INET6;
+#endif
+ hints.ai_socktype = SOCK_STREAM;
+ if (!server)
+ hints.ai_flags = AI_PASSIVE;
+
+ snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
+ if (getaddrinfo_retro(server, port_buf, &hints, &res) < 0)
+ {
+#ifdef HAVE_INET6
+ if (!server)
+ {
+ /* Didn't work with IPv6, try wildcard */
+ hints.ai_family = 0;
+ if (getaddrinfo_retro(server, port_buf, &hints, &res) < 0)
+ return false;
+ }
+ else
+#endif
+ return false;
+ }
+
+ if (!res)
+ return false;
+
+ }
+ else
+ {
+ /* I'll build my own addrinfo! With blackjack and hookers! */
+ 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;
+
+ }
+
+ /* If we're serving on IPv6, make sure we accept all connections, including
+ * IPv4 */
+#ifdef HAVE_INET6
+ if (!direct_host && !server && res->ai_family == AF_INET6)
+ {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) res->ai_addr;
+ sin6->sin6_addr = in6addr_any;
+ }
+#endif
+
+ /* If "localhost" is used, it is important to check every possible
+ * address for IPv4/IPv6. */
+ tmp_info = res;
+
+ while (tmp_info)
+ {
+ struct sockaddr_storage sad;
+ int fd = init_tcp_connection(
+ tmp_info,
+ direct_host || server,
+ (struct sockaddr*)&sad,
+ sizeof(sad));
+
+ if (fd >= 0)
+ {
+ ret = true;
+ if (direct_host || server)
+ {
+ netplay->connections[0].active = true;
+ netplay->connections[0].fd = fd;
+ netplay->connections[0].addr = sad;
+ }
+ else
+ {
+ netplay->listen_fd = fd;
+ }
+ break;
+ }
+
+ tmp_info = tmp_info->ai_next;
+ }
+
+ if (res && !direct_host)
+ freeaddrinfo_retro(res);
+
+ if (!ret)
+ RARCH_ERR("Failed to set up netplay sockets.\n");
+
+ return ret;
+}
+
+static bool init_socket(netplay_t *netplay, void *direct_host, const char *server, uint16_t port)
+{
+ if (!network_init())
+ return false;
+
+ if (!init_tcp_socket(netplay, direct_host, server, port))
+ return false;
+
+ if (netplay->is_server && netplay->nat_traversal)
+ netplay_init_nat_traversal(netplay);
+
+ return true;
+}
+
+static bool netplay_init_socket_buffers(netplay_t *netplay)
+{
+ /* Make our packet buffer big enough for a save state and stall-frames-many
+ * frames of input data, plus the headers for each of them */
+ size_t i;
+ size_t packet_buffer_size = netplay->zbuffer_size +
+ NETPLAY_MAX_STALL_FRAMES * WORDS_PER_FRAME + (NETPLAY_MAX_STALL_FRAMES+1)*3;
+ netplay->packet_buffer_size = packet_buffer_size;
+
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active)
+ {
+ if (connection->send_packet_buffer.data)
+ {
+ if (!netplay_resize_socket_buffer(&connection->send_packet_buffer,
+ packet_buffer_size) ||
+ !netplay_resize_socket_buffer(&connection->recv_packet_buffer,
+ packet_buffer_size))
+ return false;
+ }
+ else
+ {
+ if (!netplay_init_socket_buffer(&connection->send_packet_buffer,
+ packet_buffer_size) ||
+ !netplay_init_socket_buffer(&connection->recv_packet_buffer,
+ packet_buffer_size))
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool netplay_init_serialization(netplay_t *netplay)
+{
+ unsigned i;
+ retro_ctx_size_info_t info;
+
+ if (netplay->state_size)
+ return true;
+
+ core_serialize_size(&info);
+
+ if (!info.size)
+ return false;
+
+ netplay->state_size = info.size;
+
+ for (i = 0; i < netplay->buffer_size; i++)
+ {
+ netplay->buffer[i].state = calloc(netplay->state_size, 1);
+
+ if (!netplay->buffer[i].state)
+ {
+ netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
+ return false;
+ }
+ }
+
+ netplay->zbuffer_size = netplay->state_size * 2;
+ netplay->zbuffer = (uint8_t *) calloc(netplay->zbuffer_size, 1);
+ if (!netplay->zbuffer)
+ {
+ netplay->quirks |= NETPLAY_QUIRK_NO_TRANSMISSION;
+ netplay->zbuffer_size = 0;
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * netplay_try_init_serialization
+ *
+ * Try to initialize serialization. For quirky cores.
+ *
+ * Returns true if serialization is now ready, false otherwise.
+ */
+bool netplay_try_init_serialization(netplay_t *netplay)
+{
+ retro_ctx_serialize_info_t serial_info;
+ size_t packet_buffer_size;
+
+ if (netplay->state_size)
+ return true;
+
+ if (!netplay_init_serialization(netplay))
+ return false;
+
+ /* Check if we can actually save */
+ serial_info.data_const = NULL;
+ serial_info.data = netplay->buffer[netplay->self_ptr].state;
+ serial_info.size = netplay->state_size;
+
+ if (!core_serialize(&serial_info))
+ return false;
+
+ /* Once initialized, we no longer exhibit this quirk */
+ netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION);
+
+ return netplay_init_socket_buffers(netplay);
+}
+
+/**
+ * netplay_wait_and_init_serialization
+ *
+ * Try very hard to initialize serialization, simulating multiple frames if
+ * necessary. For quirky cores.
+ *
+ * Returns true if serialization is now ready, false otherwise.
+ */
+bool netplay_wait_and_init_serialization(netplay_t *netplay)
+{
+ int frame;
+
+ if (netplay->state_size)
+ return true;
+
+ /* Wait a maximum of 60 frames */
+ for (frame = 0; frame < 60; frame++) {
+ if (netplay_try_init_serialization(netplay))
+ return true;
+
+#if defined(HAVE_THREADS)
+ autosave_lock();
+#endif
+ core_run();
+#if defined(HAVE_THREADS)
+ autosave_unlock();
+#endif
+ }
+
+ return false;
+}
+
+static bool netplay_init_buffers(netplay_t *netplay)
+{
+ size_t packet_buffer_size;
+
+ if (!netplay)
+ return false;
+
+ /* Enough to get ahead or behind by MAX_STALL_FRAMES frames */
+ netplay->buffer_size = NETPLAY_MAX_STALL_FRAMES + 1;
+
+ /* If we're the server, we need enough to get ahead AND behind by
+ * MAX_STALL_FRAMES frame */
+ if (netplay->is_server)
+ netplay->buffer_size *= 2;
+
+ netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size,
+ sizeof(*netplay->buffer));
+
+ if (!netplay->buffer)
+ return false;
+
+ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_INITIALIZATION)))
+ netplay_init_serialization(netplay);
+
+ return netplay_init_socket_buffers(netplay);
+}
+
+/**
+ * netplay_new:
+ * @direct_host : Netplay host discovered from scanning.
+ * @server : IP address of server.
+ * @port : Port of server.
+ * @play_password : Password required to play.
+ * @spectate_password : Password required to connect.
+ * @stateless_mode : Shall we use stateless mode?
+ * @check_frames : Frequency with which to check CRCs.
+ * @cb : Libretro callbacks.
+ * @nat_traversal : If true, attempt NAT traversal.
+ * @nick : Nickname of user.
+ * @quirks : Netplay quirks required for this session.
+ *
+ * Creates a new netplay handle. A NULL server means we're
+ * hosting.
+ *
+ * Returns: new netplay data.
+ */
+netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port,
+ const char *play_password, const char *spectate_password,
+ 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));
+ if (!netplay)
+ return NULL;
+
+ netplay->listen_fd = -1;
+ netplay->tcp_port = port;
+ netplay->cbs = *cb;
+ netplay->connected_players = 0;
+ netplay->player_max = 1;
+ netplay->is_server = server == NULL;
+ netplay->nat_traversal = netplay->is_server ? 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->self_mode = netplay->is_server ?
+ NETPLAY_CONNECTION_PLAYING :
+ NETPLAY_CONNECTION_NONE;
+
+ if (netplay->is_server)
+ {
+ netplay->connections = NULL;
+ netplay->connections_size = 0;
+ }
+ else
+ {
+ netplay->connections = &netplay->one_connection;
+ netplay->connections_size = 1;
+ netplay->connections[0].fd = -1;
+ }
+
+ strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick));
+ strlcpy(netplay->play_password, play_password ? play_password : "", sizeof(netplay->play_password));
+ strlcpy(netplay->spectate_password, spectate_password ? spectate_password : "", sizeof(netplay->spectate_password));
+
+ if (!init_socket(netplay, direct_host, server, port))
+ {
+ free(netplay);
+ return NULL;
+ }
+
+ if (!netplay_init_buffers(netplay))
+ {
+ free(netplay);
+ return NULL;
+ }
+
+ if (!netplay->is_server)
+ {
+ netplay_handshake_init_send(netplay, &netplay->connections[0]);
+ netplay->connections[0].mode = 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 error;
+ }
+ else
+ {
+ if (!socket_nonblock(netplay->connections[0].fd))
+ goto error;
+ }
+
+ return netplay;
+
+error:
+ if (netplay->listen_fd >= 0)
+ socket_close(netplay->listen_fd);
+
+ if (netplay->connections && netplay->connections[0].fd >= 0)
+ socket_close(netplay->connections[0].fd);
+
+ free(netplay);
+ return NULL;
+}
+
+/**
+ * netplay_free
+ * @netplay : pointer to netplay object
+ *
+ * Frees netplay data/
+ */
+void netplay_free(netplay_t *netplay)
+{
+ size_t i;
+
+ if (netplay->listen_fd >= 0)
+ socket_close(netplay->listen_fd);
+
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active)
+ {
+ socket_close(connection->fd);
+ netplay_deinit_socket_buffer(&connection->send_packet_buffer);
+ netplay_deinit_socket_buffer(&connection->recv_packet_buffer);
+ }
+ }
+
+ if (netplay->connections && netplay->connections != &netplay->one_connection)
+ free(netplay->connections);
+
+ if (netplay->nat_traversal)
+ natt_free(&netplay->nat_traversal_state);
+
+ if (netplay->buffer)
+ {
+ for (i = 0; i < netplay->buffer_size; i++)
+ if (netplay->buffer[i].state)
+ free(netplay->buffer[i].state);
+
+ free(netplay->buffer);
+ }
+
+ if (netplay->zbuffer)
+ free(netplay->zbuffer);
+
+ if (netplay->compress_nil.compression_stream)
+ {
+ netplay->compress_nil.compression_backend->stream_free(netplay->compress_nil.compression_stream);
+ netplay->compress_nil.decompression_backend->stream_free(netplay->compress_nil.decompression_stream);
+ }
+ if (netplay->compress_zlib.compression_stream)
+ {
+ netplay->compress_zlib.compression_backend->stream_free(netplay->compress_zlib.compression_stream);
+ netplay->compress_zlib.decompression_backend->stream_free(netplay->compress_zlib.decompression_stream);
+ }
+
+ if (netplay->addr)
+ freeaddrinfo_retro(netplay->addr);
+
+ free(netplay);
+}
diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c
new file mode 100644
index 0000000000..cb92a16eff
--- /dev/null
+++ b/network/netplay/netplay_io.c
@@ -0,0 +1,1371 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2016 - Daniel De Matteis
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "netplay_private.h"
+
+#include "../../runloop.h"
+
+#if 0
+#define DEBUG_NETPLAY_STEPS 1
+
+static void print_state(netplay_t *netplay)
+{
+ char msg[512];
+ size_t cur = 0;
+ uint32_t player;
+
+#define APPEND(out) cur += snprintf out
+#define M msg + cur, sizeof(msg) - cur
+
+ APPEND((M, "NETPLAY: S:%u U:%u O:%u", netplay->self_frame_count, netplay->unread_frame_count, netplay->other_frame_count));
+ if (!netplay->is_server)
+ APPEND((M, " H:%u", netplay->server_frame_count));
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if ((netplay->connected_players & (1<read_frame_count[player]));
+ }
+ APPEND((M, "\n"));
+ msg[sizeof(msg)-1] = '\0';
+
+ RARCH_LOG("%s\n", msg);
+
+#undef APPEND
+#undef M
+}
+#endif
+
+/**
+ * remote_unpaused
+ *
+ * Mark a particular remote connection as unpaused and, if relevant, inform
+ * every one else that they may resume.
+ */
+static void remote_unpaused(netplay_t *netplay, struct netplay_connection *connection)
+{
+ size_t i;
+ connection->paused = false;
+ netplay->remote_paused = false;
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *sc = &netplay->connections[i];
+ if (sc->active && sc->paused)
+ {
+ netplay->remote_paused = true;
+ break;
+ }
+ }
+ if (!netplay->remote_paused && !netplay->local_paused)
+ netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_RESUME, NULL, 0);
+}
+
+/**
+ * netplay_hangup:
+ *
+ * Disconnects an active Netplay connection due to an error
+ */
+void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection)
+{
+ char msg[512];
+ const char *dmsg;
+
+ if (!netplay)
+ return;
+ if (!connection->active)
+ return;
+
+ msg[0] = msg[sizeof(msg)-1] = '\0';
+ dmsg = msg;
+
+ /* Report this disconnection */
+ if (netplay->is_server)
+ {
+ if (connection->nick[0])
+ snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_SERVER_NAMED_HANGUP), connection->nick);
+ else
+ dmsg = msg_hash_to_str(MSG_NETPLAY_SERVER_HANGUP);
+ }
+ else
+ {
+ dmsg = msg_hash_to_str(MSG_NETPLAY_CLIENT_HANGUP);
+ }
+ RARCH_LOG("%s\n", dmsg);
+ runloop_msg_queue_push(dmsg, 1, 180, false);
+
+ socket_close(connection->fd);
+ connection->active = false;
+ netplay_deinit_socket_buffer(&connection->send_packet_buffer);
+ netplay_deinit_socket_buffer(&connection->recv_packet_buffer);
+
+ if (!netplay->is_server)
+ {
+ netplay->self_mode = NETPLAY_CONNECTION_NONE;
+ netplay->connected_players = 0;
+ netplay->stall = NETPLAY_STALL_NONE;
+
+ }
+ else
+ {
+ /* Remove this player */
+ if (connection->mode == NETPLAY_CONNECTION_PLAYING)
+ {
+ netplay->connected_players &= ~(1<player);
+
+ /* FIXME: Duplication */
+ if (netplay->is_server)
+ {
+ uint32_t payload[2];
+ payload[0] = htonl(netplay->read_frame_count[connection->player]);
+ payload[1] = htonl(connection->player);
+ netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
+ }
+ }
+
+ }
+
+ /* Unpause them */
+ if (connection->paused)
+ remote_unpaused(netplay, connection);
+}
+
+/* Send the specified input data */
+static bool send_input_frame(netplay_t *netplay,
+ struct netplay_connection *only, struct netplay_connection *except,
+ uint32_t frame, uint32_t player, uint32_t *state)
+{
+ uint32_t buffer[2 + WORDS_PER_FRAME];
+ size_t i;
+
+ buffer[0] = htonl(NETPLAY_CMD_INPUT);
+ buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t));
+ buffer[2] = htonl(frame);
+ buffer[3] = htonl(player);
+ buffer[4] = htonl(state[0]);
+ buffer[5] = htonl(state[1]);
+ buffer[6] = htonl(state[2]);
+
+ if (only)
+ {
+ if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer)))
+ {
+ netplay_hangup(netplay, only);
+ return false;
+ }
+ }
+ else
+ {
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection == except) continue;
+ if (connection->active &&
+ connection->mode >= NETPLAY_CONNECTION_CONNECTED &&
+ (connection->mode != NETPLAY_CONNECTION_PLAYING ||
+ connection->player != player))
+ {
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd,
+ buffer, sizeof(buffer)))
+ netplay_hangup(netplay, connection);
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * netplay_send_cur_input
+ *
+ * Send the current input frame to a given connection.
+ *
+ * Returns true if successful, false otherwise.
+ */
+bool netplay_send_cur_input(netplay_t *netplay,
+ struct netplay_connection *connection)
+{
+ struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr];
+ uint32_t player;
+
+ if (netplay->is_server)
+ {
+ /* Send the other players' input data */
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if (connection->mode == NETPLAY_CONNECTION_PLAYING &&
+ connection->player == player)
+ continue;
+ if ((netplay->connected_players & (1<have_real[player])
+ {
+ if (!send_input_frame(netplay, connection, NULL,
+ netplay->self_frame_count, player,
+ dframe->real_input_state[player]))
+ return false;
+ }
+ }
+ }
+
+ /* If we're not playing, send a NOINPUT */
+ if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING)
+ {
+ uint32_t payload = htonl(netplay->self_frame_count);
+ if (!netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NOINPUT,
+ &payload, sizeof(payload)))
+ return false;
+ }
+
+ }
+
+ /* Send our own data */
+ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
+ {
+ if (!send_input_frame(netplay, connection, NULL,
+ netplay->self_frame_count,
+ (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player,
+ dframe->self_state))
+ return false;
+ }
+
+ if (!netplay_send_flush(&connection->send_packet_buffer, connection->fd,
+ false))
+ return false;
+
+ return true;
+}
+
+/**
+ * netplay_send_raw_cmd
+ *
+ * Send a raw Netplay command to the given connection.
+ *
+ * Returns true on success, false on failure.
+ */
+bool netplay_send_raw_cmd(netplay_t *netplay,
+ struct netplay_connection *connection, uint32_t cmd, const void *data,
+ size_t size)
+{
+ uint32_t cmdbuf[2];
+
+ cmdbuf[0] = htonl(cmd);
+ cmdbuf[1] = htonl(size);
+
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmdbuf,
+ sizeof(cmdbuf)))
+ return false;
+
+ if (size > 0)
+ if (!netplay_send(&connection->send_packet_buffer, connection->fd, data, size))
+ return false;
+
+ return true;
+}
+
+/**
+ * netplay_send_raw_cmd_all
+ *
+ * Send a raw Netplay command to all connections, optionally excluding one
+ * (typically the client that the relevant command came from)
+ */
+void netplay_send_raw_cmd_all(netplay_t *netplay,
+ struct netplay_connection *except, uint32_t cmd, const void *data,
+ size_t size)
+{
+ size_t i;
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection == except)
+ continue;
+ if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED)
+ {
+ if (!netplay_send_raw_cmd(netplay, connection, cmd, data, size))
+ netplay_hangup(netplay, connection);
+ }
+ }
+}
+
+static bool netplay_cmd_nak(netplay_t *netplay,
+ struct netplay_connection *connection)
+{
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NAK, NULL, 0);
+ return false;
+}
+
+/**
+ * netplay_cmd_crc
+ *
+ * Send a CRC command to all active clients.
+ */
+bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta)
+{
+ uint32_t payload[2];
+ bool success = true;
+ size_t i;
+ payload[0] = htonl(delta->frame);
+ payload[1] = htonl(delta->crc);
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ if (netplay->connections[i].active &&
+ netplay->connections[i].mode >= NETPLAY_CONNECTION_CONNECTED)
+ success = netplay_send_raw_cmd(netplay, &netplay->connections[i],
+ NETPLAY_CMD_CRC, payload, sizeof(payload)) && success;
+ }
+ return success;
+}
+
+/**
+ * netplay_cmd_request_savestate
+ *
+ * Send a savestate request command.
+ */
+bool netplay_cmd_request_savestate(netplay_t *netplay)
+{
+ if (netplay->connections_size == 0 ||
+ !netplay->connections[0].active ||
+ netplay->connections[0].mode < NETPLAY_CONNECTION_CONNECTED)
+ return false;
+ if (netplay->savestate_request_outstanding)
+ return true;
+ netplay->savestate_request_outstanding = true;
+ return netplay_send_raw_cmd(netplay, &netplay->connections[0],
+ NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0);
+}
+
+/**
+ * netplay_cmd_mode
+ *
+ * Send a mode request command to either play or spectate.
+ */
+bool netplay_cmd_mode(netplay_t *netplay,
+ struct netplay_connection *connection,
+ enum rarch_netplay_connection_mode mode)
+{
+ uint32_t cmd;
+ switch (mode)
+ {
+ case NETPLAY_CONNECTION_SPECTATING:
+ cmd = NETPLAY_CMD_SPECTATE;
+ break;
+
+ case NETPLAY_CONNECTION_PLAYING:
+ cmd = NETPLAY_CMD_PLAY;
+ break;
+
+ default:
+ return false;
+ }
+ return netplay_send_raw_cmd(netplay, connection, cmd, NULL, 0);
+}
+
+static bool netplay_get_cmd(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input)
+{
+ uint32_t cmd;
+ uint32_t flip_frame;
+ uint32_t cmd_size;
+ ssize_t recvd;
+ char msg[512];
+
+ /* We don't handle the initial handshake here */
+ if (connection->mode < NETPLAY_CONNECTION_CONNECTED)
+ return netplay_handshake(netplay, connection, had_input);
+
+#define RECV(buf, sz) \
+ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \
+ (sz), false); \
+ if (recvd >= 0 && recvd < (sz)) goto shrt; \
+ else if (recvd < 0)
+
+ RECV(&cmd, sizeof(cmd))
+ return false;
+
+ cmd = ntohl(cmd);
+
+ RECV(&cmd_size, sizeof(cmd_size))
+ return false;
+
+ cmd_size = ntohl(cmd_size);
+
+ netplay->timeout_cnt = 0;
+
+ switch (cmd)
+ {
+ case NETPLAY_CMD_ACK:
+ /* Why are we even bothering? */
+ break;
+
+ case NETPLAY_CMD_NAK:
+ /* Disconnect now! */
+ return false;
+
+ case NETPLAY_CMD_INPUT:
+ {
+ uint32_t buffer[WORDS_PER_FRAME];
+ uint32_t player;
+ unsigned i;
+ struct delta_frame *dframe;
+
+ if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t))
+ {
+ RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(buffer, sizeof(buffer))
+ {
+ RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ for (i = 0; i < WORDS_PER_FRAME; i++)
+ buffer[i] = ntohl(buffer[i]);
+
+ if (netplay->is_server)
+ {
+ /* Ignore the claimed player #, must be this client */
+ if (connection->mode != NETPLAY_CONNECTION_PLAYING)
+ {
+ RARCH_ERR("Netplay input from non-participating player.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ player = connection->player;
+ }
+ else
+ {
+ player = buffer[1] & ~NETPLAY_CMD_INPUT_BIT_SERVER;
+ }
+
+ if (player >= MAX_USERS || !(netplay->connected_players & (1<read_frame_count[player])
+ {
+ /* We already had this, so ignore the new transmission */
+ break;
+ }
+ else if (buffer[0] > netplay->read_frame_count[player])
+ {
+ /* Out of order = out of luck */
+ RARCH_ERR("Netplay input out of order.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ /* The data's good! */
+ dframe = &netplay->buffer[netplay->read_ptr[player]];
+ if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player]))
+ {
+ /* Hopefully we'll be ready after another round of input */
+ goto shrt;
+ }
+ memcpy(dframe->real_input_state[player], buffer + 2,
+ WORDS_PER_INPUT*sizeof(uint32_t));
+ dframe->have_real[player] = true;
+ netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]);
+ netplay->read_frame_count[player]++;
+
+ if (netplay->is_server)
+ {
+ /* Forward it on if it's past data*/
+ if (dframe->frame <= netplay->self_frame_count)
+ send_input_frame(netplay, NULL, connection, buffer[0],
+ player, dframe->real_input_state[player]);
+ }
+
+ /* If this was server data, advance our server pointer too */
+ if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER))
+ {
+ netplay->server_ptr = netplay->read_ptr[player];
+ netplay->server_frame_count = netplay->read_frame_count[player];
+ }
+
+#ifdef DEBUG_NETPLAY_STEPS
+ RARCH_LOG("Received input from %u\n", player);
+ print_state(netplay);
+#endif
+ break;
+ }
+
+ case NETPLAY_CMD_NOINPUT:
+ {
+ uint32_t frame;
+
+ if (netplay->is_server)
+ {
+ RARCH_ERR("NETPLAY_CMD_NOINPUT from a client.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(&frame, sizeof(frame))
+ {
+ RARCH_ERR("Failed to receive NETPLAY_CMD_NOINPUT payload.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ frame = ntohl(frame);
+
+ if (frame != netplay->server_frame_count)
+ {
+ RARCH_ERR("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++;
+ break;
+ }
+
+ case NETPLAY_CMD_FLIP_PLAYERS:
+ if (cmd_size != sizeof(uint32_t))
+ {
+ RARCH_ERR("CMD_FLIP_PLAYERS received an unexpected command size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(&flip_frame, sizeof(flip_frame))
+ {
+ RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ if (netplay->is_server)
+ {
+ RARCH_ERR("NETPLAY_CMD_FLIP_PLAYERS from a client.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ flip_frame = ntohl(flip_frame);
+
+ if (flip_frame < netplay->server_frame_count)
+ {
+ RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ netplay->flip ^= true;
+ netplay->flip_frame = flip_frame;
+
+ /* Force a rewind to assure the flip happens: This just prevents us
+ * from skipping other past the flip because our prediction was
+ * correct */
+ if (flip_frame < netplay->self_frame_count)
+ netplay->force_rewind = true;
+
+ RARCH_LOG("%s.\n", msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED));
+ runloop_msg_queue_push(
+ msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED), 1, 180, false);
+
+ break;
+
+ case NETPLAY_CMD_SPECTATE:
+ {
+ uint32_t payload[2];
+
+ if (!netplay->is_server)
+ {
+ RARCH_ERR("NETPLAY_CMD_SPECTATE from a server.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ if (connection->mode == NETPLAY_CONNECTION_PLAYING)
+ {
+ /* The frame we haven't received is their end frame */
+ payload[0] = htonl(netplay->read_frame_count[connection->player]);
+
+ /* Mark them as not playing anymore */
+ connection->mode = NETPLAY_CONNECTION_SPECTATING;
+ netplay->connected_players &= ~(1<player);
+
+ /* Tell everyone */
+ payload[1] = htonl(connection->player);
+ netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
+
+ /* Announce it */
+ msg[sizeof(msg)-1] = '\0';
+ snprintf(msg, sizeof(msg)-1, "Player %d has left", connection->player+1);
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ }
+ else
+ {
+ payload[0] = htonl(0);
+ }
+
+ /* Tell the player even if they were confused */
+ payload[1] = htonl(NETPLAY_CMD_MODE_BIT_YOU | connection->player);
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
+ break;
+ }
+
+ case NETPLAY_CMD_PLAY:
+ {
+ uint32_t payload[2];
+ uint32_t player = 0;
+ payload[0] = htonl(netplay->self_frame_count + 1);
+
+ if (!netplay->is_server)
+ {
+ RARCH_ERR("NETPLAY_CMD_PLAY from a server.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ if (!connection->can_play)
+ {
+ /* Not allowed to play */
+ payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED);
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t));
+ break;
+ }
+
+ /* Find an available player slot */
+ for (player = 0; player <= netplay->player_max; player++)
+ {
+ if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING &&
+ netplay->self_player == player) &&
+ !(netplay->connected_players & (1< netplay->player_max)
+ {
+ /* No slots free! */
+ payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS);
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t));
+ break;
+ }
+
+ if (connection->mode != NETPLAY_CONNECTION_PLAYING)
+ {
+ /* Mark them as playing */
+ connection->mode = NETPLAY_CONNECTION_PLAYING;
+ connection->player = player;
+ netplay->connected_players |= 1<player);
+ netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
+
+ /* Announce it */
+ msg[sizeof(msg)-1] = '\0';
+ snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1);
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+
+ }
+
+ /* Tell the player even if they were confused */
+ payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING |
+ NETPLAY_CMD_MODE_BIT_YOU | connection->player);
+ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
+
+ /* And expect their data */
+ netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr);
+ netplay->read_frame_count[player] = netplay->self_frame_count + 1;
+ break;
+ }
+
+ case NETPLAY_CMD_MODE:
+ {
+ uint32_t payload[2];
+ uint32_t frame, mode, player;
+ size_t ptr;
+ struct delta_frame *dframe;
+
+#define START(which) \
+ do { \
+ ptr = which; \
+ dframe = &netplay->buffer[ptr]; \
+ } while(0)
+#define NEXT() \
+ do { \
+ ptr = NEXT_PTR(ptr); \
+ dframe = &netplay->buffer[ptr]; \
+ } while(0)
+
+ if (cmd_size != sizeof(payload) ||
+ netplay->is_server)
+ {
+ RARCH_ERR("Invalid payload size for NETPLAY_CMD_MODE.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(payload, sizeof(payload))
+ {
+ RARCH_ERR("NETPLAY_CMD_MODE failed to receive payload.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ frame = ntohl(payload[0]);
+
+ /* We're changing past input, so must replay it */
+ if (frame < netplay->self_frame_count)
+ netplay->force_rewind = true;
+
+ mode = ntohl(payload[1]);
+ player = mode & 0xFFFF;
+ if (player >= MAX_USERS)
+ {
+ RARCH_ERR("Received NETPLAY_CMD_MODE for a higher player number than we support.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ if (mode & NETPLAY_CMD_MODE_BIT_YOU)
+ {
+ /* A change to me! */
+ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING)
+ {
+ if (frame != netplay->server_frame_count)
+ {
+ RARCH_ERR("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");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ netplay->self_mode = NETPLAY_CONNECTION_PLAYING;
+ netplay->self_player = player;
+
+ /* Fix up current frame info */
+ if (frame <= netplay->self_frame_count)
+ {
+ /* It wanted past frames, better send 'em! */
+ START(netplay->server_ptr);
+ while (dframe->used && dframe->frame <= netplay->self_frame_count)
+ {
+ memcpy(dframe->real_input_state[player], dframe->self_state, sizeof(dframe->self_state));
+ dframe->have_real[player] = true;
+ send_input_frame(netplay, connection, NULL, dframe->frame, player, dframe->self_state);
+ if (dframe->frame == netplay->self_frame_count) break;
+ NEXT();
+ }
+
+ }
+ else
+ {
+ uint32_t frame_count;
+
+ /* It wants future frames, make sure we don't capture or send intermediate ones */
+ START(netplay->self_ptr);
+ frame_count = netplay->self_frame_count;
+ while (true)
+ {
+ if (!dframe->used)
+ {
+ /* 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");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ }
+
+ memset(dframe->self_state, 0, sizeof(dframe->self_state));
+ memset(dframe->real_input_state[player], 0, sizeof(dframe->self_state));
+ dframe->have_local = true;
+
+ /* Go on to the next delta frame */
+ NEXT();
+ frame_count++;
+
+ if (frame_count >= frame)
+ break;
+ }
+
+ }
+
+ /* Announce it */
+ msg[sizeof(msg)-1] = '\0';
+ snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1);
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+
+#ifdef DEBUG_NETPLAY_STEPS
+ RARCH_LOG("Received mode change self->%u\n", player);
+ print_state(netplay);
+#endif
+
+ }
+ else /* YOU && !PLAYING */
+ {
+ /* 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");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ /* Announce it */
+ strlcpy(msg, "You have left the game", sizeof(msg));
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+
+#ifdef DEBUG_NETPLAY_STEPS
+ RARCH_LOG("Received mode change %u self->spectating\n", netplay->self_player);
+ print_state(netplay);
+#endif
+
+ }
+
+ }
+ else /* !YOU */
+ {
+ /* Somebody else is joining or parting */
+ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING)
+ {
+ if (frame != netplay->server_frame_count)
+ {
+ RARCH_ERR("Received mode change out of order.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ netplay->connected_players |= (1<read_ptr[player] = netplay->server_ptr;
+ netplay->read_frame_count[player] = netplay->server_frame_count;
+
+ /* Announce it */
+ msg[sizeof(msg)-1] = '\0';
+ snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1);
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+
+#ifdef DEBUG_NETPLAY_STEPS
+ RARCH_LOG("Received mode change spectator->%u\n", player);
+ print_state(netplay);
+#endif
+
+ }
+ else
+ {
+ netplay->connected_players &= ~(1<spectator\n", player);
+ print_state(netplay);
+#endif
+
+ }
+
+ }
+
+ break;
+
+#undef START
+#undef NEXT
+ }
+
+ case NETPLAY_CMD_MODE_REFUSED:
+ {
+ uint32_t reason;
+ const char *dmsg = NULL;
+
+ if (netplay->is_server)
+ {
+ RARCH_ERR("NETPLAY_CMD_MODE_REFUSED from client.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ if (cmd_size != sizeof(uint32_t))
+ {
+ RARCH_ERR("Received invalid payload size for NETPLAY_CMD_MODE_REFUSED.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(&reason, sizeof(reason))
+ {
+ RARCH_ERR("Failed to receive NETPLAY_CMD_MODE_REFUSED payload.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ reason = ntohl(reason);
+
+ switch (reason)
+ {
+ case NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED:
+ dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED);
+ break;
+
+ case NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS:
+ dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS);
+ break;
+
+ default:
+ dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY);
+ }
+
+ if (dmsg)
+ {
+ RARCH_LOG("%s\n", dmsg);
+ runloop_msg_queue_push(dmsg, 1, 180, false);
+ }
+ break;
+ }
+
+ case NETPLAY_CMD_DISCONNECT:
+ netplay_hangup(netplay, connection);
+ return true;
+
+ case NETPLAY_CMD_CRC:
+ {
+ uint32_t buffer[2];
+ size_t tmp_ptr = netplay->self_ptr;
+ bool found = false;
+
+ if (cmd_size != sizeof(buffer))
+ {
+ RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(buffer, sizeof(buffer))
+ {
+ RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ buffer[0] = ntohl(buffer[0]);
+ buffer[1] = ntohl(buffer[1]);
+
+ /* Received a CRC for some frame. If we still have it, check if it
+ * matched. This approach could be improved with some quick modular
+ * arithmetic. */
+ do
+ {
+ if ( netplay->buffer[tmp_ptr].used
+ && netplay->buffer[tmp_ptr].frame == buffer[0])
+ {
+ found = true;
+ break;
+ }
+
+ tmp_ptr = PREV_PTR(tmp_ptr);
+ } while (tmp_ptr != netplay->self_ptr);
+
+ if (!found)
+ {
+ /* Oh well, we got rid of it! */
+ break;
+ }
+
+ if (buffer[0] <= netplay->other_frame_count)
+ {
+ /* We've already replayed up to this frame, so we can check it
+ * directly */
+ uint32_t local_crc = netplay_delta_frame_crc(
+ netplay, &netplay->buffer[tmp_ptr]);
+
+ if (buffer[1] != local_crc)
+ {
+ /* Problem! */
+ netplay_cmd_request_savestate(netplay);
+ }
+ }
+ else
+ {
+ /* We'll have to check it when we catch up */
+ netplay->buffer[tmp_ptr].crc = buffer[1];
+ }
+
+ break;
+ }
+
+ case NETPLAY_CMD_REQUEST_SAVESTATE:
+ /* Delay until next frame so we don't send the savestate after the
+ * input */
+ netplay->force_send_savestate = true;
+ break;
+
+ case NETPLAY_CMD_LOAD_SAVESTATE:
+ {
+ uint32_t frame;
+ uint32_t isize;
+ uint32_t rd, wn;
+ uint32_t player;
+ struct compression_transcoder *ctrans;
+
+ /* Make sure we're ready for it */
+ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
+ {
+ if (!netplay->is_replay)
+ {
+ netplay->is_replay = true;
+ netplay->replay_ptr = netplay->self_ptr;
+ netplay->replay_frame_count = netplay->self_frame_count;
+ netplay_wait_and_init_serialization(netplay);
+ netplay->is_replay = false;
+ }
+ else
+ {
+ netplay_wait_and_init_serialization(netplay);
+ }
+ }
+
+ /* Only players may load states */
+ if (connection->mode != NETPLAY_CONNECTION_PLAYING)
+ {
+ RARCH_ERR("Netplay state load from a spectator.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ /* We only allow players to load state if we're in a simple
+ * 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");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ /* There is a subtlty in whether the load comes before or after the
+ * current frame:
+ *
+ * If it comes before the current frame, then we need to force a
+ * rewind to that point.
+ *
+ * If it comes after the current frame, we need to jump ahead, then
+ * (strangely) force a rewind to the frame we're already on, so it
+ * gets loaded. This is just to avoid having reloading implemented in
+ * too many places. */
+ if (cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t))
+ {
+ RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(&frame, sizeof(frame))
+ {
+ RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ frame = ntohl(frame);
+
+ if (frame != netplay->read_frame_count[connection->player])
+ {
+ RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr[connection->player]], frame))
+ {
+ /* Hopefully it will be after another round of input */
+ goto shrt;
+ }
+
+ RECV(&isize, sizeof(isize))
+ {
+ RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ isize = ntohl(isize);
+
+ if (isize != netplay->state_size)
+ {
+ RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t))
+ {
+ RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ /* And decompress it */
+ switch (connection->compression_supported)
+ {
+ case NETPLAY_COMPRESSION_ZLIB:
+ ctrans = &netplay->compress_zlib;
+ break;
+ default:
+ ctrans = &netplay->compress_nil;
+ }
+ ctrans->decompression_backend->set_in(ctrans->decompression_stream,
+ netplay->zbuffer, cmd_size - 2*sizeof(uint32_t));
+ ctrans->decompression_backend->set_out(ctrans->decompression_stream,
+ (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state,
+ netplay->state_size);
+ ctrans->decompression_backend->trans(ctrans->decompression_stream,
+ true, &rd, &wn, NULL);
+
+ /* Skip ahead if it's past where we are */
+ if (frame > netplay->self_frame_count)
+ {
+ /* This is squirrely: We need to assure that when we advance the
+ * frame in post_frame, THEN we're referring to the frame to
+ * load into. If we refer directly to read_ptr, then we'll end
+ * up never reading the input for read_frame_count itself, which
+ * will make the other side unhappy. */
+ netplay->self_ptr = PREV_PTR(netplay->read_ptr[connection->player]);
+ netplay->self_frame_count = frame - 1;
+ }
+
+ /* Don't expect earlier data from other clients */
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if (!(netplay->connected_players & (1< netplay->read_frame_count[player])
+ {
+ netplay->read_ptr[player] = netplay->read_ptr[connection->player];
+ netplay->read_frame_count[player] = frame;
+ }
+ }
+
+ /* And force rewind to it */
+ netplay->force_rewind = true;
+ netplay->savestate_request_outstanding = false;
+ netplay->other_ptr = netplay->read_ptr[connection->player];
+ netplay->other_frame_count = frame;
+
+#ifdef DEBUG_NETPLAY_STEPS
+ RARCH_LOG("Loading state at %u\n", frame);
+ print_state(netplay);
+#endif
+
+ break;
+ }
+
+ case NETPLAY_CMD_PAUSE:
+ {
+ char msg[512], nick[NETPLAY_NICK_LEN];
+ msg[sizeof(msg)-1] = '\0';
+
+ /* Read in the paused nick */
+ if (cmd_size != sizeof(nick))
+ {
+ RARCH_ERR("NETPLAY_CMD_PAUSE received invalid payload size.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ RECV(nick, sizeof(nick))
+ {
+ RARCH_ERR("Failed to receive paused nickname.\n");
+ return netplay_cmd_nak(netplay, connection);
+ }
+ nick[sizeof(nick)-1] = '\0';
+
+ /* We outright ignore pausing from spectators */
+ if (connection->mode != NETPLAY_CONNECTION_PLAYING)
+ break;
+
+ connection->paused = true;
+ netplay->remote_paused = true;
+ if (netplay->is_server)
+ {
+ snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), connection->nick);
+ netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE,
+ connection->nick, NETPLAY_NICK_LEN);
+ }
+ else
+ {
+ snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), nick);
+ }
+ RARCH_LOG("%s\n", msg);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ break;
+ }
+
+ case NETPLAY_CMD_RESUME:
+ remote_unpaused(netplay, connection);
+ break;
+
+ default:
+ RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED));
+ return netplay_cmd_nak(netplay, connection);
+ }
+
+ netplay_recv_flush(&connection->recv_packet_buffer);
+ netplay->timeout_cnt = 0;
+ if (had_input)
+ *had_input = true;
+ return true;
+
+shrt:
+ /* No more data, reset and try again */
+ netplay_recv_reset(&connection->recv_packet_buffer);
+ return true;
+
+#undef RECV
+}
+
+/**
+ * netplay_poll_net_input
+ *
+ * Poll input from the network
+ */
+int netplay_poll_net_input(netplay_t *netplay, bool block)
+{
+ bool had_input = false;
+ int max_fd = 0;
+ size_t i;
+
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active && connection->fd >= max_fd)
+ max_fd = connection->fd + 1;
+ }
+
+ if (max_fd == 0)
+ return 0;
+
+ do
+ {
+ had_input = false;
+
+ netplay->timeout_cnt++;
+
+ /* Read input from each connection */
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active && !netplay_get_cmd(netplay, connection, &had_input))
+ netplay_hangup(netplay, connection);
+ }
+
+ if (block)
+ {
+ netplay_update_unread_ptr(netplay);
+
+ /* If we were blocked for input, pass if we have this frame's input */
+ if (netplay->unread_frame_count > netplay->self_frame_count)
+ break;
+
+ /* If we're supposed to block but we didn't have enough input, wait for it */
+ if (!had_input)
+ {
+ fd_set fds;
+ struct timeval tv = {0};
+ tv.tv_usec = RETRY_MS * 1000;
+
+ FD_ZERO(&fds);
+ for (i = 0; i < netplay->connections_size; i++)
+ {
+ struct netplay_connection *connection = &netplay->connections[i];
+ if (connection->active)
+ FD_SET(connection->fd, &fds);
+ }
+
+ if (socket_select(max_fd, &fds, NULL, NULL, &tv) < 0)
+ return -1;
+
+ RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n",
+ netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES);
+
+ if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused)
+ return -1;
+ }
+ }
+ } while (had_input || block);
+
+ return 0;
+}
+
+/**
+ * netplay_flip_port
+ *
+ * Should we flip ports 0 and 1?
+ */
+bool netplay_flip_port(netplay_t *netplay)
+{
+ size_t frame = netplay->self_frame_count;
+
+ if (netplay->flip_frame == 0)
+ return false;
+
+ if (netplay->is_replay)
+ frame = netplay->replay_frame_count;
+
+ return netplay->flip ^ (frame < netplay->flip_frame);
+}
+
+/**
+ * netplay_announce_nat_traversal
+ *
+ * Announce successful NAT traversal.
+ */
+void netplay_announce_nat_traversal(netplay_t *netplay)
+{
+#ifndef HAVE_SOCKET_LEGACY
+ char msg[512], host[PATH_MAX_LENGTH], port[6];
+
+ if (netplay->nat_traversal_state.have_inet4)
+ {
+ if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr,
+ sizeof(struct sockaddr_in),
+ host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0)
+ return;
+
+ }
+#ifdef HAVE_INET6
+ else if (netplay->nat_traversal_state.have_inet6)
+ {
+ if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr,
+ sizeof(struct sockaddr_in6),
+ host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0)
+ return;
+
+ }
+#endif
+ else
+ return;
+
+ snprintf(msg, sizeof(msg), "%s: %s:%s\n",
+ msg_hash_to_str(MSG_PUBLIC_ADDRESS),
+ host, port);
+ runloop_msg_queue_push(msg, 1, 180, false);
+ RARCH_LOG("%s\n", msg);
+#endif
+}
+
+/**
+ * netplay_init_nat_traversal
+ *
+ * Initialize the NAT traversal library and try to open a port
+ */
+void netplay_init_nat_traversal(netplay_t *netplay)
+{
+ natt_init();
+
+ if (!natt_new(&netplay->nat_traversal_state))
+ {
+ netplay->nat_traversal = false;
+ return;
+ }
+
+ natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP);
+
+#ifndef HAVE_SOCKET_LEGACY
+ if (!netplay->nat_traversal_state.request_outstanding)
+ netplay_announce_nat_traversal(netplay);
+#endif
+}
diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c
deleted file mode 100644
index f90d2362ef..0000000000
--- a/network/netplay/netplay_net.c
+++ /dev/null
@@ -1,359 +0,0 @@
-/* RetroArch - A frontend for libretro.
- * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
- * Copyright (C) 2011-2016 - Daniel De Matteis
- * Copyright (C) 2016 - Gregor Richards
- *
- * 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 .
- */
-
-#include
-#include
-
-#include
-#include
-#include
-
-#include "netplay_private.h"
-
-#include "retro_assert.h"
-
-#include "../../autosave.h"
-
-#if 0
-#define DEBUG_NONDETERMINISTIC_CORES
-#endif
-
-static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta)
-{
- static bool crcs_valid = true;
- if (netplay_is_server(netplay))
- {
- if (netplay->check_frames &&
- (delta->frame % netplay->check_frames == 0 || delta->frame == 1))
- {
- delta->crc = netplay_delta_frame_crc(netplay, delta);
- netplay_cmd_crc(netplay, delta);
- }
- }
- else if (delta->crc && crcs_valid)
- {
- /* We have a remote CRC, so check it */
- uint32_t local_crc = netplay_delta_frame_crc(netplay, delta);
- if (local_crc != delta->crc)
- {
- if (delta->frame == 1)
- {
- /* We check frame 1 just to make sure the CRCs make sense at all.
- * If we've diverged at frame 1, we assume CRCs are not useful. */
- crcs_valid = false;
- }
- else if (crcs_valid)
- {
- /* Fix this! */
- netplay_cmd_request_savestate(netplay);
- }
- }
- }
-}
-
-/**
- * netplay_net_pre_frame:
- * @netplay : pointer to netplay object
- *
- * Pre-frame for Netplay (normal version).
- **/
-static bool netplay_net_pre_frame(netplay_t *netplay)
-{
- retro_ctx_serialize_info_t serial_info;
-
- if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
- {
- serial_info.data_const = NULL;
- serial_info.data = netplay->buffer[netplay->self_ptr].state;
- serial_info.size = netplay->state_size;
-
- memset(serial_info.data, 0, serial_info.size);
- if ((netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) || netplay->self_frame_count == 0)
- {
- /* Don't serialize until it's safe */
- }
- else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info))
- {
- if (netplay->force_send_savestate && !netplay->stall)
- {
- /* Send this along to the other side */
- serial_info.data_const = netplay->buffer[netplay->self_ptr].state;
- netplay_load_savestate(netplay, &serial_info, false);
- netplay->force_send_savestate = false;
- }
- }
- else
- {
- /* If the core can't serialize properly, we must stall for the
- * remote input on EVERY frame, because we can't recover */
- netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
- netplay->delay_frames = 0;
- }
-
- /* If we can't transmit savestates, we must stall until the client is ready */
- if (!netplay->has_connection &&
- netplay->self_frame_count > 0 &&
- (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)))
- netplay->stall = RARCH_NETPLAY_STALL_NO_CONNECTION;
- }
-
- if (netplay->is_server && !netplay->has_connection)
- {
- fd_set fds;
- struct timeval tmp_tv = {0};
- int new_fd;
- struct sockaddr_storage their_addr;
- socklen_t addr_size;
-
- /* Check for a connection */
- FD_ZERO(&fds);
- FD_SET(netplay->fd, &fds);
- if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) > 0 &&
- FD_ISSET(netplay->fd, &fds))
- {
- addr_size = sizeof(their_addr);
- new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size);
- if (new_fd < 0)
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED));
- return true;
- }
-
- socket_close(netplay->fd);
- netplay->fd = new_fd;
-
-#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
- {
- int flag = 1;
- if (setsockopt(netplay->fd, IPPROTO_TCP, TCP_NODELAY,
-#ifdef _WIN32
- (const char*)
-#else
- (const void*)
-#endif
- &flag,
- sizeof(int)) < 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(netplay->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
-
- /* Establish the connection */
- if (netplay_handshake(netplay))
- {
- netplay->has_connection = true;
-
- /* Send them the savestate */
- if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)))
- {
- netplay->force_send_savestate = true;
- }
- else
- {
- /* Because the first frame isn't serialized, we're actually at
- * frame 1 */
- netplay->self_ptr = NEXT_PTR(netplay->self_ptr);
- netplay->self_frame_count = 1;
- }
-
- /* And expect the current frame from the other side */
- netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count;
- netplay->read_ptr = netplay->other_ptr = netplay->self_ptr;
-
- /* Unstall if we were waiting for this */
- if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION)
- netplay->stall = 0;
-
- }
- else
- {
- socket_close(netplay->fd);
- /* FIXME: Get in a state to accept another client */
-
- }
- }
- }
-
- netplay->can_poll = true;
- input_poll_net();
-
- return (netplay->stall != RARCH_NETPLAY_STALL_NO_CONNECTION);
-}
-
-/**
- * netplay_net_post_frame:
- * @netplay : pointer to netplay object
- *
- * Post-frame for Netplay (normal version).
- * We check if we have new input and replay from recorded input.
- **/
-static void netplay_net_post_frame(netplay_t *netplay)
-{
- netplay->self_ptr = NEXT_PTR(netplay->self_ptr);
- netplay->self_frame_count++;
-
- /* Only relevant if we're connected */
- if (!netplay->has_connection)
- {
- netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count;
- netplay->read_ptr = netplay->other_ptr = netplay->self_ptr;
- return;
- }
-
-#ifndef DEBUG_NONDETERMINISTIC_CORES
- if (!netplay->force_rewind)
- {
- /* Skip ahead if we predicted correctly.
- * Skip until our simulation failed. */
- while (netplay->other_frame_count < netplay->read_frame_count &&
- netplay->other_frame_count < netplay->self_frame_count)
- {
- struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr];
-
- if (memcmp(ptr->simulated_input_state, ptr->real_input_state,
- sizeof(ptr->real_input_state)) != 0
- && !ptr->used_real)
- break;
- netplay_handle_frame_hash(netplay, ptr);
- netplay->other_ptr = NEXT_PTR(netplay->other_ptr);
- netplay->other_frame_count++;
- }
- }
-#endif
-
- /* Now replay the real input if we've gotten ahead of it */
- if (netplay->force_rewind ||
- (netplay->other_frame_count < netplay->read_frame_count &&
- netplay->other_frame_count < netplay->self_frame_count))
- {
- retro_ctx_serialize_info_t serial_info;
-
- /* Replay frames. */
- netplay->is_replay = true;
- netplay->replay_ptr = netplay->other_ptr;
- netplay->replay_frame_count = netplay->other_frame_count;
-
- if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
- /* Make sure we're initialized before we start loading things */
- netplay_wait_and_init_serialization(netplay);
-
- serial_info.data = NULL;
- serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
- serial_info.size = netplay->state_size;
-
- if (!core_unserialize(&serial_info))
- {
- RARCH_ERR("Netplay savestate loading failed: Prepare for desync!\n");
- }
-
- while (netplay->replay_frame_count < netplay->self_frame_count)
- {
- struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr];
- serial_info.data = ptr->state;
- serial_info.size = netplay->state_size;
- serial_info.data_const = NULL;
-
- /* Remember the current state */
- memset(serial_info.data, 0, serial_info.size);
- core_serialize(&serial_info);
- if (netplay->replay_frame_count < netplay->read_frame_count)
- netplay_handle_frame_hash(netplay, ptr);
-
- /* Simulate this frame's input */
- if (netplay->replay_frame_count >= netplay->read_frame_count)
- netplay_simulate_input(netplay, netplay->replay_ptr, true);
-
- autosave_lock();
- core_run();
- autosave_unlock();
- netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr);
- netplay->replay_frame_count++;
-
-#ifdef DEBUG_NONDETERMINISTIC_CORES
- if (ptr->have_remote && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->replay_ptr], netplay->replay_frame_count))
- {
- RARCH_LOG("PRE %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr));
- if (netplay->is_server)
- RARCH_LOG("INP %X %X\n", ptr->real_input_state[0], ptr->self_state[0]);
- else
- RARCH_LOG("INP %X %X\n", ptr->self_state[0], ptr->real_input_state[0]);
- ptr = &netplay->buffer[netplay->replay_ptr];
- serial_info.data = ptr->state;
- memset(serial_info.data, 0, serial_info.size);
- core_serialize(&serial_info);
- RARCH_LOG("POST %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr));
- }
-#endif
- }
-
- if (netplay->read_frame_count < netplay->self_frame_count)
- {
- netplay->other_ptr = netplay->read_ptr;
- netplay->other_frame_count = netplay->read_frame_count;
- }
- else
- {
- netplay->other_ptr = netplay->self_ptr;
- netplay->other_frame_count = netplay->self_frame_count;
- }
- netplay->is_replay = false;
- netplay->force_rewind = false;
- }
-
- /* If we're supposed to stall, rewind (we shouldn't get this far if we're
- * stalled, so this is a last resort) */
- if (netplay->stall)
- {
- retro_ctx_serialize_info_t serial_info;
-
- netplay->self_ptr = PREV_PTR(netplay->self_ptr);
- netplay->self_frame_count--;
-
- serial_info.data = NULL;
- serial_info.data_const = netplay->buffer[netplay->self_ptr].state;
- serial_info.size = netplay->state_size;
-
- core_unserialize(&serial_info);
- }
-}
-
-static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames)
-{
- if (!netplay_is_server(netplay))
- {
- if (!netplay_handshake(netplay))
- return false;
- netplay->has_connection = true;
- }
-
- return true;
-}
-
-struct netplay_callbacks* netplay_get_cbs_net(void)
-{
- static struct netplay_callbacks cbs = {
- &netplay_net_pre_frame,
- &netplay_net_post_frame,
- &netplay_net_info_cb
- };
- return &cbs;
-}
diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h
index faff16b4a2..e0794f0faa 100644
--- a/network/netplay/netplay_private.h
+++ b/network/netplay/netplay_private.h
@@ -24,22 +24,29 @@
#include
#include
#include
-#include
-#include "../../core.h"
#include "../../msg_hash.h"
#include "../../verbosity.h"
-#ifdef ANDROID
-#define HAVE_IPV6
-#endif
+#define WORDS_PER_INPUT 3 /* Buttons, left stick, right stick */
+#define WORDS_PER_FRAME (WORDS_PER_INPUT+2) /* + frameno, playerno */
+
+#define NETPLAY_PROTOCOL_VERSION 4
-#define WORDS_PER_FRAME 4 /* Allows us to send 128 bits worth of state per frame. */
-#define MAX_SPECTATORS 16
#define RARCH_DEFAULT_PORT 55435
#define RARCH_DEFAULT_NICK "Anonymous"
-#define NETPLAY_PROTOCOL_VERSION 3
+#define NETPLAY_NICK_LEN 32
+#define NETPLAY_PASS_LEN 128
+#define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */
+
+#define MAX_SERVER_STALL_TIME_USEC (5*1000*1000)
+#define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000)
+#define MAX_RETRIES 16
+#define RETRY_MS 500
+
+#define NETPLAY_MAX_STALL_FRAMES 60
+#define NETPLAY_FRAME_RUN_TIME_WINDOW 120
#define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size)
@@ -78,6 +85,130 @@
#define NETPLAY_COMPRESSION_SUPPORTED 0
#endif
+enum netplay_cmd
+{
+ /* Basic commands */
+
+ /* Acknowlegement response */
+ NETPLAY_CMD_ACK = 0x0000,
+
+ /* Failed acknowlegement response */
+ NETPLAY_CMD_NAK = 0x0001,
+
+ /* Gracefully disconnects from host */
+ NETPLAY_CMD_DISCONNECT = 0x0002,
+
+ /* Input data */
+ NETPLAY_CMD_INPUT = 0x0003,
+
+ /* Non-input data */
+ NETPLAY_CMD_NOINPUT = 0x0004,
+
+ /* Initialization commands */
+
+ /* Inform the other side of our nick (must be first command) */
+ NETPLAY_CMD_NICK = 0x0020,
+
+ /* Give the connection password */
+ NETPLAY_CMD_PASSWORD = 0x0021,
+
+ /* Give core/content info */
+ NETPLAY_CMD_INFO = 0x0022,
+
+ /* Initial synchronization info (frame, sram, player info) */
+ NETPLAY_CMD_SYNC = 0x0023,
+
+ /* Join spectator mode */
+ NETPLAY_CMD_SPECTATE = 0x0024,
+
+ /* Join play mode */
+ NETPLAY_CMD_PLAY = 0x0025,
+
+ /* Report player mode */
+ NETPLAY_CMD_MODE = 0x0026,
+
+ /* Report player mode refused */
+ NETPLAY_CMD_MODE_REFUSED = 0x0027,
+
+ /* Loading and synchronization */
+
+ /* Send the CRC hash of a frame's state */
+ NETPLAY_CMD_CRC = 0x0040,
+
+ /* Request a savestate */
+ NETPLAY_CMD_REQUEST_SAVESTATE = 0x0041,
+
+ /* Send a savestate for the client to load */
+ NETPLAY_CMD_LOAD_SAVESTATE = 0x0042,
+
+ /* Pauses the game, takes no arguments */
+ NETPLAY_CMD_PAUSE = 0x0043,
+
+ /* Resumes the game, takes no arguments */
+ NETPLAY_CMD_RESUME = 0x0044,
+
+ /* Sends over cheats enabled on client (unsupported) */
+ NETPLAY_CMD_CHEATS = 0x0045,
+
+ /* Misc. commands */
+
+ /* Swap inputs between player 1 and player 2 */
+ NETPLAY_CMD_FLIP_PLAYERS = 0x0060,
+
+ /* Sends multiple config requests over,
+ * See enum netplay_cmd_cfg */
+ NETPLAY_CMD_CFG = 0x0061,
+
+ /* CMD_CFG streamlines sending multiple
+ configurations. This acknowledges
+ each one individually */
+ NETPLAY_CMD_CFG_ACK = 0x0062
+};
+
+#define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31)
+#define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31)
+#define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<17)
+#define NETPLAY_CMD_MODE_BIT_YOU (1U<<16)
+
+/* These are the reasons given for mode changes to be rejected */
+enum netplay_cmd_mode_reasons
+{
+ /* Other/unknown reason */
+ NETPLAY_CMD_MODE_REFUSED_REASON_OTHER,
+
+ /* You don't have permission to play */
+ NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED,
+
+ /* There are no free player slots */
+ NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS
+};
+
+enum rarch_netplay_connection_mode
+{
+ NETPLAY_CONNECTION_NONE = 0,
+
+ /* Initialization: */
+ NETPLAY_CONNECTION_INIT, /* Waiting for header */
+ NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */
+ NETPLAY_CONNECTION_PRE_PASSWORD, /* Waiting for password */
+ NETPLAY_CONNECTION_PRE_INFO, /* Waiting for core/content info */
+ NETPLAY_CONNECTION_PRE_SYNC, /* Waiting for sync */
+
+ /* Ready: */
+ NETPLAY_CONNECTION_CONNECTED, /* Modes above this are connected */
+ NETPLAY_CONNECTION_SPECTATING, /* Spectator mode */
+ NETPLAY_CONNECTION_PLAYING /* Normal ready state */
+};
+
+enum rarch_netplay_stall_reason
+{
+ NETPLAY_STALL_NONE = 0,
+ NETPLAY_STALL_RUNNING_FAST,
+ NETPLAY_STALL_NO_CONNECTION
+};
+
+typedef uint32_t netplay_input_state_t[WORDS_PER_INPUT];
+
struct delta_frame
{
bool used; /* a bit derpy, but this is how we know if the delta's been used at all */
@@ -89,74 +220,167 @@ struct delta_frame
/* The CRC-32 of the serialized state if we've calculated it, else 0 */
uint32_t crc;
- uint32_t real_input_state[WORDS_PER_FRAME - 1];
- uint32_t simulated_input_state[WORDS_PER_FRAME - 1];
- uint32_t self_state[WORDS_PER_FRAME - 1];
+ /* The real, simulated and local input. If we're playing, self_state is
+ * mirrored to the appropriate real_input_state player. */
+ netplay_input_state_t real_input_state[MAX_USERS];
+ netplay_input_state_t simulated_input_state[MAX_USERS];
+ netplay_input_state_t self_state;
/* Have we read local input? */
bool have_local;
- /* Have we read the real remote input? */
- bool have_remote;
+ /* Have we read the real (remote) input? */
+ bool have_real[MAX_USERS];
- /* Is the current state as of self_frame_count using the real remote data? */
- bool used_real;
+ /* Is the current state as of self_frame_count using the real (remote) data? */
+ bool used_real[MAX_USERS];
};
-struct netplay_callbacks {
- bool (*pre_frame) (netplay_t *netplay);
- void (*post_frame)(netplay_t *netplay);
- bool (*info_cb) (netplay_t *netplay, unsigned frames);
-};
-
-enum rarch_netplay_stall_reasons
+struct socket_buffer
{
- RARCH_NETPLAY_STALL_NONE = 0,
- RARCH_NETPLAY_STALL_RUNNING_FAST,
- RARCH_NETPLAY_STALL_NO_CONNECTION
+ unsigned char *data;
+ size_t bufsz;
+ size_t start, end;
+ size_t read;
+};
+
+/* Each connection gets a connection struct */
+struct netplay_connection
+{
+ /* Is this connection buffer in use? */
+ bool active;
+
+ /* fd associated with this connection */
+ int fd;
+
+ /* Address of peer */
+ struct sockaddr_storage addr;
+
+ /* Nickname of peer */
+ char nick[NETPLAY_NICK_LEN];
+
+ /* Salt associated with password transaction */
+ uint32_t salt;
+
+ /* Is this connection allowed to play (server only)? */
+ bool can_play;
+
+ /* Buffers for sending and receiving data */
+ struct socket_buffer send_packet_buffer, recv_packet_buffer;
+
+ /* Mode of the connection */
+ enum rarch_netplay_connection_mode mode;
+
+ /* Player # of connected player */
+ int player;
+
+ /* What compression does this peer support? */
+ uint32_t compression_supported;
+
+ /* Is this player paused? */
+ bool paused;
+
+ /* Is this connection stalling? */
+ enum rarch_netplay_stall_reason stall;
+ retro_time_t stall_time;
+};
+
+/* Compression transcoder */
+struct compression_transcoder
+{
+ const struct trans_stream_backend *compression_backend;
+ void *compression_stream;
+ const struct trans_stream_backend *decompression_backend;
+ void *decompression_stream;
};
struct netplay
{
- char nick[32];
- char other_nick[32];
- struct sockaddr_storage other_addr;
+ /* Are we the server? */
+ bool is_server;
+
+ /* Our nickname */
+ char nick[NETPLAY_NICK_LEN];
+
+ /* TCP connection for listening (server only) */
+ int listen_fd;
+
+ /* Password required to play (server only) */
+ char play_password[NETPLAY_PASS_LEN];
+
+ /* Password required to connect (server only) */
+ char spectate_password[NETPLAY_PASS_LEN];
+
+ /* Our player number */
+ uint32_t self_player;
+
+ /* Our mode and status */
+ enum rarch_netplay_connection_mode self_mode;
+
+ /* All of our connections */
+ struct netplay_connection *connections;
+ size_t connections_size;
+ struct netplay_connection one_connection; /* Client only */
+
+ /* Bitmap of players with controllers (whether local or remote) (low bit is
+ * player 1) */
+ uint32_t connected_players;
+
+ /* Maximum player number */
+ uint32_t player_max;
struct retro_callbacks cbs;
- /* TCP connection for state sending, etc. Also used for commands */
- int fd;
- /* TCP port (if serving) */
+
+ /* TCP port (only set if serving) */
uint16_t tcp_port;
+
/* NAT traversal info (if NAT traversal is used and serving) */
bool nat_traversal;
struct natt_status nat_traversal_state;
- /* Which port is governed by netplay (other user)? */
- unsigned port;
- bool has_connection;
struct delta_frame *buffer;
size_t buffer_size;
/* Compression transcoder */
- const struct trans_stream_backend *compression_backend;
- void *compression_stream;
- const struct trans_stream_backend *decompression_backend;
- void *decompression_stream;
+ struct compression_transcoder compress_nil,
+ compress_zlib;
/* A buffer into which to compress frames for transfer */
uint8_t *zbuffer;
size_t zbuffer_size;
- /* Pointer where we are now. */
- size_t self_ptr;
- /* Points to the last reliable state that self ever had. */
+ /* The size of our packet buffers */
+ size_t packet_buffer_size;
+
+ /* The current frame seen by the frontend */
+ size_t self_ptr;
+ uint32_t self_frame_count;
+
+ /* The first frame at which some data might be unreliable */
size_t other_ptr;
- /* Pointer to where we are reading.
- * Generally, other_ptr <= read_ptr <= self_ptr. */
- size_t read_ptr;
+ uint32_t other_frame_count;
+
+ /* Pointer to the first frame for which we're missing the data of at least
+ * one connected player excluding ourself.
+ * Generally, other_ptr <= unread_ptr <= self_ptr, but unread_ptr can get ahead
+ * of self_ptr if the peer is running fast. */
+ size_t unread_ptr;
+ uint32_t unread_frame_count;
+
+ /* Pointer to the next frame to read from each player */
+ size_t read_ptr[MAX_USERS];
+ uint32_t read_frame_count[MAX_USERS];
+
+ /* Pointer to the next frame to read from the server (as it might not be a
+ * player but still synchronizes) */
+ size_t server_ptr;
+ uint32_t server_frame_count;
+
/* A pointer used temporarily for replay. */
size_t replay_ptr;
+ uint32_t replay_frame_count;
+ /* Size of savestates */
size_t state_size;
/* Are we replaying old frames? */
@@ -172,95 +396,403 @@ struct netplay
/* Quirks in the savestate implementation */
uint64_t quirks;
- /* Force our state to be sent to the other side. Used when they request a
- * savestate, to send at the next pre-frame. */
+ /* Force our state to be sent to all connections */
bool force_send_savestate;
/* Have we requested a savestate as a sync point? */
bool savestate_request_outstanding;
/* A buffer for outgoing input packets. */
- uint32_t packet_buffer[2 + WORDS_PER_FRAME];
- uint32_t self_frame_count;
- uint32_t read_frame_count;
- uint32_t other_frame_count;
- uint32_t replay_frame_count;
+ uint32_t input_packet_buffer[2 + WORDS_PER_FRAME];
+
+ /* Our local socket info */
struct addrinfo *addr;
- struct sockaddr_storage their_addr;
- bool has_client_addr;
+ /* Counter for timeouts */
unsigned timeout_cnt;
- /* Spectating. */
- struct {
- bool enabled;
- int fds[MAX_SPECTATORS];
- uint32_t frames[MAX_SPECTATORS];
- uint16_t *input;
- size_t input_ptr;
- size_t input_sz;
- } spectate;
- bool is_server;
-
/* User flipping
* Flipping state. If frame >= flip_frame, we apply the flip.
* If not, we apply the opposite, effectively creating a trigger point. */
bool flip;
uint32_t flip_frame;
- /* Netplay pausing
- */
+ /* Netplay pausing */
bool local_paused;
bool remote_paused;
- /* And stalling */
- uint32_t delay_frames;
- int stall;
+ /* If true, never progress without peer input (stateless/rewindless mode) */
+ bool stateless_mode;
+
+ /* We stall if we're far enough ahead that we couldn't transparently rewind.
+ * To know if we could transparently rewind, we need to know how long
+ * running a frame takes. We record that every frame and get a running
+ * (window) average */
+ retro_time_t frame_run_time[NETPLAY_FRAME_RUN_TIME_WINDOW];
+ int frame_run_time_ptr;
+ retro_time_t frame_run_time_sum, frame_run_time_avg;
+
+ /* Are we stalled? */
+ enum rarch_netplay_stall_reason stall;
+
+ /* How long have we been stalled? */
retro_time_t stall_time;
- /* Frequency with which to check CRCs */
- uint32_t check_frames;
+ /* Opposite of stalling, should we be catching up? */
+ bool catch_up;
- struct netplay_callbacks* net_cbs;
+ /* Frequency with which to check CRCs */
+ int check_frames;
+
+ /* Have we checked whether CRCs are valid at all? */
+ bool crc_validity_checked;
+
+ /* Are they valid? */
+ bool crcs_valid;
};
-struct netplay_callbacks* netplay_get_cbs_net(void);
-struct netplay_callbacks* netplay_get_cbs_spectate(void);
+/***************************************************************
+ * NETPLAY-BUF.C
+ **************************************************************/
-/* Normally called at init time, unless the INITIALIZATION quirk is set */
-bool netplay_init_serialization(netplay_t *netplay);
+/**
+ * netplay_init_socket_buffer
+ *
+ * Initialize a new socket buffer.
+ */
+bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size);
-/* Force serialization to be ready by fast-forwarding the core */
-bool netplay_wait_and_init_serialization(netplay_t *netplay);
+/**
+ * netplay_resize_socket_buffer
+ *
+ * Resize the given socket_buffer's buffer to the requested size.
+ */
+bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize);
-void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim);
+/**
+ * netplay_deinit_socket_buffer
+ *
+ * Free a socket buffer.
+ */
+void netplay_deinit_socket_buffer(struct socket_buffer *sbuf);
-void netplay_log_connection(const struct sockaddr_storage *their_addr,
- unsigned slot, const char *nick);
+/**
+ * netplay_send
+ *
+ * Queue the given data for sending.
+ */
+bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf,
+ size_t len);
-bool netplay_get_nickname(netplay_t *netplay, int fd);
+/**
+ * netplay_send_flush
+ *
+ * Flush unsent data in the given socket buffer, blocking to do so if
+ * requested.
+ *
+ * Returns false only on socket failures, true otherwise.
+ */
+bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block);
-bool netplay_send_nickname(netplay_t *netplay, int fd);
+/**
+ * netplay_recv
+ *
+ * Receive buffered or fresh data.
+ *
+ * Returns number of bytes returned, which may be short or 0, or -1 on error.
+ */
+ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf,
+ size_t len, bool block);
-bool netplay_handshake(netplay_t *netplay);
+/**
+ * netplay_recv_reset
+ *
+ * Reset our recv buffer so that future netplay_recvs will read the same data
+ * again.
+ */
+void netplay_recv_reset(struct socket_buffer *sbuf);
-uint32_t netplay_impl_magic(void);
+/**
+ * netplay_recv_flush
+ *
+ * Flush our recv buffer, so a future netplay_recv_reset will reset to this
+ * point.
+ */
+void netplay_recv_flush(struct socket_buffer *sbuf);
-bool netplay_is_server(netplay_t* netplay);
-bool netplay_is_spectate(netplay_t* netplay);
+/***************************************************************
+ * NETPLAY-DELTA.C
+ **************************************************************/
-bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame);
+/**
+ * netplay_delta_frame_ready
+ *
+ * Prepares, if possible, a delta frame for input, and reports whether it is
+ * ready.
+ *
+ * Returns: True if the delta frame is ready for input at the given frame,
+ * false otherwise.
+ */
+bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta,
+ uint32_t frame);
+/**
+ * netplay_delta_frame_crc
+ *
+ * Get the CRC for the serialization of this frame.
+ */
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta);
-bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta);
-bool netplay_cmd_request_savestate(netplay_t *netplay);
-
-/* DISCOVERY: */
+/***************************************************************
+ * 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
+ **************************************************************/
+
+/**
+ * netplay_load_savestate
+ * @netplay : pointer to netplay object
+ * @serial_info : the savestate being loaded, NULL means
+ * "load it yourself"
+ * @save : Whether to save the provided serial_info
+ * into the frame buffer
+ *
+ * Inform Netplay of a savestate load and send it to the other side
+ **/
+void netplay_load_savestate(netplay_t *netplay,
+ retro_ctx_serialize_info_t *serial_info, bool save);
+
+/**
+ * input_poll_net
+ *
+ * Poll the network if necessary.
+ */
+void input_poll_net(void);
+
+/***************************************************************
+ * NETPLAY-HANDSHAKE.C
+ **************************************************************/
+
+/**
+ * netplay_handshake_init_send
+ *
+ * Initialize our handshake and send the first part of the handshake protocol.
+ */
+bool netplay_handshake_init_send(netplay_t *netplay,
+ struct netplay_connection *connection);
+
+/**
+ * netplay_handshake
+ *
+ * Data receiver for all handshake states.
+ */
+bool netplay_handshake(netplay_t *netplay,
+ struct netplay_connection *connection, bool *had_input);
+
+
+/***************************************************************
+ * NETPLAY-INIT.C
+ **************************************************************/
+
+/**
+ * netplay_try_init_serialization
+ *
+ * Try to initialize serialization. For quirky cores.
+ *
+ * Returns true if serialization is now ready, false otherwise.
+ */
+bool netplay_try_init_serialization(netplay_t *netplay);
+
+/**
+ * netplay_wait_and_init_serialization
+ *
+ * Try very hard to initialize serialization, simulating multiple frames if
+ * necessary. For quirky cores.
+ *
+ * Returns true if serialization is now ready, false otherwise.
+ */
+bool netplay_wait_and_init_serialization(netplay_t *netplay);
+
+/**
+ * netplay_new:
+ * @direct_host : Netplay host discovered from scanning.
+ * @server : IP address of server.
+ * @port : Port of server.
+ * @play_password : Password required to play.
+ * @spectate_password : Password required to connect.
+ * @stateless_mode : Shall we run in stateless mode?
+ * @check_frames : Frequency with which to check CRCs.
+ * @cb : Libretro callbacks.
+ * @nat_traversal : If true, attempt NAT traversal.
+ * @nick : Nickname of user.
+ * @quirks : Netplay quirks required for this session.
+ *
+ * Creates a new netplay handle. A NULL server means we're
+ * hosting.
+ *
+ * Returns: new netplay data.
+ */
+netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port,
+ const char *play_password, const char *spectate_password,
+ bool stateless_mode, int check_frames,
+ const struct retro_callbacks *cb, bool nat_traversal, const char *nick,
+ uint64_t quirks);
+
+/**
+ * netplay_free
+ * @netplay : pointer to netplay object
+ *
+ * Frees netplay data/
+ */
+void netplay_free(netplay_t *netplay);
+
+
+/***************************************************************
+ * NETPLAY-IO.C
+ **************************************************************/
+
+/**
+ * netplay_hangup:
+ *
+ * Disconnects an active Netplay connection due to an error
+ */
+void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection);
+
+/**
+ * netplay_send_cur_input
+ *
+ * Send the current input frame to a given connection.
+ *
+ * Returns true if successful, false otherwise.
+ */
+bool netplay_send_cur_input(netplay_t *netplay,
+ struct netplay_connection *connection);
+
+/**
+ * netplay_send_raw_cmd
+ *
+ * Send a raw Netplay command to the given connection.
+ *
+ * Returns true on success, false on failure.
+ */
+bool netplay_send_raw_cmd(netplay_t *netplay,
+ struct netplay_connection *connection, uint32_t cmd, const void *data,
+ size_t size);
+
+/**
+ * netplay_send_raw_cmd_all
+ *
+ * Send a raw Netplay command to all connections, optionally excluding one
+ * (typically the client that the relevant command came from)
+ */
+void netplay_send_raw_cmd_all(netplay_t *netplay,
+ struct netplay_connection *except, uint32_t cmd, const void *data,
+ size_t size);
+
+/**
+ * netplay_cmd_crc
+ *
+ * Send a CRC command to all active clients.
+ */
+bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta);
+
+/**
+ * netplay_cmd_request_savestate
+ *
+ * Send a savestate request command.
+ */
+bool netplay_cmd_request_savestate(netplay_t *netplay);
+
+/**
+ * netplay_cmd_mode
+ *
+ * Send a mode request command to either play or spectate.
+ */
+bool netplay_cmd_mode(netplay_t *netplay,
+ struct netplay_connection *connection,
+ enum rarch_netplay_connection_mode mode);
+
+/**
+ * netplay_poll_net_input
+ *
+ * Poll input from the network
+ */
+int netplay_poll_net_input(netplay_t *netplay, bool block);
+
+/**
+ * netplay_flip_port
+ *
+ * Should we flip ports 0 and 1?
+ */
+bool netplay_flip_port(netplay_t *netplay);
+
+/**
+ * netplay_announce_nat_traversal
+ *
+ * Announce successful NAT traversal.
+ */
+void netplay_announce_nat_traversal(netplay_t *netplay);
+
+/**
+ * netplay_init_nat_traversal
+ *
+ * Initialize the NAT traversal library and try to open a port
+ */
+void netplay_init_nat_traversal(netplay_t *netplay);
+
+
+/***************************************************************
+ * NETPLAY-SYNC.C
+ **************************************************************/
+
+/**
+ * netplay_update_unread_ptr
+ *
+ * Update the global unread_ptr and unread_frame_count to correspond to the
+ * earliest unread frame count of any connected player
+ */
+void netplay_update_unread_ptr(netplay_t *netplay);
+
+/**
+ * netplay_simulate_input
+ * @netplay : pointer to netplay object
+ * @sim_ptr : frame index for which to simulate input
+ * @resim : are we resimulating, or simulating this frame for the
+ * first time?
+ *
+ * "Simulate" input by assuming it hasn't changed since the last read input.
+ */
+void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim);
+
+/**
+ * netplay_sync_pre_frame
+ * @netplay : pointer to netplay object
+ *
+ * Pre-frame for Netplay synchronization.
+ */
+bool netplay_sync_pre_frame(netplay_t *netplay);
+
+/**
+ * netplay_sync_post_frame
+ * @netplay : pointer to netplay object
+ * @stalled : true if we're currently stalled
+ *
+ * Post-frame for Netplay synchronization.
+ * We check if we have new input and replay from recorded input.
+ */
+void netplay_sync_post_frame(netplay_t *netplay, bool stalled);
+
#endif
diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c
deleted file mode 100644
index 7f20f7e976..0000000000
--- a/network/netplay/netplay_spectate.c
+++ /dev/null
@@ -1,301 +0,0 @@
-/* RetroArch - A frontend for libretro.
- * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
- * Copyright (C) 2011-2016 - Daniel De Matteis
- * Copyright (C) 2016 - Gregor Richards
- *
- * 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 .
- */
-
-#include
-#include
-
-#include
-#include
-#include
-
-#include "netplay_private.h"
-
-#include "retro_assert.h"
-
-#include "../../autosave.h"
-
-/**
- * netplay_spectate_pre_frame:
- * @netplay : pointer to netplay object
- *
- * Pre-frame for Netplay (spectator version).
- **/
-static bool netplay_spectate_pre_frame(netplay_t *netplay)
-{
- if (netplay_is_server(netplay))
- {
- fd_set fds;
- int new_fd, idx, i;
- struct sockaddr_storage their_addr;
- socklen_t addr_size;
- retro_ctx_serialize_info_t serial_info;
- uint32_t header[3];
- struct timeval tmp_tv = {0};
-
- netplay->can_poll = true;
- input_poll_net();
-
- /* Send our input to any connected spectators */
- for (i = 0; i < MAX_SPECTATORS; i++)
- {
- if (netplay->spectate.fds[i] >= 0)
- {
- netplay->packet_buffer[2] = htonl(netplay->self_frame_count - netplay->spectate.frames[i]);
- if (!socket_send_all_blocking(netplay->spectate.fds[i], netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
- {
- socket_close(netplay->spectate.fds[i]);
- netplay->spectate.fds[i] = -1;
- }
- }
- }
-
- /* Check for connections */
- FD_ZERO(&fds);
- FD_SET(netplay->fd, &fds);
- if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
- return true;
-
- if (!FD_ISSET(netplay->fd, &fds))
- return true;
-
- addr_size = sizeof(their_addr);
- new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size);
- if (new_fd < 0)
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_ACCEPT_INCOMING_SPECTATOR));
- return true;
- }
-
- idx = -1;
- for (i = 0; i < MAX_SPECTATORS; i++)
- {
- if (netplay->spectate.fds[i] == -1)
- {
- idx = i;
- break;
- }
- }
-
- /* No vacant client streams :( */
- if (idx == -1)
- {
- socket_close(new_fd);
- return true;
- }
-
- if (!netplay_get_nickname(netplay, new_fd))
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT));
- socket_close(new_fd);
- return true;
- }
-
- if (!netplay_send_nickname(netplay, new_fd))
- {
- RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT));
- socket_close(new_fd);
- return true;
- }
-
- /* Wait until it's safe to serialize */
- if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
- {
- netplay->is_replay = true;
- netplay->replay_ptr = netplay->self_ptr;
- netplay->replay_frame_count = netplay->self_frame_count;
- netplay_wait_and_init_serialization(netplay);
- netplay->is_replay = false;
- }
-
- /* Start them at the current frame */
- netplay->spectate.frames[idx] = netplay->self_frame_count;
- serial_info.data_const = NULL;
- serial_info.data = netplay->buffer[netplay->self_ptr].state;
- serial_info.size = netplay->state_size;
- if (core_serialize(&serial_info))
- {
- /* Send them the savestate */
- header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE);
- header[1] = htonl(serial_info.size + sizeof(uint32_t));
- header[2] = htonl(0);
- if (!socket_send_all_blocking(new_fd, header, sizeof(header), false))
- {
- socket_close(new_fd);
- return true;
- }
-
- if (!socket_send_all_blocking(new_fd, serial_info.data, serial_info.size, false))
- {
- socket_close(new_fd);
- return true;
- }
- }
-
- /* And send them this frame's input */
- netplay->packet_buffer[2] = htonl(0);
- if (!socket_send_all_blocking(new_fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
- {
- socket_close(new_fd);
- return true;
- }
-
- netplay->spectate.fds[idx] = new_fd;
-
- }
- else
- {
- if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
- {
- /* Mark our own data as already read, so we ignore local input */
- netplay->buffer[netplay->self_ptr].have_local = true;
- }
-
- netplay->can_poll = true;
- input_poll_net();
-
- /* Only proceed if we have data */
- if (netplay->read_frame_count <= netplay->self_frame_count)
- return false;
-
- }
-
- return true;
-}
-
-/**
- * netplay_spectate_post_frame:
- * @netplay : pointer to netplay object
- *
- * Post-frame for Netplay (spectator version).
- * Not much here, just fast forward if we're behind the server.
- **/
-static void netplay_spectate_post_frame(netplay_t *netplay)
-{
- netplay->self_ptr = NEXT_PTR(netplay->self_ptr);
- netplay->self_frame_count++;
-
- if (netplay_is_server(netplay))
- {
- /* Not expecting any client data */
- netplay->read_ptr = netplay->other_ptr = netplay->self_ptr;
- netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count;
-
- }
- else
- {
- /* If we must rewind, it's because we got a save state */
- if (netplay->force_rewind)
- {
- retro_ctx_serialize_info_t serial_info;
-
- /* Replay frames. */
- netplay->is_replay = true;
- netplay->replay_ptr = netplay->other_ptr;
- netplay->replay_frame_count = netplay->other_frame_count;
-
- /* Wait until it's safe to serialize */
- if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
- netplay_wait_and_init_serialization(netplay);
-
- serial_info.data = NULL;
- serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
- serial_info.size = netplay->state_size;
-
- core_unserialize(&serial_info);
-
- while (netplay->replay_frame_count < netplay->self_frame_count)
- {
- autosave_lock();
- core_run();
- autosave_unlock();
- netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr);
- netplay->replay_frame_count++;
- }
-
- netplay->is_replay = false;
- netplay->force_rewind = false;
- }
-
- /* We're in sync by definition */
- if (netplay->read_frame_count < netplay->self_frame_count)
- {
- netplay->other_ptr = netplay->read_ptr;
- netplay->other_frame_count = netplay->read_frame_count;
- }
- else
- {
- netplay->other_ptr = netplay->self_ptr;
- netplay->other_frame_count = netplay->self_frame_count;
- }
-
- /* If the server gets significantly ahead, skip to catch up */
- if (netplay->self_frame_count + netplay->delay_frames <= netplay->read_frame_count)
- {
- /* "Replay" into the future */
- netplay->is_replay = true;
- netplay->replay_ptr = netplay->self_ptr;
- netplay->replay_frame_count = netplay->self_frame_count;
-
- while (netplay->replay_frame_count < netplay->read_frame_count - 1)
- {
- autosave_lock();
- core_run();
- autosave_unlock();
-
- netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr);
- netplay->replay_frame_count++;
- netplay->self_ptr = netplay->replay_ptr;
- netplay->self_frame_count = netplay->replay_frame_count;
- }
-
- netplay->is_replay = false;
- }
-
- }
-}
-
-static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames)
-{
- if (netplay_is_server(netplay))
- {
- int i;
- for (i = 0; i < MAX_SPECTATORS; i++)
- netplay->spectate.fds[i] = -1;
- }
- else
- {
- if (!netplay_send_nickname(netplay, netplay->fd))
- return false;
-
- if (!netplay_get_nickname(netplay, netplay->fd))
- return false;
- }
-
- netplay->has_connection = true;
-
- return true;
-}
-
-struct netplay_callbacks* netplay_get_cbs_spectate(void)
-{
- static struct netplay_callbacks cbs = {
- &netplay_spectate_pre_frame,
- &netplay_spectate_post_frame,
- &netplay_spectate_info_cb
- };
- return &cbs;
-}
diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c
new file mode 100644
index 0000000000..fe163c50f4
--- /dev/null
+++ b/network/netplay/netplay_sync.c
@@ -0,0 +1,527 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
+ * Copyright (C) 2011-2016 - Daniel De Matteis
+ * Copyright (C) 2016 - Gregor Richards
+ *
+ * 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 .
+ */
+
+#include
+#include
+#include
+
+#include
+
+#include "netplay_private.h"
+
+#include "../../autosave.h"
+#include "../../driver.h"
+#include "../../input/input_driver.h"
+
+#if 0
+#define DEBUG_NONDETERMINISTIC_CORES
+#endif
+
+/**
+ * netplay_update_unread_ptr
+ *
+ * Update the global unread_ptr and unread_frame_count to correspond to the
+ * earliest unread frame count of any connected player
+ */
+void netplay_update_unread_ptr(netplay_t *netplay)
+{
+ if (netplay->is_server && !netplay->connected_players)
+ {
+ /* Nothing at all to read! */
+ netplay->unread_ptr = netplay->self_ptr;
+ netplay->unread_frame_count = netplay->self_frame_count;
+
+ }
+ else
+ {
+ size_t new_unread_ptr = 0;
+ uint32_t new_unread_frame_count = (uint32_t) -1;
+ uint32_t player;
+
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if (!(netplay->connected_players & (1<read_frame_count[player] < new_unread_frame_count)
+ {
+ new_unread_ptr = netplay->read_ptr[player];
+ new_unread_frame_count = netplay->read_frame_count[player];
+ }
+ }
+
+ if (!netplay->is_server && netplay->server_frame_count < new_unread_frame_count)
+ {
+ new_unread_ptr = netplay->server_ptr;
+ new_unread_frame_count = netplay->server_frame_count;
+ }
+
+ netplay->unread_ptr = new_unread_ptr;
+ netplay->unread_frame_count = new_unread_frame_count;
+ }
+}
+
+/**
+ * netplay_simulate_input
+ * @netplay : pointer to netplay object
+ * @sim_ptr : frame index for which to simulate input
+ * @resim : are we resimulating, or simulating this frame for the
+ * first time?
+ *
+ * "Simulate" input by assuming it hasn't changed since the last read input.
+ */
+void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim)
+{
+ uint32_t player;
+ size_t prev;
+ struct delta_frame *simframe, *pframe;
+
+ simframe = &netplay->buffer[sim_ptr];
+
+ for (player = 0; player < MAX_USERS; player++)
+ {
+ if (!(netplay->connected_players & (1<have_real[player]) continue;
+
+ prev = PREV_PTR(netplay->read_ptr[player]);
+ pframe = &netplay->buffer[prev];
+
+ if (resim)
+ {
+ /* In resimulation mode, we only copy the buttons. The reason for this
+ * is nonobvious:
+ *
+ * If we resimulated nothing, then the /duration/ with which any input
+ * was pressed would be approximately correct, since the original
+ * simulation came in as the input came in, but the /number of times/
+ * the input was pressed would be wrong, as there would be an
+ * advancing wavefront of real data overtaking the simulated data
+ * (which is really just real data offset by some frames).
+ *
+ * That's acceptable for arrows in most situations, since the amount
+ * you move is tied to the duration, but unacceptable for buttons,
+ * which will seem to jerkily be pressed numerous times with those
+ * wavefronts.
+ */
+ const uint32_t keep = (1U<simulated_input_state[player][0] & keep;
+ sim_state |= pframe->real_input_state[player][0] & ~keep;
+ simframe->simulated_input_state[player][0] = sim_state;
+ }
+ else
+ {
+ memcpy(simframe->simulated_input_state[player],
+ pframe->real_input_state[player],
+ WORDS_PER_INPUT * sizeof(uint32_t));
+ }
+ }
+}
+
+static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta)
+{
+ if (netplay->is_server)
+ {
+ if (netplay->check_frames &&
+ delta->frame % abs(netplay->check_frames) == 0)
+ {
+ delta->crc = netplay_delta_frame_crc(netplay, delta);
+ netplay_cmd_crc(netplay, delta);
+ }
+ }
+ else if (delta->crc && netplay->crcs_valid)
+ {
+ /* We have a remote CRC, so check it */
+ uint32_t local_crc = netplay_delta_frame_crc(netplay, delta);
+ if (local_crc != delta->crc)
+ {
+ if (!netplay->crc_validity_checked)
+ {
+ /* If the very first check frame is wrong, they probably just don't
+ * work */
+ netplay->crcs_valid = false;
+ }
+ else if (netplay->crcs_valid)
+ {
+ /* Fix this! */
+ if (netplay->check_frames < 0)
+ {
+ /* Just report */
+ RARCH_ERR("Netplay CRCs mismatch!\n");
+ }
+ else
+ {
+ netplay_cmd_request_savestate(netplay);
+ }
+ }
+ }
+ else if (!netplay->crc_validity_checked)
+ {
+ netplay->crc_validity_checked = true;
+ }
+ }
+}
+
+/**
+ * netplay_sync_pre_frame
+ * @netplay : pointer to netplay object
+ *
+ * Pre-frame for Netplay synchronization.
+ */
+bool netplay_sync_pre_frame(netplay_t *netplay)
+{
+ retro_ctx_serialize_info_t serial_info;
+
+ if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
+ {
+ serial_info.data_const = NULL;
+ serial_info.data = netplay->buffer[netplay->self_ptr].state;
+ serial_info.size = netplay->state_size;
+
+ memset(serial_info.data, 0, serial_info.size);
+ if ((netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) || netplay->self_frame_count == 0)
+ {
+ /* Don't serialize until it's safe */
+ }
+ else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info))
+ {
+ if (netplay->force_send_savestate && !netplay->stall && !netplay->remote_paused)
+ {
+ /* Send this along to the other side */
+ serial_info.data_const = netplay->buffer[netplay->self_ptr].state;
+ netplay_load_savestate(netplay, &serial_info, false);
+ netplay->force_send_savestate = false;
+ }
+ }
+ else
+ {
+ /* If the core can't serialize properly, we must stall for the
+ * remote input on EVERY frame, because we can't recover */
+ netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
+ netplay->stateless_mode = true;
+ }
+
+ /* If we can't transmit savestates, we must stall until the client is ready */
+ if (netplay->self_frame_count > 0 &&
+ (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)) &&
+ (netplay->connections_size == 0 || !netplay->connections[0].active ||
+ netplay->connections[0].mode < NETPLAY_CONNECTION_CONNECTED))
+ netplay->stall = NETPLAY_STALL_NO_CONNECTION;
+ }
+
+ 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;
+
+ /* 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))
+ {
+ addr_size = sizeof(their_addr);
+ new_fd = accept(netplay->listen_fd, (struct sockaddr*)&their_addr, &addr_size);
+ if (new_fd < 0)
+ {
+ RARCH_ERR("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED));
+ 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 flag = 1;
+ if (setsockopt(new_fd, IPPROTO_TCP, TCP_NODELAY,
+#ifdef _WIN32
+ (const char*)
+#else
+ (const void*)
+#endif
+ &flag,
+ sizeof(int)) < 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
+
+ /* Allocate a connection */
+ for (connection_num = 0; connection_num < netplay->connections_size; connection_num++)
+ if (!netplay->connections[connection_num].active) break;
+ if (connection_num == netplay->connections_size)
+ {
+ if (connection_num == 0)
+ {
+ netplay->connections = malloc(sizeof(struct netplay_connection));
+ if (netplay->connections == NULL)
+ {
+ socket_close(new_fd);
+ goto process;
+ }
+ netplay->connections_size = 1;
+
+ }
+ else
+ {
+ size_t new_connections_size = netplay->connections_size * 2;
+ struct netplay_connection *new_connections =
+ realloc(netplay->connections,
+ new_connections_size*sizeof(struct netplay_connection));
+ if (new_connections == NULL)
+ {
+ socket_close(new_fd);
+ goto process;
+ }
+
+ memset(new_connections + netplay->connections_size, 0,
+ netplay->connections_size * sizeof(struct netplay_connection));
+ netplay->connections = new_connections;
+ netplay->connections_size = new_connections_size;
+
+ }
+ }
+ connection = &netplay->connections[connection_num];
+
+ /* Set it up */
+ memset(connection, 0, sizeof(*connection));
+ connection->active = true;
+ connection->fd = new_fd;
+ connection->mode = NETPLAY_CONNECTION_INIT;
+
+ if (!netplay_init_socket_buffer(&connection->send_packet_buffer,
+ netplay->packet_buffer_size) ||
+ !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);
+ connection->active = false;
+ socket_close(new_fd);
+ goto process;
+ }
+
+ netplay_handshake_init_send(netplay, connection);
+
+ }
+ }
+
+process:
+ netplay->can_poll = true;
+ input_poll_net();
+
+ return (netplay->stall != NETPLAY_STALL_NO_CONNECTION);
+}
+
+/**
+ * netplay_sync_post_frame
+ * @netplay : pointer to netplay object
+ *
+ * Post-frame for Netplay synchronization.
+ * We check if we have new input and replay from recorded input.
+ */
+void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
+{
+ uint32_t cmp_frame_count;
+
+ /* Unless we're stalling, we've just finished running a frame */
+ if (!stalled)
+ {
+ netplay->self_ptr = NEXT_PTR(netplay->self_ptr);
+ netplay->self_frame_count++;
+ }
+
+ /* Only relevant if we're connected */
+ if ((netplay->is_server && !netplay->connected_players) ||
+ (netplay->self_mode < NETPLAY_CONNECTION_CONNECTED))
+ {
+ netplay->other_frame_count = netplay->self_frame_count;
+ netplay->other_ptr = netplay->self_ptr;
+ /* FIXME: Duplication */
+ if (netplay->catch_up)
+ {
+ netplay->catch_up = false;
+ input_driver_unset_nonblock_state();
+ driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL);
+ }
+ return;
+ }
+
+#ifndef DEBUG_NONDETERMINISTIC_CORES
+ if (!netplay->force_rewind)
+ {
+ /* Skip ahead if we predicted correctly.
+ * Skip until our simulation failed. */
+ while (netplay->other_frame_count < netplay->unread_frame_count &&
+ netplay->other_frame_count < netplay->self_frame_count)
+ {
+ struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr];
+ size_t i;
+
+ for (i = 0; i < MAX_USERS; i++)
+ {
+ if (memcmp(ptr->simulated_input_state[i], ptr->real_input_state[i],
+ sizeof(ptr->real_input_state[i])) != 0
+ && !ptr->used_real[i])
+ break;
+ }
+ if (i != MAX_USERS) break;
+ netplay_handle_frame_hash(netplay, ptr);
+ netplay->other_ptr = NEXT_PTR(netplay->other_ptr);
+ netplay->other_frame_count++;
+ }
+ }
+#endif
+
+ /* Now replay the real input if we've gotten ahead of it */
+ if (netplay->force_rewind ||
+ (netplay->other_frame_count < netplay->unread_frame_count &&
+ netplay->other_frame_count < netplay->self_frame_count))
+ {
+ retro_ctx_serialize_info_t serial_info;
+
+ /* Replay frames. */
+ netplay->is_replay = true;
+ netplay->replay_ptr = netplay->other_ptr;
+ netplay->replay_frame_count = netplay->other_frame_count;
+
+ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
+ /* Make sure we're initialized before we start loading things */
+ netplay_wait_and_init_serialization(netplay);
+
+ serial_info.data = NULL;
+ serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
+ serial_info.size = netplay->state_size;
+
+ if (!core_unserialize(&serial_info))
+ {
+ RARCH_ERR("Netplay savestate loading failed: Prepare for desync!\n");
+ }
+
+ while (netplay->replay_frame_count < netplay->self_frame_count)
+ {
+ retro_time_t start, tm;
+
+ struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr];
+ serial_info.data = ptr->state;
+ serial_info.size = netplay->state_size;
+ serial_info.data_const = NULL;
+
+ start = cpu_features_get_time_usec();
+
+ /* Remember the current state */
+ memset(serial_info.data, 0, serial_info.size);
+ core_serialize(&serial_info);
+ if (netplay->replay_frame_count < netplay->unread_frame_count)
+ netplay_handle_frame_hash(netplay, ptr);
+
+ /* Re-simulate this frame's input */
+ netplay_simulate_input(netplay, netplay->replay_ptr, true);
+
+ autosave_lock();
+ core_run();
+ autosave_unlock();
+ netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr);
+ netplay->replay_frame_count++;
+
+#ifdef DEBUG_NONDETERMINISTIC_CORES
+ if (ptr->have_remote && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->replay_ptr], netplay->replay_frame_count))
+ {
+ RARCH_LOG("PRE %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr));
+ if (netplay->is_server)
+ RARCH_LOG("INP %X %X\n", ptr->real_input_state[0], ptr->self_state[0]);
+ else
+ RARCH_LOG("INP %X %X\n", ptr->self_state[0], ptr->real_input_state[0]);
+ ptr = &netplay->buffer[netplay->replay_ptr];
+ serial_info.data = ptr->state;
+ memset(serial_info.data, 0, serial_info.size);
+ core_serialize(&serial_info);
+ RARCH_LOG("POST %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr));
+ }
+#endif
+
+ /* Get our time window */
+ tm = cpu_features_get_time_usec() - start;
+ netplay->frame_run_time_sum -= netplay->frame_run_time[netplay->frame_run_time_ptr];
+ netplay->frame_run_time[netplay->frame_run_time_ptr] = tm;
+ netplay->frame_run_time_sum += tm;
+ netplay->frame_run_time_ptr++;
+ if (netplay->frame_run_time_ptr >= NETPLAY_FRAME_RUN_TIME_WINDOW)
+ netplay->frame_run_time_ptr = 0;
+ }
+
+ /* Average our time */
+ netplay->frame_run_time_avg = netplay->frame_run_time_sum / NETPLAY_FRAME_RUN_TIME_WINDOW;
+
+ if (netplay->unread_frame_count < netplay->self_frame_count)
+ {
+ netplay->other_ptr = netplay->unread_ptr;
+ netplay->other_frame_count = netplay->unread_frame_count;
+ }
+ else
+ {
+ netplay->other_ptr = netplay->self_ptr;
+ netplay->other_frame_count = netplay->self_frame_count;
+ }
+ netplay->is_replay = false;
+ netplay->force_rewind = false;
+ }
+
+ if (netplay->is_server)
+ cmp_frame_count = netplay->unread_frame_count;
+ else
+ cmp_frame_count = netplay->server_frame_count;
+
+ /* If we're behind, try to catch up */
+ if (netplay->catch_up)
+ {
+ /* Are we caught up? */
+ if (netplay->self_frame_count >= cmp_frame_count)
+ {
+ netplay->catch_up = false;
+ input_driver_unset_nonblock_state();
+ driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL);
+ }
+
+ }
+ else if (!stalled)
+ {
+ /* Are we falling behind? */
+ if (netplay->self_frame_count < cmp_frame_count - 2)
+ {
+ netplay->catch_up = true;
+ input_driver_set_nonblock_state();
+ driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL);
+ }
+ }
+}
diff --git a/retroarch.c b/retroarch.c
index af54841baa..b0b10c8236 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -104,6 +104,7 @@
enum
{
RA_OPT_MENU = 256, /* must be outside the range of a char */
+ RA_OPT_STATELESS,
RA_OPT_CHECK_FRAMES,
RA_OPT_PORT,
RA_OPT_SPECTATE,
@@ -143,7 +144,7 @@ static bool has_set_state_path = false;
static bool has_set_netplay_mode = false;
static bool has_set_netplay_ip_address = false;
static bool has_set_netplay_ip_port = false;
-static bool has_set_netplay_delay_frames = false;
+static bool has_set_netplay_stateless_mode = false;
static bool has_set_netplay_check_frames = false;
static bool has_set_ups_pref = false;
static bool has_set_bps_pref = false;
@@ -339,10 +340,10 @@ static void retroarch_print_help(const char *arg0)
puts(" -H, --host Host netplay as user 1.");
puts(" -C, --connect=HOST Connect to netplay server as user 2.");
puts(" --port=PORT Port used to netplay. Default is 55435.");
- puts(" -F, --frames=NUMBER Delay frames when using netplay.");
+ puts(" --stateless Use \"stateless\" mode for netplay");
+ puts(" (requires a very fast network).");
puts(" --check-frames=NUMBER\n"
" Check frames when using netplay.");
- puts(" --spectate Connect to netplay server as spectator.");
#if defined(HAVE_NETWORK_CMD)
puts(" --command Sends a command over UDP to an already "
"running program process.");
@@ -427,10 +428,9 @@ static void retroarch_parse_input(int argc, char *argv[])
#ifdef HAVE_NETWORKING
{ "host", 0, NULL, 'H' },
{ "connect", 1, NULL, 'C' },
- { "frames", 1, NULL, 'F' },
+ { "stateless", 0, NULL, RA_OPT_STATELESS },
{ "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES },
{ "port", 1, NULL, RA_OPT_PORT },
- { "spectate", 0, NULL, RA_OPT_SPECTATE },
#if defined(HAVE_NETWORK_CMD)
{ "command", 1, NULL, RA_OPT_COMMAND },
#endif
@@ -707,10 +707,10 @@ static void retroarch_parse_input(int argc, char *argv[])
sizeof(settings->netplay.server));
break;
- case 'F':
- settings->netplay.delay_frames = strtol(optarg, NULL, 0);
+ case RA_OPT_STATELESS:
+ settings->netplay.stateless_mode = true;
retroarch_override_setting_set(
- RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL);
+ RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL);
break;
case RA_OPT_CHECK_FRAMES:
@@ -725,12 +725,6 @@ static void retroarch_parse_input(int argc, char *argv[])
settings->netplay.port = strtoul(optarg, NULL, 0);
break;
- case RA_OPT_SPECTATE:
- retroarch_override_setting_set(
- RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
- settings->netplay.is_spectate = true;
- break;
-
#if defined(HAVE_NETWORK_CMD)
case RA_OPT_COMMAND:
if (command_network_send((const char*)optarg))
@@ -1369,8 +1363,8 @@ bool retroarch_override_setting_is_set(enum rarch_override_setting enum_idx, voi
return has_set_netplay_ip_address;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
return has_set_netplay_ip_port;
- case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
- return has_set_netplay_delay_frames;
+ case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
+ return has_set_netplay_stateless_mode;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
return has_set_netplay_check_frames;
case RARCH_OVERRIDE_SETTING_UPS_PREF:
@@ -1426,8 +1420,8 @@ void retroarch_override_setting_set(enum rarch_override_setting enum_idx, void *
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
has_set_netplay_ip_port = true;
break;
- case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
- has_set_netplay_delay_frames = true;
+ case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
+ has_set_netplay_stateless_mode = true;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
has_set_netplay_check_frames = true;
@@ -1485,8 +1479,8 @@ void retroarch_override_setting_unset(enum rarch_override_setting enum_idx, void
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
has_set_netplay_ip_port = false;
break;
- case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
- has_set_netplay_delay_frames = false;
+ case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
+ has_set_netplay_stateless_mode = false;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
has_set_netplay_check_frames = false;
diff --git a/retroarch.h b/retroarch.h
index 15799c69fd..32d9a860aa 100644
--- a/retroarch.h
+++ b/retroarch.h
@@ -129,7 +129,7 @@ enum rarch_override_setting
RARCH_OVERRIDE_SETTING_NETPLAY_MODE,
RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS,
RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT,
- RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES,
+ RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE,
RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES,
RARCH_OVERRIDE_SETTING_UPS_PREF,
RARCH_OVERRIDE_SETTING_BPS_PREF,
diff --git a/runloop.c b/runloop.c
index 29e413d7fe..c05f34aa65 100644
--- a/runloop.c
+++ b/runloop.c
@@ -922,6 +922,9 @@ static enum runloop_state runloop_check_state(
#ifdef HAVE_NETWORKING
tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_FLIP);
netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, &tmp);
+ tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_GAME_WATCH);
+ if (tmp)
+ netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL);
tmp = runloop_cmd_triggered(trigger_input, RARCH_FULLSCREEN_TOGGLE_KEY);
#endif