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