mirror of
https://github.com/libretro/RetroArch
synced 2025-01-31 15:32:59 +00:00
Merge pull request #4274 from GregorR/netplay-nouveau-nouveau
>2-player Netplay
This commit is contained in:
commit
032d29efa4
@ -1122,11 +1122,14 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
|
||||
# Netplay
|
||||
DEFINES += -DHAVE_NETWORK_CMD
|
||||
OBJ += network/netplay/netplay_net.o \
|
||||
network/netplay/netplay_spectate.o \
|
||||
network/netplay/netplay_common.o \
|
||||
OBJ += network/netplay/netplay_delta.o \
|
||||
network/netplay/netplay_frontend.o \
|
||||
network/netplay/netplay_handshake.o \
|
||||
network/netplay/netplay_init.o \
|
||||
network/netplay/netplay_io.o \
|
||||
network/netplay/netplay_sync.o \
|
||||
network/netplay/netplay_discovery.o \
|
||||
network/netplay/netplay.o
|
||||
network/netplay/netplay_buf.o
|
||||
|
||||
# Retro Achievements (also depends on threads)
|
||||
|
||||
|
15
command.c
15
command.c
@ -286,6 +286,7 @@ static const struct cmd_map map[] = {
|
||||
{ "MUTE", RARCH_MUTE },
|
||||
{ "OSK", RARCH_OSK },
|
||||
{ "NETPLAY_FLIP", RARCH_NETPLAY_FLIP },
|
||||
{ "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH },
|
||||
{ "SLOWMOTION", RARCH_SLOWMOTION },
|
||||
{ "VOLUME_UP", RARCH_VOLUME_UP },
|
||||
{ "VOLUME_DOWN", RARCH_VOLUME_DOWN },
|
||||
@ -1227,8 +1228,7 @@ static void command_event_load_auto_state(void)
|
||||
global_t *global = global_get_ptr();
|
||||
|
||||
#ifdef HAVE_NETWORKING
|
||||
if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)
|
||||
&& !settings->netplay.is_spectate)
|
||||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||||
return;
|
||||
#endif
|
||||
|
||||
@ -2362,14 +2362,21 @@ bool command_event(enum event_command cmd, void *data)
|
||||
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
|
||||
#ifdef HAVE_NETWORKING
|
||||
if (!init_netplay(
|
||||
settings->netplay.is_spectate, data, settings->netplay.server,
|
||||
settings->netplay.port))
|
||||
data, settings->netplay.server,
|
||||
settings->netplay.port,
|
||||
settings->netplay.password,
|
||||
settings->netplay.spectate_password))
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case CMD_EVENT_NETPLAY_FLIP_PLAYERS:
|
||||
#ifdef HAVE_NETWORKING
|
||||
netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, NULL);
|
||||
#endif
|
||||
break;
|
||||
case CMD_EVENT_NETPLAY_GAME_WATCH:
|
||||
#ifdef HAVE_NETWORKING
|
||||
netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL);
|
||||
#endif
|
||||
break;
|
||||
case CMD_EVENT_FULLSCREEN_TOGGLE:
|
||||
|
@ -173,6 +173,8 @@ enum event_command
|
||||
CMD_EVENT_NETPLAY_DEINIT,
|
||||
/* Flip netplay players. */
|
||||
CMD_EVENT_NETPLAY_FLIP_PLAYERS,
|
||||
/* Switch between netplay gaming and watching. */
|
||||
CMD_EVENT_NETPLAY_GAME_WATCH,
|
||||
/* Initializes BSV movie. */
|
||||
CMD_EVENT_BSV_MOVIE_INIT,
|
||||
/* Deinitializes BSV movie. */
|
||||
|
@ -794,10 +794,15 @@ static const bool pause_nonactive = true;
|
||||
* It is measured in seconds. A value of 0 disables autosave. */
|
||||
static const unsigned autosave_interval = 0;
|
||||
|
||||
/* Netplay without savestates/rewind */
|
||||
static const bool netplay_stateless_mode = false;
|
||||
|
||||
/* When being client over netplay, use keybinds for
|
||||
* user 1 rather than user 2. */
|
||||
static const bool netplay_client_swap_input = true;
|
||||
|
||||
static const bool netplay_nat_traversal = false;
|
||||
|
||||
static const unsigned netplay_delay_frames = 16;
|
||||
|
||||
static const unsigned netplay_check_frames = 30;
|
||||
@ -987,7 +992,8 @@ static const struct retro_keybind retro_keybinds_1[] = {
|
||||
{ true, RARCH_SCREENSHOT, MENU_ENUM_LABEL_VALUE_INPUT_META_SCREENSHOT, RETROK_F8, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE, RETROK_F9, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, RETROK_F12, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, RETROK_i, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, RETROK_UNKNOWN, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH, RETROK_i, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION, RETROK_e, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, RETROK_UNKNOWN, NO_BTN, 0, AXIS_NONE },
|
||||
{ true, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, RETROK_KP_PLUS, NO_BTN, 0, AXIS_NONE },
|
||||
|
@ -607,6 +607,8 @@ static int populate_settings_path(settings_t *settings, struct config_path_setti
|
||||
SETTING_PATH("core_updater_buildbot_assets_url", settings->network.buildbot_assets_url, false, NULL, true);
|
||||
#ifdef HAVE_NETWORKING
|
||||
SETTING_PATH("netplay_ip_address", settings->netplay.server, false, NULL, true);
|
||||
SETTING_PATH("netplay_password", settings->netplay.password, false, NULL, true);
|
||||
SETTING_PATH("netplay_spectate_password", settings->netplay.spectate_password, false, NULL, true);
|
||||
#endif
|
||||
SETTING_PATH("recording_output_directory",
|
||||
global->record.output_dir, false, NULL, true);
|
||||
@ -716,6 +718,7 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti
|
||||
SETTING_BOOL("all_users_control_menu", &settings->input.all_users_control_menu, true, all_users_control_menu, false);
|
||||
SETTING_BOOL("menu_swap_ok_cancel_buttons", &settings->input.menu_swap_ok_cancel_buttons, true, menu_swap_ok_cancel_buttons, false);
|
||||
#ifdef HAVE_NETWORKING
|
||||
SETTING_BOOL("netplay_stateless_mode", &settings->netplay.stateless_mode, false, netplay_stateless_mode, false);
|
||||
SETTING_BOOL("netplay_client_swap_input", &settings->netplay.swap_input, true, netplay_client_swap_input, false);
|
||||
#endif
|
||||
SETTING_BOOL("input_descriptor_label_show", &settings->input.input_descriptor_label_show, true, input_descriptor_label_show, false);
|
||||
@ -822,7 +825,6 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti
|
||||
SETTING_BOOL("network_remote_enable", &settings->network_remote_enable, false, false /* TODO */, false);
|
||||
#endif
|
||||
#ifdef HAVE_NETWORKING
|
||||
SETTING_BOOL("netplay_spectator_mode_enable",&settings->netplay.is_spectate, false, false /* TODO */, false);
|
||||
SETTING_BOOL("netplay_nat_traversal", &settings->netplay.nat_traversal, true, true, false);
|
||||
#endif
|
||||
SETTING_BOOL("block_sram_overwrite", &settings->block_sram_overwrite, true, block_sram_overwrite, false);
|
||||
@ -946,7 +948,6 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting
|
||||
SETTING_INT("state_slot", (unsigned*)&settings->state_slot, false, 0 /* TODO */, false);
|
||||
#ifdef HAVE_NETWORKING
|
||||
SETTING_INT("netplay_ip_port", &settings->netplay.port, true, RARCH_DEFAULT_PORT, false);
|
||||
SETTING_INT("netplay_delay_frames", &settings->netplay.delay_frames, true, netplay_delay_frames, false);
|
||||
SETTING_INT("netplay_check_frames", &settings->netplay.check_frames, true, netplay_check_frames, false);
|
||||
#endif
|
||||
#ifdef HAVE_LANGEXTRA
|
||||
@ -1807,13 +1808,6 @@ static bool config_load_file(const char *path, bool set_defaults,
|
||||
if (!rarch_ctl(RARCH_CTL_IS_FORCE_FULLSCREEN, NULL))
|
||||
CONFIG_GET_BOOL_BASE(conf, settings, video.fullscreen, "video_fullscreen");
|
||||
|
||||
#ifdef HAVE_NETWORKING
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL))
|
||||
{
|
||||
CONFIG_GET_BOOL_BASE(conf, settings, netplay.is_spectate,
|
||||
"netplay_spectator_mode_enable");
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_NETWORKGAMEPAD
|
||||
for (i = 0; i < MAX_USERS; i++)
|
||||
{
|
||||
@ -1862,8 +1856,8 @@ static bool config_load_file(const char *path, bool set_defaults,
|
||||
}
|
||||
|
||||
#ifdef HAVE_NETWORKING
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL))
|
||||
CONFIG_GET_INT_BASE(conf, settings, netplay.delay_frames, "netplay_delay_frames");
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL))
|
||||
CONFIG_GET_BOOL_BASE(conf, settings, netplay.stateless_mode, "netplay_stateless_mode");
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL))
|
||||
CONFIG_GET_INT_BASE(conf, settings, netplay.check_frames, "netplay_check_frames");
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, NULL))
|
||||
|
@ -401,11 +401,12 @@ typedef struct settings
|
||||
{
|
||||
char server[255];
|
||||
unsigned port;
|
||||
unsigned delay_frames;
|
||||
unsigned check_frames;
|
||||
bool is_spectate;
|
||||
bool stateless_mode;
|
||||
int check_frames;
|
||||
bool swap_input;
|
||||
bool nat_traversal;
|
||||
char password[128];
|
||||
char spectate_password[128];
|
||||
} netplay;
|
||||
#endif
|
||||
|
||||
|
@ -881,11 +881,14 @@ THREAD
|
||||
NETPLAY
|
||||
============================================================ */
|
||||
#ifdef HAVE_NETWORKING
|
||||
#include "../network/netplay/netplay_net.c"
|
||||
#include "../network/netplay/netplay_spectate.c"
|
||||
#include "../network/netplay/netplay_common.c"
|
||||
#include "../network/netplay/netplay_delta.c"
|
||||
#include "../network/netplay/netplay_frontend.c"
|
||||
#include "../network/netplay/netplay_handshake.c"
|
||||
#include "../network/netplay/netplay_init.c"
|
||||
#include "../network/netplay/netplay_io.c"
|
||||
#include "../network/netplay/netplay_sync.c"
|
||||
#include "../network/netplay/netplay_discovery.c"
|
||||
#include "../network/netplay/netplay.c"
|
||||
#include "../network/netplay/netplay_buf.c"
|
||||
#include "../libretro-common/net/net_compat.c"
|
||||
#include "../libretro-common/net/net_socket.c"
|
||||
#include "../libretro-common/net/net_http.c"
|
||||
|
@ -125,7 +125,8 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = {
|
||||
DECLARE_META_BIND(2, screenshot, RARCH_SCREENSHOT, MENU_ENUM_LABEL_VALUE_INPUT_META_SCREENSHOT),
|
||||
DECLARE_META_BIND(2, audio_mute, RARCH_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE),
|
||||
DECLARE_META_BIND(2, osk_toggle, RARCH_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK),
|
||||
DECLARE_META_BIND(2, netplay_flip_players, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP),
|
||||
DECLARE_META_BIND(2, netplay_flip_players_1_2, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP),
|
||||
DECLARE_META_BIND(2, netplay_game_watch, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH),
|
||||
DECLARE_META_BIND(2, slowmotion, RARCH_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION),
|
||||
DECLARE_META_BIND(2, enable_hotkey, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY),
|
||||
DECLARE_META_BIND(2, volume_up, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP),
|
||||
|
@ -72,6 +72,7 @@ enum
|
||||
RARCH_MUTE,
|
||||
RARCH_OSK,
|
||||
RARCH_NETPLAY_FLIP,
|
||||
RARCH_NETPLAY_GAME_WATCH,
|
||||
RARCH_SLOWMOTION,
|
||||
RARCH_ENABLE_HOTKEY,
|
||||
RARCH_VOLUME_UP,
|
||||
|
@ -3543,8 +3543,6 @@ const char *msg_hash_to_str_chs(enum msg_hash_enums msg)
|
||||
return "Stopping movie record.";
|
||||
case MSG_NETPLAY_FAILED:
|
||||
return "Failed to initialize netplay.";
|
||||
case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
|
||||
return "Movie playback has started. Cannot start netplay.";
|
||||
case MSG_NO_CONTENT_STARTING_DUMMY_CORE:
|
||||
return "No content, starting dummy core.";
|
||||
case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET:
|
||||
|
@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
|
||||
"Stopping movie record.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED,
|
||||
"Failed to initialize netplay.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
"Movie playback has started. Cannot start netplay.")
|
||||
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
|
||||
"No content, starting dummy core.")
|
||||
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
|
||||
|
@ -1418,8 +1418,6 @@ const char *msg_hash_to_str_es(enum msg_hash_enums msg)
|
||||
return "Deteniendo grabación de vídeo.";
|
||||
case MSG_NETPLAY_FAILED:
|
||||
return "Error al iniciar el juego en red.";
|
||||
case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
|
||||
return "Se ha iniciado una reproducción. No se puede ejecutar el juego en red.";
|
||||
case MSG_PAUSED:
|
||||
return "En pausa.";
|
||||
case MSG_PROGRAM:
|
||||
|
@ -1928,8 +1928,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
|
||||
"Arrêt de l'enregistrement vidéo.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED,
|
||||
"Échec de l'initialisation du jeu en réseau")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
"Lecture en cours. Impossible d'activer le jeu en réseau.")
|
||||
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
|
||||
"No content, starting dummy core.")
|
||||
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
|
||||
|
@ -1997,8 +1997,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
|
||||
"Stopping movie record.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED,
|
||||
"Failed to initialize netplay.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
"Movie playback has started. Cannot start netplay.")
|
||||
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
|
||||
"No content, starting dummy core.")
|
||||
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
|
||||
|
@ -571,8 +571,6 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
|
||||
"netplay_check_frames")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT,
|
||||
"netplay_client_swap_input")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
|
||||
"netplay_delay_frames")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DISCONNECT,
|
||||
"menu_netplay_disconnect")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE,
|
||||
@ -589,10 +587,16 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
|
||||
"netplay_nat_traversal")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NICKNAME,
|
||||
"netplay_nickname")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PASSWORD,
|
||||
"netplay_password")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS,
|
||||
"menu_netplay_settings")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
|
||||
"netplay_spectate_password")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
|
||||
"netplay_spectator_mode_enable")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
|
||||
"netplay_stateless_mode")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT,
|
||||
"netplay_tcp_udp_port")
|
||||
MSG_HASH(MENU_ENUM_LABEL_NETPLAY_LAN_SCAN_SETTINGS,
|
||||
|
@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
|
||||
"Stopping movie record.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED,
|
||||
"Failed to initialize netplay.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
"Movie playback has started. Cannot start netplay.")
|
||||
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
|
||||
"No content, starting dummy core.")
|
||||
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
|
||||
|
@ -925,8 +925,6 @@ const char *msg_hash_to_str_pl(enum msg_hash_enums msg)
|
||||
return "Zatrzymano nagrywanie filmu.";
|
||||
case MSG_NETPLAY_FAILED:
|
||||
return "Nie udało się zainicjalizować gry sieciowej.";
|
||||
case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
|
||||
return "Odtwarzanie filmu w toku. Nie można rozpocząć gry sieciowej.";
|
||||
case MSG_PAUSED:
|
||||
return "Wstrzymano.";
|
||||
case MSG_PROGRAM:
|
||||
|
@ -1961,8 +1961,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
|
||||
"Запись остановлена.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED,
|
||||
"Ошибка запуска сетевой игры.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
"Воспроизведение записи. Невозможно начать сетевую игру.")
|
||||
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
|
||||
"No content, starting dummy core.")
|
||||
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
|
||||
|
@ -91,6 +91,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
snprintf(s, len,
|
||||
"Netplay flip users.");
|
||||
break;
|
||||
case RARCH_NETPLAY_GAME_WATCH:
|
||||
snprintf(s, len,
|
||||
"Netplay toggle play/spectate mode.");
|
||||
break;
|
||||
case RARCH_SLOWMOTION:
|
||||
snprintf(s, len,
|
||||
"Hold for slowmotion.");
|
||||
@ -1557,6 +1561,15 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
"Increasing this value will increase \n"
|
||||
"performance, but introduce more latency.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
|
||||
snprintf(s, len,
|
||||
"Whether to run netplay in a mode not requiring\n"
|
||||
"save states. \n"
|
||||
" \n"
|
||||
"If set to true, a very fast network is required,\n"
|
||||
"but no rewinding is performed, so there will be\n"
|
||||
"no netplay jitter.\n");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
|
||||
snprintf(s, len,
|
||||
"The frequency in frames with which netplay \n"
|
||||
@ -1693,6 +1706,17 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
snprintf(s, len,
|
||||
"The address of the host to connect to.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_PASSWORD:
|
||||
snprintf(s, len,
|
||||
"The password for connecting to the netplay \n"
|
||||
"host. Used only in host mode.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD:
|
||||
snprintf(s, len,
|
||||
"The password for connecting to the netplay \n"
|
||||
"host with only spectator privileges. Used \n"
|
||||
"only in host mode.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_STDIN_CMD_ENABLE:
|
||||
snprintf(s, len,
|
||||
"Enable stdin command interface.");
|
||||
@ -1787,6 +1811,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
snprintf(s, len,
|
||||
"Netplay flip users.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_GAME_WATCH:
|
||||
snprintf(s, len,
|
||||
"Netplay toggle play/spectate mode.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_CHEAT_INDEX_PLUS:
|
||||
snprintf(s, len,
|
||||
"Increment cheat index.\n");
|
||||
|
@ -46,6 +46,66 @@ MSG_HASH(
|
||||
MSG_WAITING_FOR_CLIENT,
|
||||
"Waiting for client ..."
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME,
|
||||
"You have left the game"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N,
|
||||
"You have joined as player %d"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_IMPLEMENTATIONS_DIFFER,
|
||||
"Implementations differ. Make sure you're using the exact same versions of RetroArch and the core."
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_ENDIAN_DEPENDENT,
|
||||
"This core does not support inter-architecture netplay between these systems"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_PLATFORM_DEPENDENT,
|
||||
"This core does not support inter-architecture netplay"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_ENTER_PASSWORD,
|
||||
"Enter netplay server password:"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_INCORRECT_PASSWORD,
|
||||
"Incorrect password"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_SERVER_NAMED_HANGUP,
|
||||
"\"%s\" has disconnected"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_SERVER_HANGUP,
|
||||
"A netplay client has disconnected"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_CLIENT_HANGUP,
|
||||
"Netplay disconnected"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED,
|
||||
"You do not have permission to play"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS,
|
||||
"There are no free player slots"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_CANNOT_PLAY,
|
||||
"Cannot switch to play mode"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_PEER_PAUSED,
|
||||
"Netplay peer \"%s\" paused"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_NETPLAY_CHANGED_NICK,
|
||||
"Your nickname changed to \"%s\""
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT,
|
||||
"Give hardware-rendered cores their own private context. Avoids having to assume hardware state changes inbetween frames."
|
||||
@ -714,6 +774,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE,
|
||||
"Audio mute toggle")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP,
|
||||
"Netplay flip users")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH,
|
||||
"Netplay toggle play/spectate mode")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OSK,
|
||||
"On-screen keyboard toggle")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OVERLAY_NEXT,
|
||||
@ -918,12 +980,18 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_MODE,
|
||||
"Netplay Client Enable")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NICKNAME,
|
||||
"Username")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD,
|
||||
"Server Password")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS,
|
||||
"Netplay settings")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_STATELESS_MODE,
|
||||
"Netplay Stateless Mode")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD,
|
||||
"Server Spectate-Only Password")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE,
|
||||
"Netplay Spectator Enable")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_TCP_UDP_PORT,
|
||||
"Netplay TCP/UDP Port")
|
||||
"Netplay TCP Port")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL,
|
||||
"Netplay NAT Traversal")
|
||||
MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_ENABLE,
|
||||
@ -1986,8 +2054,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED,
|
||||
"Stopping movie record.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED,
|
||||
"Failed to initialize netplay.")
|
||||
MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
"Movie playback has started. Cannot start netplay.")
|
||||
MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE,
|
||||
"No content, starting dummy core.")
|
||||
MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET,
|
||||
|
@ -3591,8 +3591,6 @@ const char *msg_hash_to_str_vn(enum msg_hash_enums msg)
|
||||
return "Stopping movie record.";
|
||||
case MSG_NETPLAY_FAILED:
|
||||
return "Failed to initialize netplay.";
|
||||
case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED:
|
||||
return "Movie playback has started. Cannot start netplay.";
|
||||
case MSG_NO_CONTENT_STARTING_DUMMY_CORE:
|
||||
return "No content, starting dummy core.";
|
||||
case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET:
|
||||
|
@ -69,6 +69,9 @@ int socket_select(int nfds, fd_set *readfs, fd_set *writefds,
|
||||
|
||||
int socket_send_all_blocking(int fd, const void *data_, size_t size, bool no_signal);
|
||||
|
||||
ssize_t socket_send_all_nonblocking(int fd, const void *data_, size_t size,
|
||||
bool no_signal);
|
||||
|
||||
int socket_receive_all_blocking(int fd, void *data_, size_t size);
|
||||
|
||||
ssize_t socket_receive_all_nonblocking(int fd, bool *error,
|
||||
|
@ -78,7 +78,11 @@ ssize_t socket_receive_all_nonblocking(int fd, bool *error,
|
||||
return ret;
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
/* Socket closed */
|
||||
*error = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (isagain(ret))
|
||||
return 0;
|
||||
@ -179,6 +183,36 @@ int socket_send_all_blocking(int fd, const void *data_, size_t size,
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t socket_send_all_nonblocking(int fd, const void *data_, size_t size,
|
||||
bool no_signal)
|
||||
{
|
||||
const uint8_t *data = (const uint8_t*)data_;
|
||||
ssize_t sent = 0;
|
||||
|
||||
while (size)
|
||||
{
|
||||
ssize_t ret = send(fd, (const char*)data, size,
|
||||
no_signal ? MSG_NOSIGNAL : 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
if (isagain(ret))
|
||||
break;
|
||||
|
||||
return -1;
|
||||
}
|
||||
else if (ret == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
data += ret;
|
||||
size -= ret;
|
||||
sent += ret;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool socket_bind(int fd, void *data)
|
||||
{
|
||||
int yes = 1;
|
||||
|
@ -4690,25 +4690,29 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data)
|
||||
PARSE_ONLY_UINT, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
|
||||
PARSE_ONLY_UINT, false) != -1)
|
||||
MENU_ENUM_LABEL_NETPLAY_PASSWORD,
|
||||
PARSE_ONLY_STRING, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
|
||||
PARSE_ONLY_STRING, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
|
||||
PARSE_ONLY_BOOL, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
|
||||
PARSE_ONLY_UINT, false) != -1)
|
||||
PARSE_ONLY_INT, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
|
||||
MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
|
||||
PARSE_ONLY_BOOL, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT,
|
||||
PARSE_ONLY_BOOL, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL,
|
||||
PARSE_ONLY_BOOL, false) != -1)
|
||||
count++;
|
||||
if (menu_displaylist_parse_settings_enum(menu, info,
|
||||
MENU_ENUM_LABEL_NETWORK_CMD_ENABLE,
|
||||
PARSE_ONLY_BOOL, false) != -1)
|
||||
|
@ -1721,34 +1721,16 @@ void general_write_handler(void *data)
|
||||
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
|
||||
#endif
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE:
|
||||
case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE:
|
||||
#ifdef HAVE_NETWORKING
|
||||
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
|
||||
#endif
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES:
|
||||
#ifdef HAVE_NETWORKING
|
||||
{
|
||||
bool val = (settings->netplay.delay_frames > 0);
|
||||
|
||||
if (val)
|
||||
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL);
|
||||
else
|
||||
retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL);
|
||||
}
|
||||
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL);
|
||||
#endif
|
||||
break;
|
||||
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
|
||||
#ifdef HAVE_NETWORKING
|
||||
{
|
||||
bool val = (settings->netplay.check_frames > 0);
|
||||
|
||||
if (val)
|
||||
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
|
||||
else
|
||||
retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -5582,19 +5564,48 @@ static bool setting_append_list(
|
||||
menu_settings_list_current_add_range(list, list_info, 0, 65535, 1, true, true);
|
||||
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
CONFIG_UINT(
|
||||
CONFIG_STRING(
|
||||
list, list_info,
|
||||
&settings->netplay.delay_frames,
|
||||
MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
|
||||
MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES,
|
||||
netplay_delay_frames,
|
||||
settings->netplay.password,
|
||||
sizeof(settings->netplay.password),
|
||||
MENU_ENUM_LABEL_NETPLAY_PASSWORD,
|
||||
MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD,
|
||||
"",
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
general_write_handler,
|
||||
general_read_handler);
|
||||
menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false);
|
||||
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
|
||||
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
CONFIG_STRING(
|
||||
list, list_info,
|
||||
settings->netplay.spectate_password,
|
||||
sizeof(settings->netplay.spectate_password),
|
||||
MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD,
|
||||
MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD,
|
||||
"",
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
general_write_handler,
|
||||
general_read_handler);
|
||||
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT);
|
||||
|
||||
CONFIG_BOOL(
|
||||
list, list_info,
|
||||
&settings->netplay.stateless_mode,
|
||||
MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE,
|
||||
MENU_ENUM_LABEL_VALUE_NETPLAY_STATELESS_MODE,
|
||||
false,
|
||||
MENU_ENUM_LABEL_VALUE_OFF,
|
||||
MENU_ENUM_LABEL_VALUE_ON,
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
general_write_handler,
|
||||
general_read_handler,
|
||||
SD_FLAG_NONE);
|
||||
|
||||
CONFIG_UINT(
|
||||
list, list_info,
|
||||
@ -5607,24 +5618,9 @@ static bool setting_append_list(
|
||||
parent_group,
|
||||
general_write_handler,
|
||||
general_read_handler);
|
||||
menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false);
|
||||
menu_settings_list_current_add_range(list, list_info, -600, 600, 1, true, false);
|
||||
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
|
||||
|
||||
CONFIG_BOOL(
|
||||
list, list_info,
|
||||
&settings->netplay.is_spectate,
|
||||
MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
|
||||
MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE,
|
||||
false,
|
||||
MENU_ENUM_LABEL_VALUE_OFF,
|
||||
MENU_ENUM_LABEL_VALUE_ON,
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
general_write_handler,
|
||||
general_read_handler,
|
||||
SD_FLAG_NONE);
|
||||
|
||||
CONFIG_BOOL(
|
||||
list, list_info,
|
||||
&settings->netplay.nat_traversal,
|
||||
|
23
msg_hash.h
23
msg_hash.h
@ -140,12 +140,28 @@ enum msg_hash_enums
|
||||
MSG_UNKNOWN = 0,
|
||||
MSG_SETTING_DISK_IN_TRAY,
|
||||
MSG_FAILED_TO_SET_DISK,
|
||||
MSG_NETPLAY_FAILED,
|
||||
MSG_NETPLAY_USERS_HAS_FLIPPED,
|
||||
MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED,
|
||||
MSG_CONNECTING_TO_NETPLAY_HOST,
|
||||
MSG_NETPLAY_LAN_SCAN_COMPLETE,
|
||||
MSG_NETPLAY_LAN_SCANNING,
|
||||
MSG_WAITING_FOR_CLIENT,
|
||||
MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME,
|
||||
MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N,
|
||||
MSG_NETPLAY_IMPLEMENTATIONS_DIFFER,
|
||||
MSG_NETPLAY_ENDIAN_DEPENDENT,
|
||||
MSG_NETPLAY_PLATFORM_DEPENDENT,
|
||||
MSG_NETPLAY_ENTER_PASSWORD,
|
||||
MSG_NETPLAY_INCORRECT_PASSWORD,
|
||||
MSG_NETPLAY_SERVER_NAMED_HANGUP,
|
||||
MSG_NETPLAY_SERVER_HANGUP,
|
||||
MSG_NETPLAY_CLIENT_HANGUP,
|
||||
MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED,
|
||||
MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS,
|
||||
MSG_NETPLAY_CANNOT_PLAY,
|
||||
MSG_NETPLAY_PEER_PAUSED,
|
||||
MSG_NETPLAY_CHANGED_NICK,
|
||||
MSG_AUTODETECT,
|
||||
MSG_AUDIO_VOLUME,
|
||||
MSG_LIBRETRO_FRONTEND,
|
||||
@ -273,8 +289,6 @@ enum msg_hash_enums
|
||||
MSG_REWIND_INIT_FAILED,
|
||||
MSG_REWIND_INIT_FAILED_THREADED_AUDIO,
|
||||
MSG_LIBRETRO_ABI_BREAK,
|
||||
MSG_NETPLAY_FAILED,
|
||||
MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED,
|
||||
MSG_DETECTED_VIEWPORT_OF,
|
||||
MSG_RECORDING_TO,
|
||||
MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING,
|
||||
@ -530,6 +544,7 @@ enum msg_hash_enums
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE,
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_OSK,
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP,
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH,
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION,
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY,
|
||||
MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP,
|
||||
@ -940,6 +955,7 @@ enum msg_hash_enums
|
||||
MENU_LABEL(UNDO_SAVE_STATE),
|
||||
|
||||
MENU_LABEL(NETPLAY_FLIP_PLAYERS),
|
||||
MENU_LABEL(NETPLAY_GAME_WATCH),
|
||||
MENU_LABEL(CHEAT_INDEX_MINUS),
|
||||
MENU_LABEL(CHEAT_INDEX_PLUS),
|
||||
MENU_LABEL(SHADER_NEXT),
|
||||
@ -994,6 +1010,7 @@ enum msg_hash_enums
|
||||
MENU_LABEL(BLUETOOTH_ENABLE),
|
||||
MENU_LABEL(NETPLAY_CLIENT_SWAP_INPUT),
|
||||
MENU_LABEL(NETPLAY_DELAY_FRAMES),
|
||||
MENU_LABEL(NETPLAY_STATELESS_MODE),
|
||||
MENU_LABEL(NETPLAY_CHECK_FRAMES),
|
||||
MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE),
|
||||
MENU_LABEL(NETPLAY_TCP_UDP_PORT),
|
||||
@ -1001,6 +1018,8 @@ enum msg_hash_enums
|
||||
MENU_LABEL(SORT_SAVEFILES_ENABLE),
|
||||
MENU_LABEL(SORT_SAVESTATES_ENABLE),
|
||||
MENU_LABEL(NETPLAY_IP_ADDRESS),
|
||||
MENU_LABEL(NETPLAY_PASSWORD),
|
||||
MENU_LABEL(NETPLAY_SPECTATE_PASSWORD),
|
||||
MENU_LABEL(NETPLAY_MODE),
|
||||
MENU_LABEL(PERFCNT_ENABLE),
|
||||
MENU_LABEL(OVERLAY_SCALE),
|
||||
|
@ -9,11 +9,7 @@ minor constraints:
|
||||
|
||||
Furthermore, if the core supports serialization (save states), Netplay allows
|
||||
for latency and clock drift, providing both host and client with a smooth
|
||||
experience.
|
||||
|
||||
Note that this documentation is all for (the poorly-named) "net" mode, which is
|
||||
the normal mode, and not "spectator" mode, which has its own whole host of
|
||||
problems.
|
||||
experience, as well as the option of more than two players.
|
||||
|
||||
Netplay in RetroArch works by expecting input to come delayed from the network,
|
||||
then rewinding and re-playing with the delayed input to get a consistent state.
|
||||
@ -24,32 +20,45 @@ correct frame.
|
||||
In terms of the implementation, Netplay is in effect a state buffer
|
||||
(implemented as a ring of buffers) and some pre- and post-frame behaviors.
|
||||
|
||||
Within the state buffers, there are three locations: self, other and read. Each
|
||||
refers to a frame, and a state buffer corresponding to that frame. The state
|
||||
buffer contains the savestate for the frame, and the input from both the local
|
||||
and remote players.
|
||||
Within the state buffers, there are three locations: self, other and unread.
|
||||
Each refers to a frame, and a state buffer corresponding to that frame. The
|
||||
state buffer contains the savestate for the frame, and the input from both the
|
||||
local and remote players.
|
||||
|
||||
Self is where the emulator believes itself to be, which may be ahead or behind
|
||||
of what it's read from the peer. Generally speaking, self progresses at 1 frame
|
||||
per frame, except when the network stalls, described later.
|
||||
per frame, but both fast-forwarding and stalling occur and are described later.
|
||||
|
||||
Other is where it was most recently in perfect sync: i.e., other-1 is the last
|
||||
frame from which both local and remote input have been actioned. As such, other
|
||||
is always less than or equal to both self and read. Since the state buffer is a
|
||||
ring, other is the first frame that it's unsafe to overwrite.
|
||||
|
||||
Read is where it's read up to, which can be slightly ahead of other since it
|
||||
can't always immediately act upon new data.
|
||||
Unread is the first frame at which not all players' data has been read, which
|
||||
can be slightly ahead of other since it can't always immediately act upon new
|
||||
data.
|
||||
|
||||
In general, other ≤ read and other ≤ self. In all likelihood, read ≤ self, but
|
||||
it is both possible and supported for the remote host to get ahead of the local
|
||||
host.
|
||||
In general, other ≤ unread and other ≤ self. In all likelihood, unread ≤ self,
|
||||
but it is both possible and supported for the remote host to get ahead of the
|
||||
local host.
|
||||
|
||||
The server has a slightly more complicated job as it can handle multiple
|
||||
clients, however it is not vastly more complicated: For each connection which
|
||||
is playing (i.e., has a controller), it maintains a per-player unread frame,
|
||||
and the global unread frame is the earliest of each player unread frame. The
|
||||
server forwards input data: When input data is received from an earlier frame
|
||||
than the server's current frame, it forwards it immediately. Otherwise, it
|
||||
forwards it when the frame is reached. i.e., during frame n, the server may
|
||||
send its own and any number of other players' data for frame n, but will never
|
||||
send frame n+1. This is because the server's clock is the arbiter of all
|
||||
synchronization-related events, such as flipping players, players joining and
|
||||
parting, and saving/loading states.
|
||||
|
||||
Pre-frame, Netplay serializes the core's state, polls for local input, and
|
||||
polls for input from the other side. If the input from the other side is too
|
||||
far behind, it stalls to allow the other side to catch up. To assure that this
|
||||
stalling does not block the UI thread, it is implemented similarly to pausing,
|
||||
rather than by blocking on the socket.
|
||||
polls for input from the network. If the input from the network is too far
|
||||
behind (i.e., unread is too far behind self), it stalls to allow the other side
|
||||
to catch up. To assure that this stalling does not block the UI thread, it is
|
||||
implemented similarly to pausing, rather than by blocking on the socket.
|
||||
|
||||
If input has not been received for the other side up to the current frame (the
|
||||
usual case), the remote input is simulated in a simplistic manner. Each
|
||||
@ -64,6 +73,10 @@ other and self > other. If so, it first checks whether its simulated remote
|
||||
data was correct. If it was, it simply moves other up. If not, it rewinds to
|
||||
other (by loading the serialized state there) and runs the core in replay mode
|
||||
with the real data up to the least of self and read, then sets other to that.
|
||||
To avoid latency building up, if the input from the network is too far ahead
|
||||
(i.e., unread is too far ahead of self), the frame limiter is momentarily
|
||||
disabled to catch up. Note that since network latency is expected, the normal
|
||||
case is the opposite: unread is behind self.
|
||||
|
||||
When in Netplay mode, the callback for receiving input is replaced by
|
||||
input_state_net. It is the role of input_state_net to combine the true local
|
||||
@ -78,6 +91,35 @@ two relevant actions: Reading the data and emulating with the data. The frame
|
||||
count is only incremented after the latter, so there is a period of time during
|
||||
which we've actually read self_frame_count+1 frames of local input.
|
||||
|
||||
Clients may come and go, and may start or stop playing even as they're
|
||||
connected. A client that is not playing is said to be “spectating”: It receives
|
||||
all the same data but sends none. A client may switch from spectating to
|
||||
playing by sending the appropriate request, at which point it is allotted a
|
||||
player number (see the SPECTATE, PLAY and MODE commands below).
|
||||
|
||||
The server may also be in spectator mode, but as the server never sends data
|
||||
early (i.e., it only forwards data on the frame it's reached), it must also
|
||||
inform all clients of its own current frame even if it has no input. The
|
||||
NOINPUT command is provided for that purpose.
|
||||
|
||||
The handshake procedure (this part is done by both server and client):
|
||||
1) Send connection header
|
||||
2) Receive and verify connection header
|
||||
3) Send nickname
|
||||
4) Receive nickname
|
||||
|
||||
For the client:
|
||||
5) Send PASSWORD if applicable
|
||||
4) Receive INFO
|
||||
5) Send INFO
|
||||
6) Receive SYNC
|
||||
|
||||
For the server:
|
||||
5) Receive PASSWORD if applicable
|
||||
6) Send INFO
|
||||
7) Receive INFO
|
||||
8) Send SYNC
|
||||
|
||||
|
||||
* Guarantee not actually a guarantee.
|
||||
|
||||
@ -100,32 +142,125 @@ Description:
|
||||
Negative Acknowledgement. If received, the connection is terminated. Sent
|
||||
whenever a command is malformed or otherwise not understood.
|
||||
|
||||
Command: INPUT
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
joypad input: uint32
|
||||
analog 1 input: uint32
|
||||
analog 2 input: uint32
|
||||
OPTIONAL state CRC: uint32
|
||||
}
|
||||
Description:
|
||||
Input state for each frame. Netplay must send an INPUT command for every
|
||||
frame in order to function at all.
|
||||
|
||||
Command: FLIP_PLAYERS
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
}
|
||||
Description:
|
||||
Flip players at the requested frame.
|
||||
|
||||
Command: DISCONNECT
|
||||
Payload: None
|
||||
Description:
|
||||
Gracefully disconnect. Not used.
|
||||
|
||||
Command: INPUT
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
is server data: 1 bit
|
||||
player: 31 bits
|
||||
joypad input: uint32
|
||||
analog 1 input: uint32
|
||||
analog 2 input: uint32
|
||||
}
|
||||
Description:
|
||||
Input state for each frame. Netplay must send an INPUT command for every
|
||||
frame in order to function at all. Client's player value is ignored. Server
|
||||
indicates which frames are its own input data because INPUT is a
|
||||
synchronization point: No synchronization events from the given frame may
|
||||
arrive after the server's input for the frame.
|
||||
|
||||
Command: NOINPUT
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
}
|
||||
Description:
|
||||
Sent by the server to indicate a frame has passed when the server is not
|
||||
otherwise sending data.
|
||||
|
||||
Command: NICK
|
||||
Payload:
|
||||
{
|
||||
nickname: char[32]
|
||||
}
|
||||
Description:
|
||||
Send nickname. Mandatory handshake command.
|
||||
|
||||
Command: PASSWORD
|
||||
Payload:
|
||||
{
|
||||
password hash: char[64]
|
||||
}
|
||||
Description:
|
||||
Send hashed password to server. Mandatory handshake command for clients if
|
||||
the server demands a password.
|
||||
|
||||
Command: INFO
|
||||
Payload
|
||||
{
|
||||
core name: char[32]
|
||||
core version: char[32]
|
||||
content CRC: uint32
|
||||
}
|
||||
Description:
|
||||
Send core/content info. Mandatory handshake command. Sent by server first,
|
||||
then by client, and must match. Server may send INFO with no payload, in
|
||||
which case the client sends its own info and expects the server to load the
|
||||
appropriate core and content then send a new INFO command. If mutual
|
||||
agreement cannot be achieved, the correct solution is to simply disconnect.
|
||||
|
||||
Command: SYNC
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
paused?: 1 bit
|
||||
connected players: 31 bits
|
||||
flip frame: uint32
|
||||
controller devices: uint32[16]
|
||||
client nick: char[32]
|
||||
sram: variable
|
||||
}
|
||||
Description:
|
||||
Initial state synchronization. Mandatory handshake command from server to
|
||||
client only. Connected players is a bitmap with the lowest bit being player
|
||||
0. Flip frame is 0 if players aren't flipped. Controller devices are the
|
||||
devices plugged into each controller port, and 16 is really MAX_USERS.
|
||||
Client is forced to have a different nick if multiple clients have the same
|
||||
nick.
|
||||
|
||||
Command: SPECTATE
|
||||
Payload: None
|
||||
Description:
|
||||
Request to enter spectate mode. The client should immediately consider
|
||||
itself to be in spectator mode and send no further input.
|
||||
|
||||
Command: PLAY
|
||||
Payload: None
|
||||
Description:
|
||||
Request to enter player mode. The client must wait for a MODE command
|
||||
before sending input.
|
||||
|
||||
Command: MODE
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
reserved: 14 bits
|
||||
playing: 1 bit
|
||||
you: 1 bit
|
||||
player number: uint16
|
||||
}
|
||||
Description:
|
||||
Inform of a connection mode change (possibly of the receiving client). Only
|
||||
server-to-client. Frame number is the first frame in which player data is
|
||||
expected, or the first frame in which player data is not expected. In the
|
||||
case of new players the frame number must be later than the last frame of
|
||||
the server's own input that has been sent, and in the case of leaving
|
||||
players the frame number must be later than the last frame of the relevant
|
||||
player's input that has been transmitted.
|
||||
|
||||
Command: MODE_REFUSED
|
||||
Payload:
|
||||
{
|
||||
reason: uint32
|
||||
}
|
||||
Description:
|
||||
Inform a client that its request to change modes has been refused.
|
||||
|
||||
Command: CRC
|
||||
Payload:
|
||||
{
|
||||
@ -155,9 +290,33 @@ Description:
|
||||
serialized state is zlib compressed. Otherwise it is uncompressed.
|
||||
|
||||
Command: PAUSE
|
||||
Payload: None
|
||||
Payload:
|
||||
{
|
||||
nickname: char[32]
|
||||
}
|
||||
Description:
|
||||
Indicates that the core is paused. The receiving peer should also pause.
|
||||
The server should pass it on, using the known correct name rather than the
|
||||
provided name.
|
||||
|
||||
Command: RESUME
|
||||
Payload: None
|
||||
Description:
|
||||
Indicates that the core is no longer paused.
|
||||
|
||||
Command: CHEATS
|
||||
Unused
|
||||
|
||||
Command: FLIP_PLAYERS
|
||||
Payload:
|
||||
{
|
||||
frame number: uint32
|
||||
}
|
||||
Description:
|
||||
Flip players at the requested frame.
|
||||
|
||||
Command: CFG
|
||||
Unused
|
||||
|
||||
Command: CFG_ACK
|
||||
Unused
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ enum rarch_netplay_ctl_state
|
||||
{
|
||||
RARCH_NETPLAY_CTL_NONE = 0,
|
||||
RARCH_NETPLAY_CTL_FLIP_PLAYERS,
|
||||
RARCH_NETPLAY_CTL_GAME_WATCH,
|
||||
RARCH_NETPLAY_CTL_POST_FRAME,
|
||||
RARCH_NETPLAY_CTL_PRE_FRAME,
|
||||
RARCH_NETPLAY_CTL_ENABLE_SERVER,
|
||||
@ -46,80 +47,6 @@ enum rarch_netplay_ctl_state
|
||||
RARCH_NETPLAY_CTL_DISCONNECT
|
||||
};
|
||||
|
||||
enum netplay_cmd
|
||||
{
|
||||
/* Basic commands */
|
||||
|
||||
/* Acknowlegement response */
|
||||
NETPLAY_CMD_ACK = 0x0000,
|
||||
|
||||
/* Failed acknowlegement response */
|
||||
NETPLAY_CMD_NAK = 0x0001,
|
||||
|
||||
/* Input data */
|
||||
NETPLAY_CMD_INPUT = 0x0002,
|
||||
|
||||
/* Misc. commands */
|
||||
|
||||
/* Swap inputs between player 1 and player 2 */
|
||||
NETPLAY_CMD_FLIP_PLAYERS = 0x0003,
|
||||
|
||||
/* Toggle spectate/join mode */
|
||||
NETPLAY_CMD_SPECTATE = 0x0004,
|
||||
|
||||
/* Gracefully disconnects from host */
|
||||
NETPLAY_CMD_DISCONNECT = 0x0005,
|
||||
|
||||
/* Sends multiple config requests over,
|
||||
* See enum netplay_cmd_cfg */
|
||||
NETPLAY_CMD_CFG = 0x0006,
|
||||
|
||||
/* CMD_CFG streamlines sending multiple
|
||||
configurations. This acknowledges
|
||||
each one individually */
|
||||
NETPLAY_CMD_CFG_ACK = 0x0007,
|
||||
|
||||
/* Loading and synchronization */
|
||||
|
||||
/* Send the CRC hash of a frame's state */
|
||||
NETPLAY_CMD_CRC = 0x0010,
|
||||
|
||||
/* Request a savestate */
|
||||
NETPLAY_CMD_REQUEST_SAVESTATE = 0x0011,
|
||||
|
||||
/* Send a savestate for the client to load */
|
||||
NETPLAY_CMD_LOAD_SAVESTATE = 0x0012,
|
||||
|
||||
/* Sends over cheats enabled on client */
|
||||
NETPLAY_CMD_CHEATS = 0x0013,
|
||||
|
||||
/* Controlling game playback */
|
||||
|
||||
/* Pauses the game, takes no arguments */
|
||||
NETPLAY_CMD_PAUSE = 0x0030,
|
||||
|
||||
/* Resumes the game, takes no arguments */
|
||||
NETPLAY_CMD_RESUME = 0x0031
|
||||
};
|
||||
|
||||
/* These are the configurations sent by NETPLAY_CMD_CFG. */
|
||||
enum netplay_cmd_cfg
|
||||
{
|
||||
/* Nickname */
|
||||
NETPLAY_CFG_NICK = 0x0001,
|
||||
|
||||
/* input.netplay_client_swap_input */
|
||||
NETPLAY_CFG_SWAP_INPUT = 0x0002,
|
||||
|
||||
/* netplay.delay_frames */
|
||||
NETPLAY_CFG_DELAY_FRAMES = 0x0004,
|
||||
|
||||
/* For more than 2 players */
|
||||
NETPLAY_CFG_PLAYER_SLOT = 0x0008
|
||||
};
|
||||
|
||||
void input_poll_net(void);
|
||||
|
||||
int16_t input_state_net(unsigned port, unsigned device,
|
||||
unsigned idx, unsigned id);
|
||||
|
||||
@ -130,93 +57,13 @@ void audio_sample_net(int16_t left, int16_t right);
|
||||
|
||||
size_t audio_sample_batch_net(const int16_t *data, size_t frames);
|
||||
|
||||
/**
|
||||
* netplay_new:
|
||||
* @direct_host : Netplay host discovered from scanning.
|
||||
* @server : IP address of server.
|
||||
* @port : Port of server.
|
||||
* @delay_frames : Amount of delay frames.
|
||||
* @check_frames : Frequency with which to check CRCs.
|
||||
* @cb : Libretro callbacks.
|
||||
* @spectate : If true, enable spectator mode.
|
||||
* @nat_traversal : If true, attempt NAT traversal.
|
||||
* @nick : Nickname of user.
|
||||
* @quirks : Netplay quirks.
|
||||
*
|
||||
* Creates a new netplay handle. A NULL host means we're
|
||||
* hosting (user 1).
|
||||
*
|
||||
* Returns: new netplay handle.
|
||||
**/
|
||||
netplay_t *netplay_new(void *direct_host, const char *server,
|
||||
uint16_t port, unsigned delay_frames, unsigned check_frames,
|
||||
const struct retro_callbacks *cb, bool spectate, bool nat_traversal,
|
||||
const char *nick, uint64_t quirks);
|
||||
|
||||
/**
|
||||
* netplay_free:
|
||||
* @netplay : pointer to netplay object
|
||||
*
|
||||
* Frees netplay handle.
|
||||
**/
|
||||
void netplay_free(netplay_t *handle);
|
||||
|
||||
/**
|
||||
* netplay_pre_frame:
|
||||
* @netplay : pointer to netplay object
|
||||
*
|
||||
* Pre-frame for Netplay.
|
||||
* Call this before running retro_run().
|
||||
*
|
||||
* Returns: true (1) if the frontend is clear to emulate the frame, false (0)
|
||||
* if we're stalled or paused
|
||||
**/
|
||||
bool netplay_pre_frame(netplay_t *handle);
|
||||
|
||||
/**
|
||||
* netplay_post_frame:
|
||||
* @netplay : pointer to netplay object
|
||||
*
|
||||
* Post-frame for Netplay.
|
||||
* We check if we have new input and replay from recorded input.
|
||||
* Call this after running retro_run().
|
||||
**/
|
||||
void netplay_post_frame(netplay_t *handle);
|
||||
|
||||
/**
|
||||
* netplay_frontend_paused
|
||||
* @netplay : pointer to netplay object
|
||||
* @paused : true if frontend is paused
|
||||
*
|
||||
* Inform Netplay of the frontend's pause state (paused or otherwise)
|
||||
**/
|
||||
void netplay_frontend_paused(netplay_t *netplay, bool paused);
|
||||
|
||||
/**
|
||||
* netplay_load_savestate
|
||||
* @netplay : pointer to netplay object
|
||||
* @serial_info : the savestate being loaded, NULL means "load it yourself"
|
||||
* @save : whether to save the provided serial_info into the frame buffer
|
||||
*
|
||||
* Inform Netplay of a savestate load and send it to the other side
|
||||
**/
|
||||
void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save);
|
||||
|
||||
/**
|
||||
* netplay_disconnect
|
||||
* @netplay : pointer to netplay object
|
||||
*
|
||||
* Disconnect netplay.
|
||||
*
|
||||
* Returns: true (1) if successful. At present, cannot fail.
|
||||
**/
|
||||
bool netplay_disconnect(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* init_netplay
|
||||
* @is_spectate : true if running in spectate mode
|
||||
* @direct_host : Host to connect to directly, if applicable (client only)
|
||||
* @server : server address to connect to (client only)
|
||||
* @port : TCP port to host on/connect to
|
||||
* @play_password : Password required to play (server only)
|
||||
* @spectate_password : Password required to connect (server only)
|
||||
*
|
||||
* Initializes netplay.
|
||||
*
|
||||
@ -224,7 +71,8 @@ bool netplay_disconnect(netplay_t *netplay);
|
||||
*
|
||||
* Returns: true (1) if successful, otherwise false (0).
|
||||
**/
|
||||
bool init_netplay(bool is_spectate, void *direct_host, const char *server, unsigned port);
|
||||
bool init_netplay(void *direct_host, const char *server, unsigned port,
|
||||
const char *play_password, const char *spectate_password);
|
||||
|
||||
void deinit_netplay(void);
|
||||
|
||||
|
357
network/netplay/netplay_buf.c
Normal file
357
network/netplay/netplay_buf.c
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
66
network/netplay/netplay_delta.c
Normal file
66
network/netplay/netplay_delta.c
Normal 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);
|
||||
}
|
@ -32,6 +32,7 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <compat/strl.h>
|
||||
@ -191,6 +192,11 @@ error:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* netplay_lan_ad_server
|
||||
*
|
||||
* Respond to any LAN ad queries that the netplay server has received.
|
||||
*/
|
||||
bool netplay_lan_ad_server(netplay_t *netplay)
|
||||
{
|
||||
fd_set fds;
|
||||
|
1019
network/netplay/netplay_frontend.c
Normal file
1019
network/netplay/netplay_frontend.c
Normal file
File diff suppressed because it is too large
Load Diff
1024
network/netplay/netplay_handshake.c
Normal file
1024
network/netplay/netplay_handshake.c
Normal file
File diff suppressed because it is too large
Load Diff
558
network/netplay/netplay_init.c
Normal file
558
network/netplay/netplay_init.c
Normal 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
1371
network/netplay/netplay_io.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
@ -24,22 +24,29 @@
|
||||
#include <net/net_natt.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <streams/trans_stream.h>
|
||||
#include <retro_endianness.h>
|
||||
|
||||
#include "../../core.h"
|
||||
#include "../../msg_hash.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
#define HAVE_IPV6
|
||||
#endif
|
||||
#define WORDS_PER_INPUT 3 /* Buttons, left stick, right stick */
|
||||
#define WORDS_PER_FRAME (WORDS_PER_INPUT+2) /* + frameno, playerno */
|
||||
|
||||
#define NETPLAY_PROTOCOL_VERSION 4
|
||||
|
||||
#define WORDS_PER_FRAME 4 /* Allows us to send 128 bits worth of state per frame. */
|
||||
#define MAX_SPECTATORS 16
|
||||
#define RARCH_DEFAULT_PORT 55435
|
||||
#define RARCH_DEFAULT_NICK "Anonymous"
|
||||
|
||||
#define NETPLAY_PROTOCOL_VERSION 3
|
||||
#define NETPLAY_NICK_LEN 32
|
||||
#define NETPLAY_PASS_LEN 128
|
||||
#define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */
|
||||
|
||||
#define MAX_SERVER_STALL_TIME_USEC (5*1000*1000)
|
||||
#define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000)
|
||||
#define MAX_RETRIES 16
|
||||
#define RETRY_MS 500
|
||||
|
||||
#define NETPLAY_MAX_STALL_FRAMES 60
|
||||
#define NETPLAY_FRAME_RUN_TIME_WINDOW 120
|
||||
|
||||
#define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1)
|
||||
#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size)
|
||||
@ -78,6 +85,130 @@
|
||||
#define NETPLAY_COMPRESSION_SUPPORTED 0
|
||||
#endif
|
||||
|
||||
enum netplay_cmd
|
||||
{
|
||||
/* Basic commands */
|
||||
|
||||
/* Acknowlegement response */
|
||||
NETPLAY_CMD_ACK = 0x0000,
|
||||
|
||||
/* Failed acknowlegement response */
|
||||
NETPLAY_CMD_NAK = 0x0001,
|
||||
|
||||
/* Gracefully disconnects from host */
|
||||
NETPLAY_CMD_DISCONNECT = 0x0002,
|
||||
|
||||
/* Input data */
|
||||
NETPLAY_CMD_INPUT = 0x0003,
|
||||
|
||||
/* Non-input data */
|
||||
NETPLAY_CMD_NOINPUT = 0x0004,
|
||||
|
||||
/* Initialization commands */
|
||||
|
||||
/* Inform the other side of our nick (must be first command) */
|
||||
NETPLAY_CMD_NICK = 0x0020,
|
||||
|
||||
/* Give the connection password */
|
||||
NETPLAY_CMD_PASSWORD = 0x0021,
|
||||
|
||||
/* Give core/content info */
|
||||
NETPLAY_CMD_INFO = 0x0022,
|
||||
|
||||
/* Initial synchronization info (frame, sram, player info) */
|
||||
NETPLAY_CMD_SYNC = 0x0023,
|
||||
|
||||
/* Join spectator mode */
|
||||
NETPLAY_CMD_SPECTATE = 0x0024,
|
||||
|
||||
/* Join play mode */
|
||||
NETPLAY_CMD_PLAY = 0x0025,
|
||||
|
||||
/* Report player mode */
|
||||
NETPLAY_CMD_MODE = 0x0026,
|
||||
|
||||
/* Report player mode refused */
|
||||
NETPLAY_CMD_MODE_REFUSED = 0x0027,
|
||||
|
||||
/* Loading and synchronization */
|
||||
|
||||
/* Send the CRC hash of a frame's state */
|
||||
NETPLAY_CMD_CRC = 0x0040,
|
||||
|
||||
/* Request a savestate */
|
||||
NETPLAY_CMD_REQUEST_SAVESTATE = 0x0041,
|
||||
|
||||
/* Send a savestate for the client to load */
|
||||
NETPLAY_CMD_LOAD_SAVESTATE = 0x0042,
|
||||
|
||||
/* Pauses the game, takes no arguments */
|
||||
NETPLAY_CMD_PAUSE = 0x0043,
|
||||
|
||||
/* Resumes the game, takes no arguments */
|
||||
NETPLAY_CMD_RESUME = 0x0044,
|
||||
|
||||
/* Sends over cheats enabled on client (unsupported) */
|
||||
NETPLAY_CMD_CHEATS = 0x0045,
|
||||
|
||||
/* Misc. commands */
|
||||
|
||||
/* Swap inputs between player 1 and player 2 */
|
||||
NETPLAY_CMD_FLIP_PLAYERS = 0x0060,
|
||||
|
||||
/* Sends multiple config requests over,
|
||||
* See enum netplay_cmd_cfg */
|
||||
NETPLAY_CMD_CFG = 0x0061,
|
||||
|
||||
/* CMD_CFG streamlines sending multiple
|
||||
configurations. This acknowledges
|
||||
each one individually */
|
||||
NETPLAY_CMD_CFG_ACK = 0x0062
|
||||
};
|
||||
|
||||
#define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31)
|
||||
#define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31)
|
||||
#define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<17)
|
||||
#define NETPLAY_CMD_MODE_BIT_YOU (1U<<16)
|
||||
|
||||
/* These are the reasons given for mode changes to be rejected */
|
||||
enum netplay_cmd_mode_reasons
|
||||
{
|
||||
/* Other/unknown reason */
|
||||
NETPLAY_CMD_MODE_REFUSED_REASON_OTHER,
|
||||
|
||||
/* You don't have permission to play */
|
||||
NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED,
|
||||
|
||||
/* There are no free player slots */
|
||||
NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS
|
||||
};
|
||||
|
||||
enum rarch_netplay_connection_mode
|
||||
{
|
||||
NETPLAY_CONNECTION_NONE = 0,
|
||||
|
||||
/* Initialization: */
|
||||
NETPLAY_CONNECTION_INIT, /* Waiting for header */
|
||||
NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */
|
||||
NETPLAY_CONNECTION_PRE_PASSWORD, /* Waiting for password */
|
||||
NETPLAY_CONNECTION_PRE_INFO, /* Waiting for core/content info */
|
||||
NETPLAY_CONNECTION_PRE_SYNC, /* Waiting for sync */
|
||||
|
||||
/* Ready: */
|
||||
NETPLAY_CONNECTION_CONNECTED, /* Modes above this are connected */
|
||||
NETPLAY_CONNECTION_SPECTATING, /* Spectator mode */
|
||||
NETPLAY_CONNECTION_PLAYING /* Normal ready state */
|
||||
};
|
||||
|
||||
enum rarch_netplay_stall_reason
|
||||
{
|
||||
NETPLAY_STALL_NONE = 0,
|
||||
NETPLAY_STALL_RUNNING_FAST,
|
||||
NETPLAY_STALL_NO_CONNECTION
|
||||
};
|
||||
|
||||
typedef uint32_t netplay_input_state_t[WORDS_PER_INPUT];
|
||||
|
||||
struct delta_frame
|
||||
{
|
||||
bool used; /* a bit derpy, but this is how we know if the delta's been used at all */
|
||||
@ -89,74 +220,167 @@ struct delta_frame
|
||||
/* The CRC-32 of the serialized state if we've calculated it, else 0 */
|
||||
uint32_t crc;
|
||||
|
||||
uint32_t real_input_state[WORDS_PER_FRAME - 1];
|
||||
uint32_t simulated_input_state[WORDS_PER_FRAME - 1];
|
||||
uint32_t self_state[WORDS_PER_FRAME - 1];
|
||||
/* The real, simulated and local input. If we're playing, self_state is
|
||||
* mirrored to the appropriate real_input_state player. */
|
||||
netplay_input_state_t real_input_state[MAX_USERS];
|
||||
netplay_input_state_t simulated_input_state[MAX_USERS];
|
||||
netplay_input_state_t self_state;
|
||||
|
||||
/* Have we read local input? */
|
||||
bool have_local;
|
||||
|
||||
/* Have we read the real remote input? */
|
||||
bool have_remote;
|
||||
/* Have we read the real (remote) input? */
|
||||
bool have_real[MAX_USERS];
|
||||
|
||||
/* Is the current state as of self_frame_count using the real remote data? */
|
||||
bool used_real;
|
||||
/* Is the current state as of self_frame_count using the real (remote) data? */
|
||||
bool used_real[MAX_USERS];
|
||||
};
|
||||
|
||||
struct netplay_callbacks {
|
||||
bool (*pre_frame) (netplay_t *netplay);
|
||||
void (*post_frame)(netplay_t *netplay);
|
||||
bool (*info_cb) (netplay_t *netplay, unsigned frames);
|
||||
};
|
||||
|
||||
enum rarch_netplay_stall_reasons
|
||||
struct socket_buffer
|
||||
{
|
||||
RARCH_NETPLAY_STALL_NONE = 0,
|
||||
RARCH_NETPLAY_STALL_RUNNING_FAST,
|
||||
RARCH_NETPLAY_STALL_NO_CONNECTION
|
||||
unsigned char *data;
|
||||
size_t bufsz;
|
||||
size_t start, end;
|
||||
size_t read;
|
||||
};
|
||||
|
||||
/* Each connection gets a connection struct */
|
||||
struct netplay_connection
|
||||
{
|
||||
/* Is this connection buffer in use? */
|
||||
bool active;
|
||||
|
||||
/* fd associated with this connection */
|
||||
int fd;
|
||||
|
||||
/* Address of peer */
|
||||
struct sockaddr_storage addr;
|
||||
|
||||
/* Nickname of peer */
|
||||
char nick[NETPLAY_NICK_LEN];
|
||||
|
||||
/* Salt associated with password transaction */
|
||||
uint32_t salt;
|
||||
|
||||
/* Is this connection allowed to play (server only)? */
|
||||
bool can_play;
|
||||
|
||||
/* Buffers for sending and receiving data */
|
||||
struct socket_buffer send_packet_buffer, recv_packet_buffer;
|
||||
|
||||
/* Mode of the connection */
|
||||
enum rarch_netplay_connection_mode mode;
|
||||
|
||||
/* Player # of connected player */
|
||||
int player;
|
||||
|
||||
/* What compression does this peer support? */
|
||||
uint32_t compression_supported;
|
||||
|
||||
/* Is this player paused? */
|
||||
bool paused;
|
||||
|
||||
/* Is this connection stalling? */
|
||||
enum rarch_netplay_stall_reason stall;
|
||||
retro_time_t stall_time;
|
||||
};
|
||||
|
||||
/* Compression transcoder */
|
||||
struct compression_transcoder
|
||||
{
|
||||
const struct trans_stream_backend *compression_backend;
|
||||
void *compression_stream;
|
||||
const struct trans_stream_backend *decompression_backend;
|
||||
void *decompression_stream;
|
||||
};
|
||||
|
||||
struct netplay
|
||||
{
|
||||
char nick[32];
|
||||
char other_nick[32];
|
||||
struct sockaddr_storage other_addr;
|
||||
/* Are we the server? */
|
||||
bool is_server;
|
||||
|
||||
/* Our nickname */
|
||||
char nick[NETPLAY_NICK_LEN];
|
||||
|
||||
/* TCP connection for listening (server only) */
|
||||
int listen_fd;
|
||||
|
||||
/* Password required to play (server only) */
|
||||
char play_password[NETPLAY_PASS_LEN];
|
||||
|
||||
/* Password required to connect (server only) */
|
||||
char spectate_password[NETPLAY_PASS_LEN];
|
||||
|
||||
/* Our player number */
|
||||
uint32_t self_player;
|
||||
|
||||
/* Our mode and status */
|
||||
enum rarch_netplay_connection_mode self_mode;
|
||||
|
||||
/* All of our connections */
|
||||
struct netplay_connection *connections;
|
||||
size_t connections_size;
|
||||
struct netplay_connection one_connection; /* Client only */
|
||||
|
||||
/* Bitmap of players with controllers (whether local or remote) (low bit is
|
||||
* player 1) */
|
||||
uint32_t connected_players;
|
||||
|
||||
/* Maximum player number */
|
||||
uint32_t player_max;
|
||||
|
||||
struct retro_callbacks cbs;
|
||||
/* TCP connection for state sending, etc. Also used for commands */
|
||||
int fd;
|
||||
/* TCP port (if serving) */
|
||||
|
||||
/* TCP port (only set if serving) */
|
||||
uint16_t tcp_port;
|
||||
|
||||
/* NAT traversal info (if NAT traversal is used and serving) */
|
||||
bool nat_traversal;
|
||||
struct natt_status nat_traversal_state;
|
||||
/* Which port is governed by netplay (other user)? */
|
||||
unsigned port;
|
||||
bool has_connection;
|
||||
|
||||
struct delta_frame *buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
/* Compression transcoder */
|
||||
const struct trans_stream_backend *compression_backend;
|
||||
void *compression_stream;
|
||||
const struct trans_stream_backend *decompression_backend;
|
||||
void *decompression_stream;
|
||||
struct compression_transcoder compress_nil,
|
||||
compress_zlib;
|
||||
|
||||
/* A buffer into which to compress frames for transfer */
|
||||
uint8_t *zbuffer;
|
||||
size_t zbuffer_size;
|
||||
|
||||
/* Pointer where we are now. */
|
||||
/* The size of our packet buffers */
|
||||
size_t packet_buffer_size;
|
||||
|
||||
/* The current frame seen by the frontend */
|
||||
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;
|
||||
/* Pointer to where we are reading.
|
||||
* Generally, other_ptr <= read_ptr <= self_ptr. */
|
||||
size_t read_ptr;
|
||||
uint32_t other_frame_count;
|
||||
|
||||
/* Pointer to the first frame for which we're missing the data of at least
|
||||
* one connected player excluding ourself.
|
||||
* Generally, other_ptr <= unread_ptr <= self_ptr, but unread_ptr can get ahead
|
||||
* of self_ptr if the peer is running fast. */
|
||||
size_t unread_ptr;
|
||||
uint32_t unread_frame_count;
|
||||
|
||||
/* Pointer to the next frame to read from each player */
|
||||
size_t read_ptr[MAX_USERS];
|
||||
uint32_t read_frame_count[MAX_USERS];
|
||||
|
||||
/* Pointer to the next frame to read from the server (as it might not be a
|
||||
* player but still synchronizes) */
|
||||
size_t server_ptr;
|
||||
uint32_t server_frame_count;
|
||||
|
||||
/* A pointer used temporarily for replay. */
|
||||
size_t replay_ptr;
|
||||
uint32_t replay_frame_count;
|
||||
|
||||
/* Size of savestates */
|
||||
size_t state_size;
|
||||
|
||||
/* Are we replaying old frames? */
|
||||
@ -172,95 +396,403 @@ struct netplay
|
||||
/* Quirks in the savestate implementation */
|
||||
uint64_t quirks;
|
||||
|
||||
/* Force our state to be sent to the other side. Used when they request a
|
||||
* savestate, to send at the next pre-frame. */
|
||||
/* Force our state to be sent to all connections */
|
||||
bool force_send_savestate;
|
||||
|
||||
/* Have we requested a savestate as a sync point? */
|
||||
bool savestate_request_outstanding;
|
||||
|
||||
/* A buffer for outgoing input packets. */
|
||||
uint32_t packet_buffer[2 + WORDS_PER_FRAME];
|
||||
uint32_t self_frame_count;
|
||||
uint32_t read_frame_count;
|
||||
uint32_t other_frame_count;
|
||||
uint32_t replay_frame_count;
|
||||
uint32_t input_packet_buffer[2 + WORDS_PER_FRAME];
|
||||
|
||||
/* Our local socket info */
|
||||
struct addrinfo *addr;
|
||||
struct sockaddr_storage their_addr;
|
||||
bool has_client_addr;
|
||||
|
||||
/* Counter for timeouts */
|
||||
unsigned timeout_cnt;
|
||||
|
||||
/* Spectating. */
|
||||
struct {
|
||||
bool enabled;
|
||||
int fds[MAX_SPECTATORS];
|
||||
uint32_t frames[MAX_SPECTATORS];
|
||||
uint16_t *input;
|
||||
size_t input_ptr;
|
||||
size_t input_sz;
|
||||
} spectate;
|
||||
bool is_server;
|
||||
|
||||
/* User flipping
|
||||
* Flipping state. If frame >= flip_frame, we apply the flip.
|
||||
* If not, we apply the opposite, effectively creating a trigger point. */
|
||||
bool flip;
|
||||
uint32_t flip_frame;
|
||||
|
||||
/* Netplay pausing
|
||||
*/
|
||||
/* Netplay pausing */
|
||||
bool local_paused;
|
||||
bool remote_paused;
|
||||
|
||||
/* And stalling */
|
||||
uint32_t delay_frames;
|
||||
int stall;
|
||||
/* If true, never progress without peer input (stateless/rewindless mode) */
|
||||
bool stateless_mode;
|
||||
|
||||
/* We stall if we're far enough ahead that we couldn't transparently rewind.
|
||||
* To know if we could transparently rewind, we need to know how long
|
||||
* running a frame takes. We record that every frame and get a running
|
||||
* (window) average */
|
||||
retro_time_t frame_run_time[NETPLAY_FRAME_RUN_TIME_WINDOW];
|
||||
int frame_run_time_ptr;
|
||||
retro_time_t frame_run_time_sum, frame_run_time_avg;
|
||||
|
||||
/* Are we stalled? */
|
||||
enum rarch_netplay_stall_reason stall;
|
||||
|
||||
/* How long have we been stalled? */
|
||||
retro_time_t stall_time;
|
||||
|
||||
/* Frequency with which to check CRCs */
|
||||
uint32_t check_frames;
|
||||
/* Opposite of stalling, should we be catching up? */
|
||||
bool catch_up;
|
||||
|
||||
struct netplay_callbacks* net_cbs;
|
||||
/* Frequency with which to check CRCs */
|
||||
int check_frames;
|
||||
|
||||
/* Have we checked whether CRCs are valid at all? */
|
||||
bool crc_validity_checked;
|
||||
|
||||
/* Are they valid? */
|
||||
bool crcs_valid;
|
||||
};
|
||||
|
||||
struct netplay_callbacks* netplay_get_cbs_net(void);
|
||||
|
||||
struct netplay_callbacks* netplay_get_cbs_spectate(void);
|
||||
/***************************************************************
|
||||
* NETPLAY-BUF.C
|
||||
**************************************************************/
|
||||
|
||||
/* Normally called at init time, unless the INITIALIZATION quirk is set */
|
||||
bool netplay_init_serialization(netplay_t *netplay);
|
||||
/**
|
||||
* netplay_init_socket_buffer
|
||||
*
|
||||
* Initialize a new socket buffer.
|
||||
*/
|
||||
bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size);
|
||||
|
||||
/* Force serialization to be ready by fast-forwarding the core */
|
||||
bool netplay_wait_and_init_serialization(netplay_t *netplay);
|
||||
/**
|
||||
* netplay_resize_socket_buffer
|
||||
*
|
||||
* Resize the given socket_buffer's buffer to the requested size.
|
||||
*/
|
||||
bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize);
|
||||
|
||||
void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim);
|
||||
/**
|
||||
* netplay_deinit_socket_buffer
|
||||
*
|
||||
* Free a socket buffer.
|
||||
*/
|
||||
void netplay_deinit_socket_buffer(struct socket_buffer *sbuf);
|
||||
|
||||
void netplay_log_connection(const struct sockaddr_storage *their_addr,
|
||||
unsigned slot, const char *nick);
|
||||
/**
|
||||
* netplay_send
|
||||
*
|
||||
* Queue the given data for sending.
|
||||
*/
|
||||
bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf,
|
||||
size_t len);
|
||||
|
||||
bool netplay_get_nickname(netplay_t *netplay, int fd);
|
||||
/**
|
||||
* netplay_send_flush
|
||||
*
|
||||
* Flush unsent data in the given socket buffer, blocking to do so if
|
||||
* requested.
|
||||
*
|
||||
* Returns false only on socket failures, true otherwise.
|
||||
*/
|
||||
bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block);
|
||||
|
||||
bool netplay_send_nickname(netplay_t *netplay, int fd);
|
||||
/**
|
||||
* netplay_recv
|
||||
*
|
||||
* Receive buffered or fresh data.
|
||||
*
|
||||
* Returns number of bytes returned, which may be short or 0, or -1 on error.
|
||||
*/
|
||||
ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf,
|
||||
size_t len, bool block);
|
||||
|
||||
bool netplay_handshake(netplay_t *netplay);
|
||||
/**
|
||||
* netplay_recv_reset
|
||||
*
|
||||
* Reset our recv buffer so that future netplay_recvs will read the same data
|
||||
* again.
|
||||
*/
|
||||
void netplay_recv_reset(struct socket_buffer *sbuf);
|
||||
|
||||
uint32_t netplay_impl_magic(void);
|
||||
/**
|
||||
* netplay_recv_flush
|
||||
*
|
||||
* Flush our recv buffer, so a future netplay_recv_reset will reset to this
|
||||
* point.
|
||||
*/
|
||||
void netplay_recv_flush(struct socket_buffer *sbuf);
|
||||
|
||||
bool netplay_is_server(netplay_t* netplay);
|
||||
|
||||
bool netplay_is_spectate(netplay_t* netplay);
|
||||
/***************************************************************
|
||||
* NETPLAY-DELTA.C
|
||||
**************************************************************/
|
||||
|
||||
bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame);
|
||||
/**
|
||||
* netplay_delta_frame_ready
|
||||
*
|
||||
* Prepares, if possible, a delta frame for input, and reports whether it is
|
||||
* ready.
|
||||
*
|
||||
* Returns: True if the delta frame is ready for input at the given frame,
|
||||
* false otherwise.
|
||||
*/
|
||||
bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta,
|
||||
uint32_t frame);
|
||||
|
||||
/**
|
||||
* netplay_delta_frame_crc
|
||||
*
|
||||
* Get the CRC for the serialization of this frame.
|
||||
*/
|
||||
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta);
|
||||
|
||||
bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta);
|
||||
|
||||
bool netplay_cmd_request_savestate(netplay_t *netplay);
|
||||
|
||||
/* DISCOVERY: */
|
||||
/***************************************************************
|
||||
* NETPLAY-DISCOVERY.C
|
||||
**************************************************************/
|
||||
|
||||
/**
|
||||
* netplay_lan_ad_server
|
||||
*
|
||||
* Respond to any LAN ad queries that the netplay server has received.
|
||||
*/
|
||||
bool netplay_lan_ad_server(netplay_t *netplay);
|
||||
|
||||
|
||||
/***************************************************************
|
||||
* NETPLAY-FRONTEND.C
|
||||
**************************************************************/
|
||||
|
||||
/**
|
||||
* netplay_load_savestate
|
||||
* @netplay : pointer to netplay object
|
||||
* @serial_info : the savestate being loaded, NULL means
|
||||
* "load it yourself"
|
||||
* @save : Whether to save the provided serial_info
|
||||
* into the frame buffer
|
||||
*
|
||||
* Inform Netplay of a savestate load and send it to the other side
|
||||
**/
|
||||
void netplay_load_savestate(netplay_t *netplay,
|
||||
retro_ctx_serialize_info_t *serial_info, bool save);
|
||||
|
||||
/**
|
||||
* input_poll_net
|
||||
*
|
||||
* Poll the network if necessary.
|
||||
*/
|
||||
void input_poll_net(void);
|
||||
|
||||
/***************************************************************
|
||||
* NETPLAY-HANDSHAKE.C
|
||||
**************************************************************/
|
||||
|
||||
/**
|
||||
* netplay_handshake_init_send
|
||||
*
|
||||
* Initialize our handshake and send the first part of the handshake protocol.
|
||||
*/
|
||||
bool netplay_handshake_init_send(netplay_t *netplay,
|
||||
struct netplay_connection *connection);
|
||||
|
||||
/**
|
||||
* netplay_handshake
|
||||
*
|
||||
* Data receiver for all handshake states.
|
||||
*/
|
||||
bool netplay_handshake(netplay_t *netplay,
|
||||
struct netplay_connection *connection, bool *had_input);
|
||||
|
||||
|
||||
/***************************************************************
|
||||
* NETPLAY-INIT.C
|
||||
**************************************************************/
|
||||
|
||||
/**
|
||||
* netplay_try_init_serialization
|
||||
*
|
||||
* Try to initialize serialization. For quirky cores.
|
||||
*
|
||||
* Returns true if serialization is now ready, false otherwise.
|
||||
*/
|
||||
bool netplay_try_init_serialization(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_wait_and_init_serialization
|
||||
*
|
||||
* Try very hard to initialize serialization, simulating multiple frames if
|
||||
* necessary. For quirky cores.
|
||||
*
|
||||
* Returns true if serialization is now ready, false otherwise.
|
||||
*/
|
||||
bool netplay_wait_and_init_serialization(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_new:
|
||||
* @direct_host : Netplay host discovered from scanning.
|
||||
* @server : IP address of server.
|
||||
* @port : Port of server.
|
||||
* @play_password : Password required to play.
|
||||
* @spectate_password : Password required to connect.
|
||||
* @stateless_mode : Shall we run in stateless mode?
|
||||
* @check_frames : Frequency with which to check CRCs.
|
||||
* @cb : Libretro callbacks.
|
||||
* @nat_traversal : If true, attempt NAT traversal.
|
||||
* @nick : Nickname of user.
|
||||
* @quirks : Netplay quirks required for this session.
|
||||
*
|
||||
* Creates a new netplay handle. A NULL server means we're
|
||||
* hosting.
|
||||
*
|
||||
* Returns: new netplay data.
|
||||
*/
|
||||
netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port,
|
||||
const char *play_password, const char *spectate_password,
|
||||
bool stateless_mode, int check_frames,
|
||||
const struct retro_callbacks *cb, bool nat_traversal, const char *nick,
|
||||
uint64_t quirks);
|
||||
|
||||
/**
|
||||
* netplay_free
|
||||
* @netplay : pointer to netplay object
|
||||
*
|
||||
* Frees netplay data/
|
||||
*/
|
||||
void netplay_free(netplay_t *netplay);
|
||||
|
||||
|
||||
/***************************************************************
|
||||
* NETPLAY-IO.C
|
||||
**************************************************************/
|
||||
|
||||
/**
|
||||
* netplay_hangup:
|
||||
*
|
||||
* Disconnects an active Netplay connection due to an error
|
||||
*/
|
||||
void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection);
|
||||
|
||||
/**
|
||||
* netplay_send_cur_input
|
||||
*
|
||||
* Send the current input frame to a given connection.
|
||||
*
|
||||
* Returns true if successful, false otherwise.
|
||||
*/
|
||||
bool netplay_send_cur_input(netplay_t *netplay,
|
||||
struct netplay_connection *connection);
|
||||
|
||||
/**
|
||||
* netplay_send_raw_cmd
|
||||
*
|
||||
* Send a raw Netplay command to the given connection.
|
||||
*
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
bool netplay_send_raw_cmd(netplay_t *netplay,
|
||||
struct netplay_connection *connection, uint32_t cmd, const void *data,
|
||||
size_t size);
|
||||
|
||||
/**
|
||||
* netplay_send_raw_cmd_all
|
||||
*
|
||||
* Send a raw Netplay command to all connections, optionally excluding one
|
||||
* (typically the client that the relevant command came from)
|
||||
*/
|
||||
void netplay_send_raw_cmd_all(netplay_t *netplay,
|
||||
struct netplay_connection *except, uint32_t cmd, const void *data,
|
||||
size_t size);
|
||||
|
||||
/**
|
||||
* netplay_cmd_crc
|
||||
*
|
||||
* Send a CRC command to all active clients.
|
||||
*/
|
||||
bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta);
|
||||
|
||||
/**
|
||||
* netplay_cmd_request_savestate
|
||||
*
|
||||
* Send a savestate request command.
|
||||
*/
|
||||
bool netplay_cmd_request_savestate(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_cmd_mode
|
||||
*
|
||||
* Send a mode request command to either play or spectate.
|
||||
*/
|
||||
bool netplay_cmd_mode(netplay_t *netplay,
|
||||
struct netplay_connection *connection,
|
||||
enum rarch_netplay_connection_mode mode);
|
||||
|
||||
/**
|
||||
* netplay_poll_net_input
|
||||
*
|
||||
* Poll input from the network
|
||||
*/
|
||||
int netplay_poll_net_input(netplay_t *netplay, bool block);
|
||||
|
||||
/**
|
||||
* netplay_flip_port
|
||||
*
|
||||
* Should we flip ports 0 and 1?
|
||||
*/
|
||||
bool netplay_flip_port(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_announce_nat_traversal
|
||||
*
|
||||
* Announce successful NAT traversal.
|
||||
*/
|
||||
void netplay_announce_nat_traversal(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_init_nat_traversal
|
||||
*
|
||||
* Initialize the NAT traversal library and try to open a port
|
||||
*/
|
||||
void netplay_init_nat_traversal(netplay_t *netplay);
|
||||
|
||||
|
||||
/***************************************************************
|
||||
* NETPLAY-SYNC.C
|
||||
**************************************************************/
|
||||
|
||||
/**
|
||||
* netplay_update_unread_ptr
|
||||
*
|
||||
* Update the global unread_ptr and unread_frame_count to correspond to the
|
||||
* earliest unread frame count of any connected player
|
||||
*/
|
||||
void netplay_update_unread_ptr(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_simulate_input
|
||||
* @netplay : pointer to netplay object
|
||||
* @sim_ptr : frame index for which to simulate input
|
||||
* @resim : are we resimulating, or simulating this frame for the
|
||||
* first time?
|
||||
*
|
||||
* "Simulate" input by assuming it hasn't changed since the last read input.
|
||||
*/
|
||||
void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim);
|
||||
|
||||
/**
|
||||
* netplay_sync_pre_frame
|
||||
* @netplay : pointer to netplay object
|
||||
*
|
||||
* Pre-frame for Netplay synchronization.
|
||||
*/
|
||||
bool netplay_sync_pre_frame(netplay_t *netplay);
|
||||
|
||||
/**
|
||||
* netplay_sync_post_frame
|
||||
* @netplay : pointer to netplay object
|
||||
* @stalled : true if we're currently stalled
|
||||
*
|
||||
* Post-frame for Netplay synchronization.
|
||||
* We check if we have new input and replay from recorded input.
|
||||
*/
|
||||
void netplay_sync_post_frame(netplay_t *netplay, bool stalled);
|
||||
|
||||
#endif
|
||||
|
@ -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;
|
||||
}
|
527
network/netplay/netplay_sync.c
Normal file
527
network/netplay/netplay_sync.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
34
retroarch.c
34
retroarch.c
@ -104,6 +104,7 @@
|
||||
enum
|
||||
{
|
||||
RA_OPT_MENU = 256, /* must be outside the range of a char */
|
||||
RA_OPT_STATELESS,
|
||||
RA_OPT_CHECK_FRAMES,
|
||||
RA_OPT_PORT,
|
||||
RA_OPT_SPECTATE,
|
||||
@ -143,7 +144,7 @@ static bool has_set_state_path = false;
|
||||
static bool has_set_netplay_mode = false;
|
||||
static bool has_set_netplay_ip_address = false;
|
||||
static bool has_set_netplay_ip_port = false;
|
||||
static bool has_set_netplay_delay_frames = false;
|
||||
static bool has_set_netplay_stateless_mode = false;
|
||||
static bool has_set_netplay_check_frames = false;
|
||||
static bool has_set_ups_pref = false;
|
||||
static bool has_set_bps_pref = false;
|
||||
@ -339,10 +340,10 @@ static void retroarch_print_help(const char *arg0)
|
||||
puts(" -H, --host Host netplay as user 1.");
|
||||
puts(" -C, --connect=HOST Connect to netplay server as user 2.");
|
||||
puts(" --port=PORT Port used to netplay. Default is 55435.");
|
||||
puts(" -F, --frames=NUMBER Delay frames when using netplay.");
|
||||
puts(" --stateless Use \"stateless\" mode for netplay");
|
||||
puts(" (requires a very fast network).");
|
||||
puts(" --check-frames=NUMBER\n"
|
||||
" Check frames when using netplay.");
|
||||
puts(" --spectate Connect to netplay server as spectator.");
|
||||
#if defined(HAVE_NETWORK_CMD)
|
||||
puts(" --command Sends a command over UDP to an already "
|
||||
"running program process.");
|
||||
@ -427,10 +428,9 @@ static void retroarch_parse_input(int argc, char *argv[])
|
||||
#ifdef HAVE_NETWORKING
|
||||
{ "host", 0, NULL, 'H' },
|
||||
{ "connect", 1, NULL, 'C' },
|
||||
{ "frames", 1, NULL, 'F' },
|
||||
{ "stateless", 0, NULL, RA_OPT_STATELESS },
|
||||
{ "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES },
|
||||
{ "port", 1, NULL, RA_OPT_PORT },
|
||||
{ "spectate", 0, NULL, RA_OPT_SPECTATE },
|
||||
#if defined(HAVE_NETWORK_CMD)
|
||||
{ "command", 1, NULL, RA_OPT_COMMAND },
|
||||
#endif
|
||||
@ -707,10 +707,10 @@ static void retroarch_parse_input(int argc, char *argv[])
|
||||
sizeof(settings->netplay.server));
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
settings->netplay.delay_frames = strtol(optarg, NULL, 0);
|
||||
case RA_OPT_STATELESS:
|
||||
settings->netplay.stateless_mode = true;
|
||||
retroarch_override_setting_set(
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL);
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL);
|
||||
break;
|
||||
|
||||
case RA_OPT_CHECK_FRAMES:
|
||||
@ -725,12 +725,6 @@ static void retroarch_parse_input(int argc, char *argv[])
|
||||
settings->netplay.port = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case RA_OPT_SPECTATE:
|
||||
retroarch_override_setting_set(
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
|
||||
settings->netplay.is_spectate = true;
|
||||
break;
|
||||
|
||||
#if defined(HAVE_NETWORK_CMD)
|
||||
case RA_OPT_COMMAND:
|
||||
if (command_network_send((const char*)optarg))
|
||||
@ -1369,8 +1363,8 @@ bool retroarch_override_setting_is_set(enum rarch_override_setting enum_idx, voi
|
||||
return has_set_netplay_ip_address;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
|
||||
return has_set_netplay_ip_port;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
|
||||
return has_set_netplay_delay_frames;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
|
||||
return has_set_netplay_stateless_mode;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
|
||||
return has_set_netplay_check_frames;
|
||||
case RARCH_OVERRIDE_SETTING_UPS_PREF:
|
||||
@ -1426,8 +1420,8 @@ void retroarch_override_setting_set(enum rarch_override_setting enum_idx, void *
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
|
||||
has_set_netplay_ip_port = true;
|
||||
break;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
|
||||
has_set_netplay_delay_frames = true;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
|
||||
has_set_netplay_stateless_mode = true;
|
||||
break;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
|
||||
has_set_netplay_check_frames = true;
|
||||
@ -1485,8 +1479,8 @@ void retroarch_override_setting_unset(enum rarch_override_setting enum_idx, void
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
|
||||
has_set_netplay_ip_port = false;
|
||||
break;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
|
||||
has_set_netplay_delay_frames = false;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
|
||||
has_set_netplay_stateless_mode = false;
|
||||
break;
|
||||
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
|
||||
has_set_netplay_check_frames = false;
|
||||
|
@ -129,7 +129,7 @@ enum rarch_override_setting
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_MODE,
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS,
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT,
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES,
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE,
|
||||
RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES,
|
||||
RARCH_OVERRIDE_SETTING_UPS_PREF,
|
||||
RARCH_OVERRIDE_SETTING_BPS_PREF,
|
||||
|
@ -922,6 +922,9 @@ static enum runloop_state runloop_check_state(
|
||||
#ifdef HAVE_NETWORKING
|
||||
tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_FLIP);
|
||||
netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, &tmp);
|
||||
tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_GAME_WATCH);
|
||||
if (tmp)
|
||||
netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL);
|
||||
tmp = runloop_cmd_triggered(trigger_input, RARCH_FULLSCREEN_TOGGLE_KEY);
|
||||
#endif
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user