Merge pull request #4274 from GregorR/netplay-nouveau-nouveau

>2-player Netplay
This commit is contained in:
Twinaphex 2016-12-19 16:28:37 +01:00 committed by GitHub
commit 032d29efa4
44 changed files with 6037 additions and 3370 deletions

View File

@ -1122,11 +1122,14 @@ ifeq ($(HAVE_NETWORKING), 1)
# Netplay # Netplay
DEFINES += -DHAVE_NETWORK_CMD DEFINES += -DHAVE_NETWORK_CMD
OBJ += network/netplay/netplay_net.o \ OBJ += network/netplay/netplay_delta.o \
network/netplay/netplay_spectate.o \ network/netplay/netplay_frontend.o \
network/netplay/netplay_common.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_discovery.o \
network/netplay/netplay.o network/netplay/netplay_buf.o
# Retro Achievements (also depends on threads) # Retro Achievements (also depends on threads)

View File

@ -286,6 +286,7 @@ static const struct cmd_map map[] = {
{ "MUTE", RARCH_MUTE }, { "MUTE", RARCH_MUTE },
{ "OSK", RARCH_OSK }, { "OSK", RARCH_OSK },
{ "NETPLAY_FLIP", RARCH_NETPLAY_FLIP }, { "NETPLAY_FLIP", RARCH_NETPLAY_FLIP },
{ "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH },
{ "SLOWMOTION", RARCH_SLOWMOTION }, { "SLOWMOTION", RARCH_SLOWMOTION },
{ "VOLUME_UP", RARCH_VOLUME_UP }, { "VOLUME_UP", RARCH_VOLUME_UP },
{ "VOLUME_DOWN", RARCH_VOLUME_DOWN }, { "VOLUME_DOWN", RARCH_VOLUME_DOWN },
@ -1227,8 +1228,7 @@ static void command_event_load_auto_state(void)
global_t *global = global_get_ptr(); global_t *global = global_get_ptr();
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
&& !settings->netplay.is_spectate)
return; return;
#endif #endif
@ -2362,14 +2362,21 @@ bool command_event(enum event_command cmd, void *data)
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
if (!init_netplay( if (!init_netplay(
settings->netplay.is_spectate, data, settings->netplay.server, data, settings->netplay.server,
settings->netplay.port)) settings->netplay.port,
settings->netplay.password,
settings->netplay.spectate_password))
return false; return false;
#endif #endif
break; break;
case CMD_EVENT_NETPLAY_FLIP_PLAYERS: case CMD_EVENT_NETPLAY_FLIP_PLAYERS:
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, NULL); 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 #endif
break; break;
case CMD_EVENT_FULLSCREEN_TOGGLE: case CMD_EVENT_FULLSCREEN_TOGGLE:

View File

@ -173,6 +173,8 @@ enum event_command
CMD_EVENT_NETPLAY_DEINIT, CMD_EVENT_NETPLAY_DEINIT,
/* Flip netplay players. */ /* Flip netplay players. */
CMD_EVENT_NETPLAY_FLIP_PLAYERS, CMD_EVENT_NETPLAY_FLIP_PLAYERS,
/* Switch between netplay gaming and watching. */
CMD_EVENT_NETPLAY_GAME_WATCH,
/* Initializes BSV movie. */ /* Initializes BSV movie. */
CMD_EVENT_BSV_MOVIE_INIT, CMD_EVENT_BSV_MOVIE_INIT,
/* Deinitializes BSV movie. */ /* Deinitializes BSV movie. */

View File

@ -794,10 +794,15 @@ static const bool pause_nonactive = true;
* It is measured in seconds. A value of 0 disables autosave. */ * It is measured in seconds. A value of 0 disables autosave. */
static const unsigned autosave_interval = 0; 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 /* When being client over netplay, use keybinds for
* user 1 rather than user 2. */ * user 1 rather than user 2. */
static const bool netplay_client_swap_input = true; 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_delay_frames = 16;
static const unsigned netplay_check_frames = 30; 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_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_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_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_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_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 }, { true, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, RETROK_KP_PLUS, NO_BTN, 0, AXIS_NONE },

View File

@ -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); SETTING_PATH("core_updater_buildbot_assets_url", settings->network.buildbot_assets_url, false, NULL, true);
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
SETTING_PATH("netplay_ip_address", settings->netplay.server, false, NULL, true); 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 #endif
SETTING_PATH("recording_output_directory", SETTING_PATH("recording_output_directory",
global->record.output_dir, false, NULL, true); 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("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); SETTING_BOOL("menu_swap_ok_cancel_buttons", &settings->input.menu_swap_ok_cancel_buttons, true, menu_swap_ok_cancel_buttons, false);
#ifdef HAVE_NETWORKING #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); SETTING_BOOL("netplay_client_swap_input", &settings->netplay.swap_input, true, netplay_client_swap_input, false);
#endif #endif
SETTING_BOOL("input_descriptor_label_show", &settings->input.input_descriptor_label_show, true, input_descriptor_label_show, false); 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); SETTING_BOOL("network_remote_enable", &settings->network_remote_enable, false, false /* TODO */, false);
#endif #endif
#ifdef HAVE_NETWORKING #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); SETTING_BOOL("netplay_nat_traversal", &settings->netplay.nat_traversal, true, true, false);
#endif #endif
SETTING_BOOL("block_sram_overwrite", &settings->block_sram_overwrite, true, block_sram_overwrite, false); 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); SETTING_INT("state_slot", (unsigned*)&settings->state_slot, false, 0 /* TODO */, false);
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
SETTING_INT("netplay_ip_port", &settings->netplay.port, true, RARCH_DEFAULT_PORT, false); 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); SETTING_INT("netplay_check_frames", &settings->netplay.check_frames, true, netplay_check_frames, false);
#endif #endif
#ifdef HAVE_LANGEXTRA #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)) if (!rarch_ctl(RARCH_CTL_IS_FORCE_FULLSCREEN, NULL))
CONFIG_GET_BOOL_BASE(conf, settings, video.fullscreen, "video_fullscreen"); 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 #ifdef HAVE_NETWORKGAMEPAD
for (i = 0; i < MAX_USERS; i++) 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 #ifdef HAVE_NETWORKING
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL)) if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL))
CONFIG_GET_INT_BASE(conf, settings, netplay.delay_frames, "netplay_delay_frames"); 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)) 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"); 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)) if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, NULL))

View File

@ -401,11 +401,12 @@ typedef struct settings
{ {
char server[255]; char server[255];
unsigned port; unsigned port;
unsigned delay_frames; bool stateless_mode;
unsigned check_frames; int check_frames;
bool is_spectate;
bool swap_input; bool swap_input;
bool nat_traversal; bool nat_traversal;
char password[128];
char spectate_password[128];
} netplay; } netplay;
#endif #endif

View File

@ -881,11 +881,14 @@ THREAD
NETPLAY NETPLAY
============================================================ */ ============================================================ */
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
#include "../network/netplay/netplay_net.c" #include "../network/netplay/netplay_delta.c"
#include "../network/netplay/netplay_spectate.c" #include "../network/netplay/netplay_frontend.c"
#include "../network/netplay/netplay_common.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_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_compat.c"
#include "../libretro-common/net/net_socket.c" #include "../libretro-common/net/net_socket.c"
#include "../libretro-common/net/net_http.c" #include "../libretro-common/net/net_http.c"

View File

@ -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, 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, 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, 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, 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, enable_hotkey, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY),
DECLARE_META_BIND(2, volume_up, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP), DECLARE_META_BIND(2, volume_up, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP),

View File

@ -72,6 +72,7 @@ enum
RARCH_MUTE, RARCH_MUTE,
RARCH_OSK, RARCH_OSK,
RARCH_NETPLAY_FLIP, RARCH_NETPLAY_FLIP,
RARCH_NETPLAY_GAME_WATCH,
RARCH_SLOWMOTION, RARCH_SLOWMOTION,
RARCH_ENABLE_HOTKEY, RARCH_ENABLE_HOTKEY,
RARCH_VOLUME_UP, RARCH_VOLUME_UP,

View File

@ -3543,8 +3543,6 @@ const char *msg_hash_to_str_chs(enum msg_hash_enums msg)
return "Stopping movie record."; return "Stopping movie record.";
case MSG_NETPLAY_FAILED: case MSG_NETPLAY_FAILED:
return "Failed to initialize netplay."; 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: case MSG_NO_CONTENT_STARTING_DUMMY_CORE:
return "No content, starting dummy core."; return "No content, starting dummy core.";
case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET: case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET:

View File

@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.") "Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED, MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.") "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, MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.") "No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,

View File

@ -1418,8 +1418,6 @@ const char *msg_hash_to_str_es(enum msg_hash_enums msg)
return "Deteniendo grabación de vídeo."; return "Deteniendo grabación de vídeo.";
case MSG_NETPLAY_FAILED: case MSG_NETPLAY_FAILED:
return "Error al iniciar el juego en red."; 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: case MSG_PAUSED:
return "En pausa."; return "En pausa.";
case MSG_PROGRAM: case MSG_PROGRAM:

View File

@ -1928,8 +1928,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Arrêt de l'enregistrement vidéo.") "Arrêt de l'enregistrement vidéo.")
MSG_HASH(MSG_NETPLAY_FAILED, MSG_HASH(MSG_NETPLAY_FAILED,
"Échec de l'initialisation du jeu en réseau") "É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, MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.") "No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,

View File

@ -1997,8 +1997,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.") "Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED, MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.") "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, MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.") "No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,

View File

@ -571,8 +571,6 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
"netplay_check_frames") "netplay_check_frames")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT,
"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, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DISCONNECT,
"menu_netplay_disconnect") "menu_netplay_disconnect")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE,
@ -589,10 +587,16 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
"netplay_nat_traversal") "netplay_nat_traversal")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NICKNAME, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NICKNAME,
"netplay_nickname") "netplay_nickname")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PASSWORD,
"netplay_password")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS,
"menu_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, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
"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, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT,
"netplay_tcp_udp_port") "netplay_tcp_udp_port")
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_LAN_SCAN_SETTINGS, MSG_HASH(MENU_ENUM_LABEL_NETPLAY_LAN_SCAN_SETTINGS,

View File

@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.") "Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED, MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.") "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, MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.") "No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,

View File

@ -925,8 +925,6 @@ const char *msg_hash_to_str_pl(enum msg_hash_enums msg)
return "Zatrzymano nagrywanie filmu."; return "Zatrzymano nagrywanie filmu.";
case MSG_NETPLAY_FAILED: case MSG_NETPLAY_FAILED:
return "Nie udało się zainicjalizować gry sieciowej."; 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: case MSG_PAUSED:
return "Wstrzymano."; return "Wstrzymano.";
case MSG_PROGRAM: case MSG_PROGRAM:

View File

@ -1961,8 +1961,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Запись остановлена.") "Запись остановлена.")
MSG_HASH(MSG_NETPLAY_FAILED, MSG_HASH(MSG_NETPLAY_FAILED,
"Ошибка запуска сетевой игры.") "Ошибка запуска сетевой игры.")
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
"Воспроизведение записи. Невозможно начать сетевую игру.")
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.") "No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,

View File

@ -91,6 +91,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
snprintf(s, len, snprintf(s, len,
"Netplay flip users."); "Netplay flip users.");
break; break;
case RARCH_NETPLAY_GAME_WATCH:
snprintf(s, len,
"Netplay toggle play/spectate mode.");
break;
case RARCH_SLOWMOTION: case RARCH_SLOWMOTION:
snprintf(s, len, snprintf(s, len,
"Hold for slowmotion."); "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" "Increasing this value will increase \n"
"performance, but introduce more latency."); "performance, but introduce more latency.");
break; 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: case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
snprintf(s, len, snprintf(s, len,
"The frequency in frames with which netplay \n" "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, snprintf(s, len,
"The address of the host to connect to."); "The address of the host to connect to.");
break; 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: case MENU_ENUM_LABEL_STDIN_CMD_ENABLE:
snprintf(s, len, snprintf(s, len,
"Enable stdin command interface."); "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, snprintf(s, len,
"Netplay flip users."); "Netplay flip users.");
break; break;
case MENU_ENUM_LABEL_NETPLAY_GAME_WATCH:
snprintf(s, len,
"Netplay toggle play/spectate mode.");
break;
case MENU_ENUM_LABEL_CHEAT_INDEX_PLUS: case MENU_ENUM_LABEL_CHEAT_INDEX_PLUS:
snprintf(s, len, snprintf(s, len,
"Increment cheat index.\n"); "Increment cheat index.\n");

View File

@ -46,6 +46,66 @@ MSG_HASH(
MSG_WAITING_FOR_CLIENT, MSG_WAITING_FOR_CLIENT,
"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( MSG_HASH(
MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT, MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT,
"Give hardware-rendered cores their own private context. Avoids having to assume hardware state changes inbetween frames." "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") "Audio mute toggle")
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP,
"Netplay flip users") "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, MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OSK,
"On-screen keyboard toggle") "On-screen keyboard toggle")
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OVERLAY_NEXT, 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") "Netplay Client Enable")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NICKNAME, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NICKNAME,
"Username") "Username")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD,
"Server Password")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS,
"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, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE,
"Netplay Spectator Enable") "Netplay Spectator Enable")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_TCP_UDP_PORT, 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, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL,
"Netplay NAT Traversal") "Netplay NAT Traversal")
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_ENABLE, MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_ENABLE,
@ -1986,8 +2054,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
"Stopping movie record.") "Stopping movie record.")
MSG_HASH(MSG_NETPLAY_FAILED, MSG_HASH(MSG_NETPLAY_FAILED,
"Failed to initialize netplay.") "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, MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
"No content, starting dummy core.") "No content, starting dummy core.")
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,

View File

@ -3591,8 +3591,6 @@ const char *msg_hash_to_str_vn(enum msg_hash_enums msg)
return "Stopping movie record."; return "Stopping movie record.";
case MSG_NETPLAY_FAILED: case MSG_NETPLAY_FAILED:
return "Failed to initialize netplay."; 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: case MSG_NO_CONTENT_STARTING_DUMMY_CORE:
return "No content, starting dummy core."; return "No content, starting dummy core.";
case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET: case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET:

View File

@ -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); 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); int socket_receive_all_blocking(int fd, void *data_, size_t size);
ssize_t socket_receive_all_nonblocking(int fd, bool *error, ssize_t socket_receive_all_nonblocking(int fd, bool *error,

View File

@ -78,7 +78,11 @@ ssize_t socket_receive_all_nonblocking(int fd, bool *error,
return ret; return ret;
if (ret == 0) if (ret == 0)
{
/* Socket closed */
*error = true;
return -1; return -1;
}
if (isagain(ret)) if (isagain(ret))
return 0; return 0;
@ -179,6 +183,36 @@ int socket_send_all_blocking(int fd, const void *data_, size_t size,
return true; 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) bool socket_bind(int fd, void *data)
{ {
int yes = 1; int yes = 1;

View File

@ -4690,25 +4690,29 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data)
PARSE_ONLY_UINT, false) != -1) PARSE_ONLY_UINT, false) != -1)
count++; count++;
if (menu_displaylist_parse_settings_enum(menu, info, if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, MENU_ENUM_LABEL_NETPLAY_PASSWORD,
PARSE_ONLY_UINT, false) != -1) 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++; count++;
if (menu_displaylist_parse_settings_enum(menu, info, if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
PARSE_ONLY_UINT, false) != -1) PARSE_ONLY_INT, false) != -1)
count++; count++;
if (menu_displaylist_parse_settings_enum(menu, info, 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) PARSE_ONLY_BOOL, false) != -1)
count++; count++;
if (menu_displaylist_parse_settings_enum(menu, info, if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT,
PARSE_ONLY_BOOL, false) != -1) PARSE_ONLY_BOOL, false) != -1)
count++; 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, if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETWORK_CMD_ENABLE, MENU_ENUM_LABEL_NETWORK_CMD_ENABLE,
PARSE_ONLY_BOOL, false) != -1) PARSE_ONLY_BOOL, false) != -1)

View File

@ -1721,34 +1721,16 @@ void general_write_handler(void *data)
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL); retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
#endif #endif
break; break;
case MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE: case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL); retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_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);
}
#endif #endif
break; break;
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
{
bool val = (settings->netplay.check_frames > 0);
if (val)
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL); retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
else
retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
}
#endif #endif
break;
default: default:
break; break;
} }
@ -5582,19 +5564,48 @@ static bool setting_append_list(
menu_settings_list_current_add_range(list, list_info, 0, 65535, 1, true, true); 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); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
CONFIG_UINT( CONFIG_STRING(
list, list_info, list, list_info,
&settings->netplay.delay_frames, settings->netplay.password,
MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, sizeof(settings->netplay.password),
MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES, MENU_ENUM_LABEL_NETPLAY_PASSWORD,
netplay_delay_frames, MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD,
"",
&group_info, &group_info,
&subgroup_info, &subgroup_info,
parent_group, parent_group,
general_write_handler, general_write_handler,
general_read_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_ALLOW_INPUT);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
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( CONFIG_UINT(
list, list_info, list, list_info,
@ -5607,24 +5618,9 @@ static bool setting_append_list(
parent_group, parent_group,
general_write_handler, general_write_handler,
general_read_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); 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( CONFIG_BOOL(
list, list_info, list, list_info,
&settings->netplay.nat_traversal, &settings->netplay.nat_traversal,

View File

@ -140,12 +140,28 @@ enum msg_hash_enums
MSG_UNKNOWN = 0, MSG_UNKNOWN = 0,
MSG_SETTING_DISK_IN_TRAY, MSG_SETTING_DISK_IN_TRAY,
MSG_FAILED_TO_SET_DISK, MSG_FAILED_TO_SET_DISK,
MSG_NETPLAY_FAILED,
MSG_NETPLAY_USERS_HAS_FLIPPED, MSG_NETPLAY_USERS_HAS_FLIPPED,
MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED, MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED,
MSG_CONNECTING_TO_NETPLAY_HOST, MSG_CONNECTING_TO_NETPLAY_HOST,
MSG_NETPLAY_LAN_SCAN_COMPLETE, MSG_NETPLAY_LAN_SCAN_COMPLETE,
MSG_NETPLAY_LAN_SCANNING, MSG_NETPLAY_LAN_SCANNING,
MSG_WAITING_FOR_CLIENT, 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_AUTODETECT,
MSG_AUDIO_VOLUME, MSG_AUDIO_VOLUME,
MSG_LIBRETRO_FRONTEND, MSG_LIBRETRO_FRONTEND,
@ -273,8 +289,6 @@ enum msg_hash_enums
MSG_REWIND_INIT_FAILED, MSG_REWIND_INIT_FAILED,
MSG_REWIND_INIT_FAILED_THREADED_AUDIO, MSG_REWIND_INIT_FAILED_THREADED_AUDIO,
MSG_LIBRETRO_ABI_BREAK, MSG_LIBRETRO_ABI_BREAK,
MSG_NETPLAY_FAILED,
MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
MSG_DETECTED_VIEWPORT_OF, MSG_DETECTED_VIEWPORT_OF,
MSG_RECORDING_TO, MSG_RECORDING_TO,
MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING, 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_MUTE,
MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK,
MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, 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_SLOWMOTION,
MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY,
MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP,
@ -940,6 +955,7 @@ enum msg_hash_enums
MENU_LABEL(UNDO_SAVE_STATE), MENU_LABEL(UNDO_SAVE_STATE),
MENU_LABEL(NETPLAY_FLIP_PLAYERS), MENU_LABEL(NETPLAY_FLIP_PLAYERS),
MENU_LABEL(NETPLAY_GAME_WATCH),
MENU_LABEL(CHEAT_INDEX_MINUS), MENU_LABEL(CHEAT_INDEX_MINUS),
MENU_LABEL(CHEAT_INDEX_PLUS), MENU_LABEL(CHEAT_INDEX_PLUS),
MENU_LABEL(SHADER_NEXT), MENU_LABEL(SHADER_NEXT),
@ -994,6 +1010,7 @@ enum msg_hash_enums
MENU_LABEL(BLUETOOTH_ENABLE), MENU_LABEL(BLUETOOTH_ENABLE),
MENU_LABEL(NETPLAY_CLIENT_SWAP_INPUT), MENU_LABEL(NETPLAY_CLIENT_SWAP_INPUT),
MENU_LABEL(NETPLAY_DELAY_FRAMES), MENU_LABEL(NETPLAY_DELAY_FRAMES),
MENU_LABEL(NETPLAY_STATELESS_MODE),
MENU_LABEL(NETPLAY_CHECK_FRAMES), MENU_LABEL(NETPLAY_CHECK_FRAMES),
MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE), MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE),
MENU_LABEL(NETPLAY_TCP_UDP_PORT), MENU_LABEL(NETPLAY_TCP_UDP_PORT),
@ -1001,6 +1018,8 @@ enum msg_hash_enums
MENU_LABEL(SORT_SAVEFILES_ENABLE), MENU_LABEL(SORT_SAVEFILES_ENABLE),
MENU_LABEL(SORT_SAVESTATES_ENABLE), MENU_LABEL(SORT_SAVESTATES_ENABLE),
MENU_LABEL(NETPLAY_IP_ADDRESS), MENU_LABEL(NETPLAY_IP_ADDRESS),
MENU_LABEL(NETPLAY_PASSWORD),
MENU_LABEL(NETPLAY_SPECTATE_PASSWORD),
MENU_LABEL(NETPLAY_MODE), MENU_LABEL(NETPLAY_MODE),
MENU_LABEL(PERFCNT_ENABLE), MENU_LABEL(PERFCNT_ENABLE),
MENU_LABEL(OVERLAY_SCALE), MENU_LABEL(OVERLAY_SCALE),

View File

@ -9,11 +9,7 @@ minor constraints:
Furthermore, if the core supports serialization (save states), Netplay allows Furthermore, if the core supports serialization (save states), Netplay allows
for latency and clock drift, providing both host and client with a smooth for latency and clock drift, providing both host and client with a smooth
experience. experience, as well as the option of more than two players.
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.
Netplay in RetroArch works by expecting input to come delayed from the network, 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. 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 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. (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 Within the state buffers, there are three locations: self, other and unread.
refers to a frame, and a state buffer corresponding to that frame. The state Each refers to a frame, and a state buffer corresponding to that frame. The
buffer contains the savestate for the frame, and the input from both the local state buffer contains the savestate for the frame, and the input from both the
and remote players. local and remote players.
Self is where the emulator believes itself to be, which may be ahead or behind 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 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 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 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 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. 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 Unread is the first frame at which not all players' data has been read, which
can't always immediately act upon new data. 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 In general, other ≤ unread and other ≤ self. In all likelihood, unread ≤ self,
it is both possible and supported for the remote host to get ahead of the local but it is both possible and supported for the remote host to get ahead of the
host. 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 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 polls for input from the network. If the input from the network is too far
far behind, it stalls to allow the other side to catch up. To assure that this behind (i.e., unread is too far behind self), it stalls to allow the other side
stalling does not block the UI thread, it is implemented similarly to pausing, to catch up. To assure that this stalling does not block the UI thread, it is
rather than by blocking on the socket. 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 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 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 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 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. 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 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 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 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. 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. * Guarantee not actually a guarantee.
@ -100,32 +142,125 @@ Description:
Negative Acknowledgement. If received, the connection is terminated. Sent Negative Acknowledgement. If received, the connection is terminated. Sent
whenever a command is malformed or otherwise not understood. 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 Command: DISCONNECT
Payload: None Payload: None
Description: Description:
Gracefully disconnect. Not used. 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 Command: CRC
Payload: Payload:
{ {
@ -155,9 +290,33 @@ Description:
serialized state is zlib compressed. Otherwise it is uncompressed. serialized state is zlib compressed. Otherwise it is uncompressed.
Command: PAUSE Command: PAUSE
Payload: None Payload:
{
nickname: char[32]
}
Description:
Indicates that the core is paused. The receiving peer should also pause. 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 Command: RESUME
Payload: None Payload: None
Description:
Indicates that the core is no longer paused. 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

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ enum rarch_netplay_ctl_state
{ {
RARCH_NETPLAY_CTL_NONE = 0, RARCH_NETPLAY_CTL_NONE = 0,
RARCH_NETPLAY_CTL_FLIP_PLAYERS, RARCH_NETPLAY_CTL_FLIP_PLAYERS,
RARCH_NETPLAY_CTL_GAME_WATCH,
RARCH_NETPLAY_CTL_POST_FRAME, RARCH_NETPLAY_CTL_POST_FRAME,
RARCH_NETPLAY_CTL_PRE_FRAME, RARCH_NETPLAY_CTL_PRE_FRAME,
RARCH_NETPLAY_CTL_ENABLE_SERVER, RARCH_NETPLAY_CTL_ENABLE_SERVER,
@ -46,80 +47,6 @@ enum rarch_netplay_ctl_state
RARCH_NETPLAY_CTL_DISCONNECT 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, int16_t input_state_net(unsigned port, unsigned device,
unsigned idx, unsigned id); 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); 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 * 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) * @server : server address to connect to (client only)
* @port : TCP port to host on/connect to * @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. * Initializes netplay.
* *
@ -224,7 +71,8 @@ bool netplay_disconnect(netplay_t *netplay);
* *
* Returns: true (1) if successful, otherwise false (0). * 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); void deinit_netplay(void);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <net/net_compat.h>
#include <net/net_socket.h>
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <compat/strl.h>
#include "netplay_private.h"
#include <net/net_socket.h>
#include <compat/strl.h>
#include <encodings/crc32.h>
#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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <sys/types.h>
#include <boolean.h>
#include <encodings/crc32.h>
#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);
}

View File

@ -32,6 +32,7 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <compat/strl.h> #include <compat/strl.h>
@ -191,6 +192,11 @@ error:
return false; 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) bool netplay_lan_ad_server(netplay_t *netplay)
{ {
fd_set fds; fd_set fds;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#if defined(_MSC_VER) && !defined(_XBOX)
#pragma comment(lib, "ws2_32")
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <boolean.h>
#include <compat/strl.h>
#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);
}

1371
network/netplay/netplay_io.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <compat/strl.h>
#include <stdio.h>
#include <net/net_compat.h>
#include <net/net_socket.h>
#include <net/net_natt.h>
#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;
}

View File

@ -24,22 +24,29 @@
#include <net/net_natt.h> #include <net/net_natt.h>
#include <features/features_cpu.h> #include <features/features_cpu.h>
#include <streams/trans_stream.h> #include <streams/trans_stream.h>
#include <retro_endianness.h>
#include "../../core.h"
#include "../../msg_hash.h" #include "../../msg_hash.h"
#include "../../verbosity.h" #include "../../verbosity.h"
#ifdef ANDROID #define WORDS_PER_INPUT 3 /* Buttons, left stick, right stick */
#define HAVE_IPV6 #define WORDS_PER_FRAME (WORDS_PER_INPUT+2) /* + frameno, playerno */
#endif
#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_PORT 55435
#define RARCH_DEFAULT_NICK "Anonymous" #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 PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size)
@ -78,6 +85,130 @@
#define NETPLAY_COMPRESSION_SUPPORTED 0 #define NETPLAY_COMPRESSION_SUPPORTED 0
#endif #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 struct delta_frame
{ {
bool used; /* a bit derpy, but this is how we know if the delta's been used at all */ 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 */ /* The CRC-32 of the serialized state if we've calculated it, else 0 */
uint32_t crc; uint32_t crc;
uint32_t real_input_state[WORDS_PER_FRAME - 1]; /* The real, simulated and local input. If we're playing, self_state is
uint32_t simulated_input_state[WORDS_PER_FRAME - 1]; * mirrored to the appropriate real_input_state player. */
uint32_t self_state[WORDS_PER_FRAME - 1]; 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? */ /* Have we read local input? */
bool have_local; bool have_local;
/* Have we read the real remote input? */ /* Have we read the real (remote) input? */
bool have_remote; bool have_real[MAX_USERS];
/* Is the current state as of self_frame_count using the real remote data? */ /* Is the current state as of self_frame_count using the real (remote) data? */
bool used_real; bool used_real[MAX_USERS];
}; };
struct netplay_callbacks { struct socket_buffer
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
{ {
RARCH_NETPLAY_STALL_NONE = 0, unsigned char *data;
RARCH_NETPLAY_STALL_RUNNING_FAST, size_t bufsz;
RARCH_NETPLAY_STALL_NO_CONNECTION 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 struct netplay
{ {
char nick[32]; /* Are we the server? */
char other_nick[32]; bool is_server;
struct sockaddr_storage other_addr;
/* 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; struct retro_callbacks cbs;
/* TCP connection for state sending, etc. Also used for commands */
int fd; /* TCP port (only set if serving) */
/* TCP port (if serving) */
uint16_t tcp_port; uint16_t tcp_port;
/* NAT traversal info (if NAT traversal is used and serving) */ /* NAT traversal info (if NAT traversal is used and serving) */
bool nat_traversal; bool nat_traversal;
struct natt_status nat_traversal_state; struct natt_status nat_traversal_state;
/* Which port is governed by netplay (other user)? */
unsigned port;
bool has_connection;
struct delta_frame *buffer; struct delta_frame *buffer;
size_t buffer_size; size_t buffer_size;
/* Compression transcoder */ /* Compression transcoder */
const struct trans_stream_backend *compression_backend; struct compression_transcoder compress_nil,
void *compression_stream; compress_zlib;
const struct trans_stream_backend *decompression_backend;
void *decompression_stream;
/* A buffer into which to compress frames for transfer */ /* A buffer into which to compress frames for transfer */
uint8_t *zbuffer; uint8_t *zbuffer;
size_t zbuffer_size; size_t zbuffer_size;
/* Pointer where we are now. */ /* The size of our packet buffers */
size_t packet_buffer_size;
/* The current frame seen by the frontend */
size_t self_ptr; size_t self_ptr;
/* Points to the last reliable state that self ever had. */ uint32_t self_frame_count;
/* The first frame at which some data might be unreliable */
size_t other_ptr; size_t other_ptr;
/* Pointer to where we are reading. uint32_t other_frame_count;
* Generally, other_ptr <= read_ptr <= self_ptr. */
size_t read_ptr; /* 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. */ /* A pointer used temporarily for replay. */
size_t replay_ptr; size_t replay_ptr;
uint32_t replay_frame_count;
/* Size of savestates */
size_t state_size; size_t state_size;
/* Are we replaying old frames? */ /* Are we replaying old frames? */
@ -172,95 +396,403 @@ struct netplay
/* Quirks in the savestate implementation */ /* Quirks in the savestate implementation */
uint64_t quirks; uint64_t quirks;
/* Force our state to be sent to the other side. Used when they request a /* Force our state to be sent to all connections */
* savestate, to send at the next pre-frame. */
bool force_send_savestate; bool force_send_savestate;
/* Have we requested a savestate as a sync point? */ /* Have we requested a savestate as a sync point? */
bool savestate_request_outstanding; bool savestate_request_outstanding;
/* A buffer for outgoing input packets. */ /* A buffer for outgoing input packets. */
uint32_t packet_buffer[2 + WORDS_PER_FRAME]; uint32_t input_packet_buffer[2 + WORDS_PER_FRAME];
uint32_t self_frame_count;
uint32_t read_frame_count; /* Our local socket info */
uint32_t other_frame_count;
uint32_t replay_frame_count;
struct addrinfo *addr; struct addrinfo *addr;
struct sockaddr_storage their_addr;
bool has_client_addr;
/* Counter for timeouts */
unsigned timeout_cnt; 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 /* User flipping
* Flipping state. If frame >= flip_frame, we apply the flip. * Flipping state. If frame >= flip_frame, we apply the flip.
* If not, we apply the opposite, effectively creating a trigger point. */ * If not, we apply the opposite, effectively creating a trigger point. */
bool flip; bool flip;
uint32_t flip_frame; uint32_t flip_frame;
/* Netplay pausing /* Netplay pausing */
*/
bool local_paused; bool local_paused;
bool remote_paused; bool remote_paused;
/* And stalling */ /* If true, never progress without peer input (stateless/rewindless mode) */
uint32_t delay_frames; bool stateless_mode;
int stall;
/* 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; retro_time_t stall_time;
/* Frequency with which to check CRCs */ /* Opposite of stalling, should we be catching up? */
uint32_t check_frames; 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); 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); /***************************************************************
* NETPLAY-DISCOVERY.C
/* DISCOVERY: */ **************************************************************/
/**
* netplay_lan_ad_server
*
* Respond to any LAN ad queries that the netplay server has received.
*/
bool netplay_lan_ad_server(netplay_t *netplay); 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 #endif

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <compat/strl.h>
#include <stdio.h>
#include <net/net_compat.h>
#include <net/net_socket.h>
#include <retro_endianness.h>
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <boolean.h>
#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<<player))) continue;
if (netplay->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<<player))) continue;
if (simframe->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<<RETRO_DEVICE_ID_JOYPAD_UP) |
(1U<<RETRO_DEVICE_ID_JOYPAD_DOWN) |
(1U<<RETRO_DEVICE_ID_JOYPAD_LEFT) |
(1U<<RETRO_DEVICE_ID_JOYPAD_RIGHT);
uint32_t sim_state = simframe->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);
}
}
}

View File

@ -104,6 +104,7 @@
enum enum
{ {
RA_OPT_MENU = 256, /* must be outside the range of a char */ RA_OPT_MENU = 256, /* must be outside the range of a char */
RA_OPT_STATELESS,
RA_OPT_CHECK_FRAMES, RA_OPT_CHECK_FRAMES,
RA_OPT_PORT, RA_OPT_PORT,
RA_OPT_SPECTATE, 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_mode = false;
static bool has_set_netplay_ip_address = false; static bool has_set_netplay_ip_address = false;
static bool has_set_netplay_ip_port = 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_netplay_check_frames = false;
static bool has_set_ups_pref = false; static bool has_set_ups_pref = false;
static bool has_set_bps_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(" -H, --host Host netplay as user 1.");
puts(" -C, --connect=HOST Connect to netplay server as user 2."); puts(" -C, --connect=HOST Connect to netplay server as user 2.");
puts(" --port=PORT Port used to netplay. Default is 55435."); 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" puts(" --check-frames=NUMBER\n"
" Check frames when using netplay."); " Check frames when using netplay.");
puts(" --spectate Connect to netplay server as spectator.");
#if defined(HAVE_NETWORK_CMD) #if defined(HAVE_NETWORK_CMD)
puts(" --command Sends a command over UDP to an already " puts(" --command Sends a command over UDP to an already "
"running program process."); "running program process.");
@ -427,10 +428,9 @@ static void retroarch_parse_input(int argc, char *argv[])
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
{ "host", 0, NULL, 'H' }, { "host", 0, NULL, 'H' },
{ "connect", 1, NULL, 'C' }, { "connect", 1, NULL, 'C' },
{ "frames", 1, NULL, 'F' }, { "stateless", 0, NULL, RA_OPT_STATELESS },
{ "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES }, { "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES },
{ "port", 1, NULL, RA_OPT_PORT }, { "port", 1, NULL, RA_OPT_PORT },
{ "spectate", 0, NULL, RA_OPT_SPECTATE },
#if defined(HAVE_NETWORK_CMD) #if defined(HAVE_NETWORK_CMD)
{ "command", 1, NULL, RA_OPT_COMMAND }, { "command", 1, NULL, RA_OPT_COMMAND },
#endif #endif
@ -707,10 +707,10 @@ static void retroarch_parse_input(int argc, char *argv[])
sizeof(settings->netplay.server)); sizeof(settings->netplay.server));
break; break;
case 'F': case RA_OPT_STATELESS:
settings->netplay.delay_frames = strtol(optarg, NULL, 0); settings->netplay.stateless_mode = true;
retroarch_override_setting_set( retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL); RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL);
break; break;
case RA_OPT_CHECK_FRAMES: 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); settings->netplay.port = strtoul(optarg, NULL, 0);
break; 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) #if defined(HAVE_NETWORK_CMD)
case RA_OPT_COMMAND: case RA_OPT_COMMAND:
if (command_network_send((const char*)optarg)) 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; return has_set_netplay_ip_address;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT: case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
return has_set_netplay_ip_port; return has_set_netplay_ip_port;
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
return has_set_netplay_delay_frames; return has_set_netplay_stateless_mode;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
return has_set_netplay_check_frames; return has_set_netplay_check_frames;
case RARCH_OVERRIDE_SETTING_UPS_PREF: 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: case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
has_set_netplay_ip_port = true; has_set_netplay_ip_port = true;
break; break;
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
has_set_netplay_delay_frames = true; has_set_netplay_stateless_mode = true;
break; break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
has_set_netplay_check_frames = true; 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: case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
has_set_netplay_ip_port = false; has_set_netplay_ip_port = false;
break; break;
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
has_set_netplay_delay_frames = false; has_set_netplay_stateless_mode = false;
break; break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
has_set_netplay_check_frames = false; has_set_netplay_check_frames = false;

View File

@ -129,7 +129,7 @@ enum rarch_override_setting
RARCH_OVERRIDE_SETTING_NETPLAY_MODE, RARCH_OVERRIDE_SETTING_NETPLAY_MODE,
RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS, RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS,
RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, 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_NETPLAY_CHECK_FRAMES,
RARCH_OVERRIDE_SETTING_UPS_PREF, RARCH_OVERRIDE_SETTING_UPS_PREF,
RARCH_OVERRIDE_SETTING_BPS_PREF, RARCH_OVERRIDE_SETTING_BPS_PREF,

View File

@ -922,6 +922,9 @@ static enum runloop_state runloop_check_state(
#ifdef HAVE_NETWORKING #ifdef HAVE_NETWORKING
tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_FLIP); tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_FLIP);
netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, &tmp); 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); tmp = runloop_cmd_triggered(trigger_input, RARCH_FULLSCREEN_TOGGLE_KEY);
#endif #endif