From aaf6096c57eecc63ff6dd08360ad38858bcb316d Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Wed, 21 Jun 2023 02:35:18 +0900 Subject: [PATCH 01/11] Add netpacket interface Adds a new libretro interface for a core to send and receive custom network packets for implementing a communication based multiplayer system instead of using the default state serialization based multiplayer. Connection management is still done by the frontend while a core gains the ability to easily support tunneling of multi-console data communication traffic. --- libretro-common/include/libretro.h | 105 ++++++++++++++ network/netplay/netplay.h | 1 + network/netplay/netplay_defines.h | 5 +- network/netplay/netplay_frontend.c | 219 ++++++++++++++++++++++++++--- network/netplay/netplay_private.h | 3 + retroarch.c | 4 + runloop.c | 30 +++- state_manager.c | 5 +- tasks/task_netplay_find_content.c | 8 +- 9 files changed, 357 insertions(+), 23 deletions(-) diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index a08b69c08350..8021f749be1f 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1809,6 +1809,28 @@ enum retro_mod * even before the microphone driver is ready. */ +#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76 + /* const struct retro_netpacket_callback * -- + * When set, a core gets control over network packets sent and + * received during a multiplayer session. This can be used to emulate + * multiplayer games that were originally played on 2 or more separate + * consoles or computers connected together. + * + * The frontend will take care of connecting players together. + * The core only needs to send the actual data as needed for the + * emulation while handshake and connection management happens in + * the background. + * + * When 2 or more players are connected and this interface has been + * set, time manipulation features (pausing, slow motion, fast forward, + * rewinding, save state loading, etc.) are disabled to not interrupt + * communication. + * + * When not set, a frontend may use state serialization based + * multiplayer where a deterministic core supporting multiple + * input devices does not need to do anything on its own. + */ + /* VFS functionality */ /* File paths: @@ -3030,6 +3052,89 @@ struct retro_disk_control_ext_callback retro_get_image_label_t get_image_label; /* Optional - may be NULL */ }; +/* Callbacks for RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE. + * A core can set it if sending and receiving custom network packets + * during a multiplayer session is desired. + */ + +/* Used by the core to send a packet to one or more connected players. + * A single packet sent via this interface can contain up to 64kb of data. + * + * If the ready callback has indicated the local player to be the host: + * - The broadcast flag can be set to true to send to multiple connected clients + * - On a broadcast, the client_id argument indicates 1 client NOT to send the packet to + * - Otherwise, the client_id argument indicates a single client to send the packet to + * If the local player is a client connected to a host: + * - The broadcast flag is ignored + * - The client_id argument must be set to 0 + * + * This function is not guaranteed to be thread-safe and must be called during + * retro_run or any of the netpacket callbacks passed with this interface. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(const void* buf, size_t len, uint16_t client_id, bool broadcast); + +/* Called by the frontend to signify that a multiplayer session has started. + * If client_id is 0 the local player is the host of the session and at this + * point no other player has connected yet. + * + * If client_id is > 0 the local player is a client connected to a host and + * at this point is already fully connected to the host. + * + * The core will have to store the retro_netpacket_send_t function pointer + * passed here and use it whenever it wants to send a packet. That send + * function pointer is valid until the frontend calls retro_netpacket_stop_t. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn); + +/* Called by the frontend when a new packet arrives which has been sent from + * a connected client or the host with retro_netpacket_send_t. + * The client_id argument indicates who has sent the packet. On the host side + * this will always be > 0 (coming from a connected client). + * On a client connected to the host it is always 0 (coming from the host). + * Packets sent with this interface arrive at this callback in a reliable + * manner, meaning in the same order they were sent and without packet loss. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id); + +/* Called by the frontend when the multiplayer session has ended. + * Once this gets called the retro_netpacket_send_t function pointer passed + * to retro_netpacket_start_t will not be valid anymore. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_stop_t)(void); + +/* Called by the frontend every frame (between calls to retro_run while + * updating the state of the multiplayer session. + * This is a good place for the core to call retro_netpacket_send_t from. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_poll_t)(void); + +/* Called by the frontend when a new player connects to the hosted session. + * This is only called on the host side, not for clients connected to the host. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_connected_t)(uint16_t client_id); + +/* Called by the frontend when a player leaves or disconnects from the hosted session. + * This is only called on the host side, not for clients connected to the host. + */ +typedef void (RETRO_CALLCONV *retro_netpacket_disconnected_t)(uint16_t client_id); + +/** + * A callback interface for giving a core the ability to send and receive custom + * network packets during a multiplayer session between two or more instances + * of a libretro frontend. + * + * @see RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE + */ +struct retro_netpacket_callback +{ + retro_netpacket_start_t start; + retro_netpacket_receive_t receive; + retro_netpacket_stop_t stop; /* Optional - may be NULL */ + retro_netpacket_poll_t poll; /* Optional - may be NULL */ + retro_netpacket_connected_t connected; /* Optional - may be NULL */ + retro_netpacket_disconnected_t disconnected; /* Optional - may be NULL */ +}; + enum retro_pixel_format { /* 0RGB1555, native endian. diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 0bd1eb6edbec..48e146fe778c 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -147,6 +147,7 @@ typedef struct struct netplay_room host_room; struct netplay_room *room_list; struct netplay_rooms *rooms_data; + struct retro_netpacket_callback *core_netpacket_interface; /* Used while Netplay is running */ netplay_t *data; netplay_client_info_t *client_info; diff --git a/network/netplay/netplay_defines.h b/network/netplay/netplay_defines.h index a52021480c0c..6c8c78340b82 100644 --- a/network/netplay/netplay_defines.h +++ b/network/netplay/netplay_defines.h @@ -74,7 +74,10 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_DESYNC_PUSH, RARCH_NETPLAY_CTL_DESYNC_POP, RARCH_NETPLAY_CTL_KICK_CLIENT, - RARCH_NETPLAY_CTL_BAN_CLIENT + RARCH_NETPLAY_CTL_BAN_CLIENT, + RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, + RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS, + RARCH_NETPLAY_CTL_ALLOW_TIMESKIP }; /* The current status of a connection */ diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index b45b64b9a652..daf8209663dd 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -675,6 +675,9 @@ static uint32_t simple_rand_uint32(unsigned long *simple_rand_next) return ((part0 << 30) + (part1 << 15) + part2); } +static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, + uint16_t client_id, bool broadcast); + /* * netplay_init_socket_buffer * @@ -1876,6 +1879,12 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, netplay->next_ping = cpu_features_get_time_usec() + NETPLAY_PING_AFTER; + /* Tell a core that uses the netpacket interface that the client is ready */ + if (networking_driver_st.core_netpacket_interface && + networking_driver_st.core_netpacket_interface->start) + networking_driver_st.core_netpacket_interface->start + ((uint16_t)netplay->self_client_num, netplay_netpacket_send); + /* Ask to switch to playing mode if we should */ if (!settings->bools.netplay_start_as_spectator) return netplay_cmd_mode(netplay, NETPLAY_CONNECTION_PLAYING); @@ -3541,7 +3550,8 @@ static bool netplay_sync_pre_frame(netplay_t *netplay) bool ret = true; if (netplay->run_frame_count > 0 && netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->run_ptr], netplay->run_frame_count)) + &netplay->buffer[netplay->run_ptr], netplay->run_frame_count) + && !networking_driver_st.core_netpacket_interface) { /* Don't serialize until it's safe. */ if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)) @@ -3664,6 +3674,10 @@ static void netplay_sync_post_frame(netplay_t *netplay, bool stalled) { uint32_t lo_frame_count, hi_frame_count; + /* When a core uses the netpacket interface frames are not synced */ + if (networking_driver_st.core_netpacket_interface) + return; + /* Unless we're stalling, we've just finished running a frame */ if (!stalled) { @@ -3748,8 +3762,9 @@ static void netplay_sync_post_frame(netplay_t *netplay, bool stalled) #endif /* Now replay the real input if we've gotten ahead of it */ - if (netplay->force_rewind || + if ((netplay->force_rewind || netplay->replay_frame_count < netplay->run_frame_count) + && !networking_driver_st.core_netpacket_interface) { retro_ctx_serialize_info_t serial_info; @@ -4067,6 +4082,12 @@ static void netplay_hangup(netplay_t *netplay, #endif } + if (networking_driver_st.core_netpacket_interface + && was_playing && netplay->is_server + && networking_driver_st.core_netpacket_interface->disconnected) + networking_driver_st.core_netpacket_interface->disconnected + ((uint16_t)(connection - netplay->connections + 1)); + RARCH_LOG("[Netplay] %s\n", dmsg); /* This notification is really only important to the server if the client was playing. * Let it be optional if server and the client wasn't playing. */ @@ -4180,6 +4201,10 @@ static bool send_input_frame(netplay_t *netplay, struct delta_frame *dframe, buffer[2] = htonl(dframe->frame); buffer[3] = htonl(client_num); + /* When a core uses the netpacket interface input is not shared */ + if (networking_driver_st.core_netpacket_interface) + return true; + /* Add the device data */ devices = netplay->client_devices[client_num]; for (device = 0; device < MAX_INPUT_DEVICES; device++) @@ -4749,7 +4774,10 @@ static void handle_play_spectate(netplay_t *netplay, if (settings->bools.netplay_allow_slaves) { - if (settings->bools.netplay_require_slaves) + /* Slave mode unused when core uses netpacket interface */ + if (networking_driver_st.core_netpacket_interface) + slave = false; + else if (settings->bools.netplay_require_slaves) slave = true; else slave = (mode & NETPLAY_CMD_PLAY_BIT_SLAVE) ? @@ -4783,6 +4811,11 @@ static void handle_play_spectate(netplay_t *netplay, announce_play_spectate(netplay, connection->nick, connection->mode, devices, connection->ping); + + if (networking_driver_st.core_netpacket_interface + && networking_driver_st.core_netpacket_interface->connected) + networking_driver_st.core_netpacket_interface->connected + ((uint16_t)(connection - netplay->connections + 1)); } else { @@ -5511,7 +5544,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* A change to me! */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { - if (frame != netplay->server_frame_count) + /* When a core uses the netpacket interface this is valid */ + if (frame != netplay->server_frame_count + && !networking_driver_st.core_netpacket_interface) { RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); @@ -5626,7 +5661,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Somebody else is joining or parting */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { - if (frame != netplay->server_frame_count) + /* When a core uses the netpacket interface this is valid */ + if (frame != netplay->server_frame_count + && !networking_driver_st.core_netpacket_interface) { RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); @@ -6126,6 +6163,32 @@ static bool netplay_get_cmd(netplay_t *netplay, break; } + case NETPLAY_CMD_NETPACKET: + { + if (!networking_driver_st.core_netpacket_interface) + { + RARCH_ERR("[Netplay] NETPLAY_CMD_NETPACKET while core netpacket interface is not set.\n"); + return netplay_cmd_nak(netplay, connection); + } + if (cmd_size > netplay->zbuffer_size) + { + RARCH_ERR("[Netplay] Received netpacket of unexpected size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(netplay->zbuffer, cmd_size) + return false; + + if (networking_driver_st.core_netpacket_interface->receive) + { + uint16_t client_id = (!netplay->is_server ? (uint16_t)0 : + (uint16_t)(connection - netplay->connections + 1)); + networking_driver_st.core_netpacket_interface->receive + (netplay->zbuffer, cmd_size, client_id); + } + break; + } + case NETPLAY_CMD_PLAYER_CHAT: { char nickname[NETPLAY_NICK_LEN]; @@ -6742,15 +6805,23 @@ static bool netplay_init_socket_buffers(netplay_t *netplay) static bool netplay_init_serialization(netplay_t *netplay) { - size_t i, info_size; + size_t i; if (netplay->state_size) return true; - info_size = core_serialize_size_special(); - if (!info_size) - return false; - netplay->state_size = info_size; + if (networking_driver_st.core_netpacket_interface) + { + /* max core netpacket size is 64 kb, hold 2 of them */ + netplay->state_size = 64*1024*2; + } + else + { + size_t info_size = core_serialize_size_special(); + if (!info_size) + return false; + netplay->state_size = info_size; + } for (i = 0; i < netplay->buffer_size; i++) { @@ -7127,6 +7198,12 @@ static void netplay_frontend_paused(netplay_t *netplay, bool paused) size_t i; uint32_t paused_ct = 0; + /* When a core uses the netpacket interface netplay doesn't control pause. + * We need this because even if RARCH_NETPLAY_CTL_ALLOW_PAUSE returns false + * on some platforms the frontend may try to force netplay to pause. */ + if (networking_driver_st.core_netpacket_interface) + return; + netplay->local_paused = paused; /* Communicating this is a bit odd: If exactly one other connection is @@ -7250,6 +7327,10 @@ void netplay_load_savestate(netplay_t *netplay, { retro_ctx_serialize_info_t tmp_serial_info = {0}; + /* When a core uses the netpacket interface save states are not shared */ + if (networking_driver_st.core_netpacket_interface) + return; + if (!serial_info) save = true; @@ -7581,6 +7662,18 @@ static bool netplay_poll(netplay_t *netplay, bool block_libretro_input) { size_t i; + /* Use simplified polling loop when a core uses the netpacket interface. */ + if (networking_driver_st.core_netpacket_interface) + { + if (netplay->self_mode == NETPLAY_CONNECTION_NONE) + return true; + netplay_poll_net_input(netplay); + if (networking_driver_st.core_netpacket_interface->poll + && netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + networking_driver_st.core_netpacket_interface->poll(); + return true; + } + if (!get_self_input_state(block_libretro_input, netplay)) goto catastrophe; @@ -8442,6 +8535,10 @@ void deinit_netplay(void) /* Reinitialize preemptive frames if enabled */ preempt_init(runloop_state_get_ptr()); #endif + + if (net_st->core_netpacket_interface + && net_st->core_netpacket_interface->stop) + net_st->core_netpacket_interface->stop(); } free(net_st->client_info); @@ -8471,9 +8568,10 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) serialization_quirks = core_serialization_quirks(); - if (!core_info_current_supports_netplay() || + if ((!core_info_current_supports_netplay() || serialization_quirks & (RETRO_SERIALIZATION_QUIRK_INCOMPLETE | RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION)) + && !net_st->core_netpacket_interface) { RARCH_ERR("[Netplay] %s\n", msg_hash_to_str(MSG_NETPLAY_UNSUPPORTED)); runloop_msg_queue_push( @@ -8593,6 +8691,11 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) netplay->self_mode = NETPLAY_CONNECTION_INIT; } + /* Tell a core that uses the netpacket interface that the host is ready */ + if (netplay->is_server && net_st->core_netpacket_interface && + net_st->core_netpacket_interface->start) + net_st->core_netpacket_interface->start(0, netplay_netpacket_send); + return true; failure: @@ -8748,6 +8851,18 @@ static bool kick_client_by_id_and_name(netplay_t *netplay, return true; } +static bool netplay_have_any_active_connection(netplay_t *netplay) +{ + size_t i; + if (!netplay || !netplay->is_server) + return (netplay && netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); + for (i = 0; i < netplay->connections_size; i++) + if ( (netplay->connections[i].flags & NETPLAY_CONN_FLAG_ACTIVE) + && (netplay->connections[i].mode >= NETPLAY_CONNECTION_CONNECTED)) + return true; + return false; +} + /** * netplay_driver_ctl * @@ -8915,7 +9030,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_GAME_WATCH: - if (netplay) + if (netplay && net_st->core_netpacket_interface == NULL) netplay_toggle_play_spectate(netplay); else ret = false; @@ -8929,7 +9044,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_ALLOW_PAUSE: - ret = (!netplay || netplay->allow_pausing); + ret = (!netplay || netplay->allow_pausing) && + ((net_st->core_netpacket_interface == NULL) + || !netplay_have_any_active_connection(netplay)); break; case RARCH_NETPLAY_CTL_PAUSE: @@ -8944,13 +9061,13 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: - if (netplay) + if (netplay && !net_st->core_netpacket_interface) netplay_load_savestate(netplay, (retro_ctx_serialize_info_t*)data, true); break; case RARCH_NETPLAY_CTL_RESET: - if (netplay) + if (netplay && !net_st->core_netpacket_interface) netplay_core_reset(netplay); break; @@ -8967,7 +9084,10 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_DESYNC_PUSH: - if (netplay) + if (net_st->core_netpacket_interface + && netplay_have_any_active_connection(netplay)) + ret = false; + else if (netplay) netplay->desync++; break; @@ -9027,6 +9147,35 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) ret = false; break; + case RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE: + if (net_st->core_netpacket_interface) + { + free(net_st->core_netpacket_interface); + net_st->core_netpacket_interface = NULL; + } + + if (data) + { + /* copy passed interface to local state */ + net_st->core_netpacket_interface = (struct retro_netpacket_callback*) + malloc(sizeof(*net_st->core_netpacket_interface)); + *net_st->core_netpacket_interface = *(struct retro_netpacket_callback*)data; + /* reset savefile dir as core_netpacket_interface affects it */ + runloop_path_set_redirect(config_get_ptr(), + dir_get_ptr(RARCH_DIR_SAVEFILE), + dir_get_ptr(RARCH_DIR_CURRENT_SAVESTATE)); + } + break; + + case RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS: + ret = (net_st->core_netpacket_interface != NULL); + break; + + case RARCH_NETPLAY_CTL_ALLOW_TIMESKIP: + ret = ((net_st->core_netpacket_interface == NULL) + || !netplay_have_any_active_connection(netplay)); + break; + case RARCH_NETPLAY_CTL_NONE: default: ret = false; @@ -9122,6 +9271,44 @@ bool netplay_decode_hostname(const char *hostname, return true; } +static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, + uint16_t client_id, bool broadcast) +{ + net_driver_state_t *net_st = &networking_driver_st; + netplay_t *netplay = net_st->data; + if (!netplay) return; + + if (broadcast && netplay->is_server) + { + size_t i, skip = client_id; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (i+1 != skip && (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) + && (connection->mode == NETPLAY_CONNECTION_PLAYING) + && !netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_NETPACKET, buf, len)) + netplay_hangup(netplay, connection); + } + } + else + { + size_t i = client_id - (netplay->is_server ? 1 : 0); + if (i >= netplay->connections_size) + { + RARCH_ERR("[Netplay] Unable to send netpacket to client id %d.\n", + client_id); + return; + } + struct netplay_connection *connection = &netplay->connections[i]; + if ( (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) + && (connection->mode == NETPLAY_CONNECTION_PLAYING) + && !netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_NETPACKET, buf, len)) + netplay_hangup(netplay, connection); + } +} + /* Netplay Widgets */ #ifdef HAVE_GFX_WIDGETS diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 781d30bbabbf..05257b77bf76 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -162,6 +162,9 @@ enum netplay_cmd /* Sends over cheats enabled on client (unsupported) */ NETPLAY_CMD_CHEATS = 0x0047, + /* Send a network packet from the raw packet core interface */ + NETPLAY_CMD_NETPACKET = 0x0048, + /* Misc. commands */ /* Sends multiple config requests over, diff --git a/retroarch.c b/retroarch.c index 68deb9451092..4b63b1ffa770 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2520,6 +2520,10 @@ bool command_event(enum event_command cmd, void *data) runloop_msg_queue_push(msg_hash_to_str(MSG_CHEEVOS_LOAD_STATE_PREVENTED_BY_HARDCORE_MODE), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); return false; } +#endif +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + return false; #endif if (!command_event_main_state(cmd)) return false; diff --git a/runloop.c b/runloop.c index 4603c60cc865..62813f67532b 100644 --- a/runloop.c +++ b/runloop.c @@ -3484,6 +3484,13 @@ bool runloop_environment_cb(unsigned cmd, void *data) } break; + case RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE: +#ifdef HAVE_NETWORKING + RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE.\n"); + netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, data); +#endif + break; + default: RARCH_LOG("[Environ]: UNSUPPORTED (#%u).\n", cmd); return false; @@ -4032,6 +4039,9 @@ void runloop_event_deinit_core(void) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) runloop_st->runtime_shader_preset_path[0] = '\0'; #endif +#ifdef HAVE_NETWORKING + netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, NULL); +#endif } static bool runloop_path_init_subsystem(runloop_state_t *runloop_st) @@ -6310,6 +6320,12 @@ static enum runloop_state_enum runloop_check_state( input_st->flags |= INP_FLAG_NONBLOCKING; } +#ifdef HAVE_NETWORKING + if (check2 + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + check2 = false; +#endif + if (check2) { if (input_st->flags & INP_FLAG_NONBLOCKING) @@ -6408,6 +6424,14 @@ static enum runloop_state_enum runloop_check_state( runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION; } +#ifdef HAVE_NETWORKING + if ((runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION) + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + { + runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION; + } +#endif + if (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION) { if (settings->uints.video_black_frame_insertion) @@ -7404,6 +7428,9 @@ bool core_set_netplay_callbacks(void) { runloop_state_t *runloop_st = &runloop_state; + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS, NULL)) + return true; + /* Force normal poll type for netplay. */ runloop_st->current_core.poll_type = POLL_TYPE_NORMAL; @@ -7983,7 +8010,8 @@ void runloop_path_set_redirect(settings_t *settings, #ifdef HAVE_NETWORKING /* Special save directory for netplay clients. */ if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) - && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL)) + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL) + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_SKIP_NETPLAY_CALLBACKS, NULL)) { fill_pathname_join(new_savefile_dir, new_savefile_dir, ".netplay", sizeof(new_savefile_dir)); diff --git a/state_manager.c b/state_manager.c index 238517cb43b6..98ea01bbc50a 100644 --- a/state_manager.c +++ b/state_manager.c @@ -748,8 +748,9 @@ bool state_manager_check_rewind( { #ifdef HAVE_NETWORKING /* Make sure netplay isn't confused */ - if (!was_reversed) - netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_PUSH, NULL); + if (!was_reversed + && !netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_PUSH, NULL)) + return false; #endif rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED; diff --git a/tasks/task_netplay_find_content.c b/tasks/task_netplay_find_content.c index 0dc84b94d7b6..08cfee0f153a 100644 --- a/tasks/task_netplay_find_content.c +++ b/tasks/task_netplay_find_content.c @@ -894,9 +894,7 @@ bool task_push_netplay_content_reload(const char *hostname) strlcpy(data->hostname, hostname, sizeof(data->hostname)); flags = content_get_flags(); - if (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT) - scan_state.state |= STATE_LOAD_CONTENTLESS; - else if (flags & CONTENT_ST_FLAG_IS_INITED) + if (flags & CONTENT_ST_FLAG_IS_INITED) { const char *psubsystem = path_get(RARCH_PATH_SUBSYSTEM); @@ -929,6 +927,10 @@ bool task_push_netplay_content_reload(const char *hostname) } } + if ((flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT) && + !(scan_state.state & (STATE_LOAD|STATE_LOAD_SUBSYSTEM))) + scan_state.state |= STATE_LOAD_CONTENTLESS; + data->current.core_loaded = true; scan_state.running = true; From 81b4518435cd9a89e01f0f710fef95e9dc1be6c0 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Wed, 21 Jun 2023 04:22:15 +0900 Subject: [PATCH 02/11] C90 fix --- network/netplay/netplay_frontend.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index daf8209663dd..2ab6a61638ae 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -9293,6 +9293,7 @@ static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, } else { + struct netplay_connection *connection; size_t i = client_id - (netplay->is_server ? 1 : 0); if (i >= netplay->connections_size) { @@ -9300,7 +9301,7 @@ static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, client_id); return; } - struct netplay_connection *connection = &netplay->connections[i]; + connection = &netplay->connections[i]; if ( (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) && (connection->mode == NETPLAY_CONNECTION_PLAYING) && !netplay_send_raw_cmd(netplay, connection, From b20e3e64cad809cd845236d0b99e73f15c2ad05f Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sat, 24 Jun 2023 02:33:12 +0900 Subject: [PATCH 03/11] Clarify netpacket interface handling code Reduce access to networking_driver_st global variable --- network/netplay/netplay_frontend.c | 201 ++++++++++++++++------------- 1 file changed, 111 insertions(+), 90 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 2ab6a61638ae..489c5cfc6d24 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -1924,8 +1924,9 @@ static bool netplay_handshake(netplay_t *netplay, return false; } - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED + && netplay->modus == NETPLAY_MODUS_INPUT_FRAME_SYNC + && !netplay_send_cur_input(netplay, connection)) return false; return ret; @@ -3551,7 +3552,7 @@ static bool netplay_sync_pre_frame(netplay_t *netplay) if (netplay->run_frame_count > 0 && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->run_ptr], netplay->run_frame_count) - && !networking_driver_st.core_netpacket_interface) + && netplay->modus == NETPLAY_MODUS_INPUT_FRAME_SYNC) { /* Don't serialize until it's safe. */ if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)) @@ -3663,21 +3664,17 @@ static bool netplay_sync_pre_frame(netplay_t *netplay) } /** - * netplay_sync_post_frame + * netplay_sync_input_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. */ -static void netplay_sync_post_frame(netplay_t *netplay, bool stalled) +static void netplay_sync_input_post_frame(netplay_t *netplay, bool stalled) { uint32_t lo_frame_count, hi_frame_count; - /* When a core uses the netpacket interface frames are not synced */ - if (networking_driver_st.core_netpacket_interface) - return; - /* Unless we're stalling, we've just finished running a frame */ if (!stalled) { @@ -3764,7 +3761,7 @@ static void netplay_sync_post_frame(netplay_t *netplay, bool stalled) /* Now replay the real input if we've gotten ahead of it */ if ((netplay->force_rewind || netplay->replay_frame_count < netplay->run_frame_count) - && !networking_driver_st.core_netpacket_interface) + && netplay->modus == NETPLAY_MODUS_INPUT_FRAME_SYNC) { retro_ctx_serialize_info_t serial_info; @@ -4201,10 +4198,6 @@ static bool send_input_frame(netplay_t *netplay, struct delta_frame *dframe, buffer[2] = htonl(dframe->frame); buffer[3] = htonl(client_num); - /* When a core uses the netpacket interface input is not shared */ - if (networking_driver_st.core_netpacket_interface) - return true; - /* Add the device data */ devices = netplay->client_devices[client_num]; for (device = 0; device < MAX_INPUT_DEVICES; device++) @@ -4775,7 +4768,7 @@ static void handle_play_spectate(netplay_t *netplay, if (settings->bools.netplay_allow_slaves) { /* Slave mode unused when core uses netpacket interface */ - if (networking_driver_st.core_netpacket_interface) + if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE) slave = false; else if (settings->bools.netplay_require_slaves) slave = true; @@ -5544,9 +5537,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* A change to me! */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { - /* When a core uses the netpacket interface this is valid */ + /* When a core uses the netpacket interface this is OK */ if (frame != netplay->server_frame_count - && !networking_driver_st.core_netpacket_interface) + && netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) { RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); @@ -5575,61 +5568,64 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->read_ptr[client_num] = netplay->server_ptr; netplay->read_frame_count[client_num] = netplay->server_frame_count; - /* Fix up current frame info */ - if (frame <= netplay->self_frame_count && - netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + if (netplay->modus == NETPLAY_MODUS_INPUT_FRAME_SYNC) { - /* It wanted past frames, better send 'em! */ - START(netplay->server_ptr); - while (dframe->used && dframe->frame <= netplay->self_frame_count) + /* Fix up current frame info */ + if (frame <= netplay->self_frame_count && + netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { - for (device = 0; device < MAX_INPUT_DEVICES; device++) + /* It wanted past frames, better send 'em! */ + START(netplay->server_ptr); + while (dframe->used && dframe->frame <= netplay->self_frame_count) { - uint32_t dsize; - netplay_input_state_t istate; - if (!(devices & (1<real_input[device], client_num, dsize, - false, false); - if (!istate) - continue; - memset(istate->data, 0, dsize*sizeof(uint32_t)); + for (device = 0; device < MAX_INPUT_DEVICES; device++) + { + uint32_t dsize; + netplay_input_state_t istate; + if (!(devices & (1<real_input[device], client_num, dsize, + false, false); + if (!istate) + continue; + memset(istate->data, 0, dsize*sizeof(uint32_t)); + } + dframe->have_local = true; + dframe->have_real[client_num] = true; + send_input_frame(netplay, dframe, connection, NULL, client_num, false); + if (dframe->frame == netplay->self_frame_count) break; + NEXT(); } - dframe->have_local = true; - dframe->have_real[client_num] = true; - send_input_frame(netplay, dframe, connection, NULL, client_num, false); - if (dframe->frame == netplay->self_frame_count) break; - NEXT(); } - } - else - { - uint32_t frame_count; + else + { + uint32_t frame_count; - /* It wants future frames, make sure we don't capture or send intermediate ones */ - START(netplay->self_ptr); - frame_count = netplay->self_frame_count; + /* It wants future frames, make sure we don't capture or send intermediate ones */ + START(netplay->self_ptr); + frame_count = netplay->self_frame_count; - do - { - if (!dframe->used) + do { - /* Make sure it's ready */ - if (!netplay_delta_frame_ready(netplay, dframe, frame_count)) + if (!dframe->used) { - RARCH_ERR("[Netplay] Received mode change but delta frame isn't ready!\n"); - return netplay_cmd_nak(netplay, connection); + /* Make sure it's ready */ + if (!netplay_delta_frame_ready(netplay, dframe, frame_count)) + { + RARCH_ERR("[Netplay] Received mode change but delta frame isn't ready!\n"); + return netplay_cmd_nak(netplay, connection); + } } - } - dframe->have_local = true; + dframe->have_local = true; - /* Go on to the next delta frame */ - NEXT(); - frame_count++; - } while (frame_count < frame); + /* Go on to the next delta frame */ + NEXT(); + frame_count++; + } while (frame_count < frame); + } } /* Announce it */ @@ -5661,9 +5657,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Somebody else is joining or parting */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { - /* When a core uses the netpacket interface this is valid */ + /* When a core uses the netpacket interface this is OK */ if (frame != netplay->server_frame_count - && !networking_driver_st.core_netpacket_interface) + && netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) { RARCH_ERR("[Netplay] Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); @@ -6810,7 +6806,7 @@ static bool netplay_init_serialization(netplay_t *netplay) if (netplay->state_size) return true; - if (networking_driver_st.core_netpacket_interface) + if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE) { /* max core netpacket size is 64 kb, hold 2 of them */ netplay->state_size = 64*1024*2; @@ -7018,7 +7014,8 @@ static void netplay_free(netplay_t *netplay) static netplay_t *netplay_new(const char *server, const char *mitm, uint16_t port, const char *mitm_session, uint32_t check_frames, const struct retro_callbacks *cb, - bool nat_traversal, const char *nick, uint32_t quirks) + bool nat_traversal, const char *nick, uint32_t quirks, + enum netplay_modus modus) { netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay)); @@ -7029,6 +7026,7 @@ static netplay_t *netplay_new(const char *server, const char *mitm, netplay->check_frames = check_frames; netplay->cbs = *cb; netplay->quirks = quirks; + netplay->modus = modus; netplay->crcs_valid = true; netplay->listen_fd = -1; netplay->next_announce = -1; @@ -7201,7 +7199,7 @@ static void netplay_frontend_paused(netplay_t *netplay, bool paused) /* When a core uses the netpacket interface netplay doesn't control pause. * We need this because even if RARCH_NETPLAY_CTL_ALLOW_PAUSE returns false * on some platforms the frontend may try to force netplay to pause. */ - if (networking_driver_st.core_netpacket_interface) + if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE) return; netplay->local_paused = paused; @@ -7327,10 +7325,6 @@ void netplay_load_savestate(netplay_t *netplay, { retro_ctx_serialize_info_t tmp_serial_info = {0}; - /* When a core uses the netpacket interface save states are not shared */ - if (networking_driver_st.core_netpacket_interface) - return; - if (!serial_info) save = true; @@ -7663,12 +7657,20 @@ static bool netplay_poll(netplay_t *netplay, bool block_libretro_input) size_t i; /* Use simplified polling loop when a core uses the netpacket interface. */ - if (networking_driver_st.core_netpacket_interface) + if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE) { if (netplay->self_mode == NETPLAY_CONNECTION_NONE) return true; + + /* Read netplay input. */ netplay_poll_net_input(netplay); - if (networking_driver_st.core_netpacket_interface->poll + + /* Handle any delayed state changes */ + if (netplay->is_server) + netplay_delayed_state_change(netplay); + + if (networking_driver_st.core_netpacket_interface + && networking_driver_st.core_netpacket_interface->poll && netplay->self_mode == NETPLAY_CONNECTION_PLAYING) networking_driver_st.core_netpacket_interface->poll(); return true; @@ -8468,12 +8470,13 @@ static bool netplay_pre_frame(netplay_t *netplay) } } - if ((netplay->stall || netplay->remote_paused) && - (!netplay->is_server || netplay->connected_players > 1)) + if ((netplay->stall || netplay->remote_paused) + && (!netplay->is_server || netplay->connected_players > 1) + && netplay->modus == NETPLAY_MODUS_INPUT_FRAME_SYNC) { /* We may have received data even if we're stalled, * so run post-frame sync. */ - netplay_sync_post_frame(netplay, true); + netplay_sync_input_post_frame(netplay, true); return false; } @@ -8492,8 +8495,12 @@ static void netplay_post_frame(netplay_t *netplay) { size_t i; - netplay_update_unread_ptr(netplay); - netplay_sync_post_frame(netplay, false); + /* When a core uses the netpacket interface frames are not synced */ + if (netplay->modus == NETPLAY_MODUS_INPUT_FRAME_SYNC) + { + netplay_update_unread_ptr(netplay); + netplay_sync_input_post_frame(netplay, false); + } for (i = 0; i < netplay->connections_size; i++) { @@ -8558,6 +8565,7 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) net_driver_state_t *net_st = &networking_driver_st; struct netplay_room *host_room = &net_st->host_room; const char *mitm = NULL; + enum netplay_modus modus = NETPLAY_MODUS_CORE_PACKET_INTERFACE; if (!(net_st->flags & NET_DRIVER_ST_FLAG_NETPLAY_ENABLED)) return false; @@ -8568,10 +8576,13 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) serialization_quirks = core_serialization_quirks(); + if (net_st->core_netpacket_interface) + modus = NETPLAY_MODUS_CORE_PACKET_INTERFACE; + if ((!core_info_current_supports_netplay() || serialization_quirks & (RETRO_SERIALIZATION_QUIRK_INCOMPLETE | RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION)) - && !net_st->core_netpacket_interface) + && modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) { RARCH_ERR("[Netplay] %s\n", msg_hash_to_str(MSG_NETPLAY_UNSUPPORTED)); runloop_msg_queue_push( @@ -8642,7 +8653,7 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) : #endif settings->paths.username, - quirks); + quirks, modus); if (!netplay) goto failure; @@ -9030,7 +9041,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_GAME_WATCH: - if (netplay && net_st->core_netpacket_interface == NULL) + if (netplay && netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) netplay_toggle_play_spectate(netplay); else ret = false; @@ -9044,9 +9055,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_ALLOW_PAUSE: - ret = (!netplay || netplay->allow_pausing) && - ((net_st->core_netpacket_interface == NULL) - || !netplay_have_any_active_connection(netplay)); + ret = (!netplay || (netplay->allow_pausing && + (netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE || + !netplay_have_any_active_connection(netplay)))); break; case RARCH_NETPLAY_CTL_PAUSE: @@ -9061,13 +9072,13 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: - if (netplay && !net_st->core_netpacket_interface) + if (netplay && netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) netplay_load_savestate(netplay, (retro_ctx_serialize_info_t*)data, true); break; case RARCH_NETPLAY_CTL_RESET: - if (netplay && !net_st->core_netpacket_interface) + if (netplay && netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) netplay_core_reset(netplay); break; @@ -9084,11 +9095,13 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_DESYNC_PUSH: - if (net_st->core_netpacket_interface - && netplay_have_any_active_connection(netplay)) - ret = false; - else if (netplay) - netplay->desync++; + if (netplay) + { + if (netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE) + netplay->desync++; + else if (netplay_have_any_active_connection(netplay)) + ret = false; + } break; case RARCH_NETPLAY_CTL_DESYNC_POP: @@ -9148,6 +9161,13 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE: + if (!net_st->core_netpacket_interface && data && netplay) + { + /* it's too late to enable the interface */ + ret = false; + break; + } + if (net_st->core_netpacket_interface) { free(net_st->core_netpacket_interface); @@ -9172,7 +9192,8 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) break; case RARCH_NETPLAY_CTL_ALLOW_TIMESKIP: - ret = ((net_st->core_netpacket_interface == NULL) + ret = (!netplay + || netplay->modus != NETPLAY_MODUS_CORE_PACKET_INTERFACE || !netplay_have_any_active_connection(netplay)); break; From ed4db42568b05b4be23aefea056e93c639395508 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sat, 24 Jun 2023 02:46:50 +0900 Subject: [PATCH 04/11] Add enum netplay_modus --- network/netplay/netplay_private.h | 16 ++++++++++++++++ retroarch.c | 4 ++++ runloop.c | 10 ++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 05257b77bf76..2e7a6744a9c7 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -268,6 +268,19 @@ enum rarch_netplay_stall_reason NETPLAY_STALL_SERVER_REQUESTED }; +enum netplay_modus +{ + /* Netplay operates by having all participants send input data every + frame and run cores deterministically in sync on all connected devices. + It will rewind frames when input data from the past arrives. */ + NETPLAY_MODUS_INPUT_FRAME_SYNC = 0, + + /* Netplay operates by having the active core send and receive custom + packets once connection setup and handshake has been completed. + Time skips (pausing, fast forward, save state loading) are refused. */ + NETPLAY_MODUS_CORE_PACKET_INTERFACE = 1, +}; + /* Input state for a particular client-device pair */ typedef struct netplay_input_state { @@ -607,6 +620,9 @@ struct netplay /* Are we stalled? */ enum rarch_netplay_stall_reason stall; + /* Netplay mode of operation (cannot change at runtime) */ + enum netplay_modus modus; + /* Keyboard mapping (network and host) */ uint16_t mapping_hton[RETROK_LAST]; uint16_t mapping_ntoh[NETPLAY_KEY_LAST]; diff --git a/retroarch.c b/retroarch.c index 4b63b1ffa770..0ddaa4e34c66 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2907,6 +2907,10 @@ bool command_event(enum event_command cmd, void *data) #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) return false; +#endif +#ifdef HAVE_NETWORKING + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL)) + return false; #endif if (rewind_enable) { diff --git a/runloop.c b/runloop.c index 62813f67532b..8f2e14c2a76c 100644 --- a/runloop.c +++ b/runloop.c @@ -3487,9 +3487,15 @@ bool runloop_environment_cb(unsigned cmd, void *data) case RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE: #ifdef HAVE_NETWORKING RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE.\n"); - netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, data); -#endif + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, data)) + { + RARCH_ERR("[Environ] RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE set too late\n"); + return false; + } break; +#else + return false; +#endif default: RARCH_LOG("[Environ]: UNSUPPORTED (#%u).\n", cmd); From 7559b58ceff525582ff2a92f179b8d68fcfc1fef Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sat, 24 Jun 2023 03:27:14 +0900 Subject: [PATCH 05/11] C89 fix --- network/netplay/netplay_private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 2e7a6744a9c7..7b2c34e223e2 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -278,7 +278,7 @@ enum netplay_modus /* Netplay operates by having the active core send and receive custom packets once connection setup and handshake has been completed. Time skips (pausing, fast forward, save state loading) are refused. */ - NETPLAY_MODUS_CORE_PACKET_INTERFACE = 1, + NETPLAY_MODUS_CORE_PACKET_INTERFACE = 1 }; /* Input state for a particular client-device pair */ From c0f85f8dfc628915ddc4c99ab413572dcae0b2f4 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sun, 25 Jun 2023 02:37:44 +0900 Subject: [PATCH 06/11] Enable clients to send netpackets to other clients --- libretro-common/include/libretro.h | 34 ++++---- network/netplay/netplay_frontend.c | 136 +++++++++++++++++++++-------- network/netplay/netplay_private.h | 3 + 3 files changed, 121 insertions(+), 52 deletions(-) diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 8021f749be1f..15ad558147a7 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1826,6 +1826,8 @@ enum retro_mod * rewinding, save state loading, etc.) are disabled to not interrupt * communication. * + * Should be set in either retro_init or retro_load_game, but not both. + * * When not set, a frontend may use state serialization based * multiplayer where a deterministic core supporting multiple * input devices does not need to do anything on its own. @@ -3052,26 +3054,32 @@ struct retro_disk_control_ext_callback retro_get_image_label_t get_image_label; /* Optional - may be NULL */ }; -/* Callbacks for RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE. +/* Definitions for RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE. * A core can set it if sending and receiving custom network packets * during a multiplayer session is desired. */ +/* Netpacket flags for retro_netpacket_send_t */ +#define RETRO_NETPACKET_UNRELIABLE 0 /* Packet to be sent unreliable, depending on network quality it might not arrive. */ +#define RETRO_NETPACKET_RELIABLE (1 << 0) /* Reliable packets are guaranteed to arrive at the target in the order they were send. */ +#define RETRO_NETPACKET_UNSEQUENCED (1 << 1) /* Packet will not be sequenced with other packets and may arrive out of order. Cannot be set on reliable packets. */ + /* Used by the core to send a packet to one or more connected players. * A single packet sent via this interface can contain up to 64kb of data. * - * If the ready callback has indicated the local player to be the host: - * - The broadcast flag can be set to true to send to multiple connected clients - * - On a broadcast, the client_id argument indicates 1 client NOT to send the packet to - * - Otherwise, the client_id argument indicates a single client to send the packet to - * If the local player is a client connected to a host: - * - The broadcast flag is ignored - * - The client_id argument must be set to 0 + * The broadcast flag can be set to true to send to multiple connected clients. + * On a broadcast, the client_id argument indicates 1 client NOT to send the + * packet to (pass 0xFFFF to send to everyone). Otherwise, the client_id + * argument indicates a single client to send the packet to. + * + * A frontend must support sending of reliable packets (RETRO_NETPACKET_RELIABLE). + * Unreliable packets might not be supported by the frontend but the flags can + * still be specified, reliable transmission will be used instead. * * This function is not guaranteed to be thread-safe and must be called during * retro_run or any of the netpacket callbacks passed with this interface. */ -typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(const void* buf, size_t len, uint16_t client_id, bool broadcast); +typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(int flags, const void* buf, size_t len, uint16_t client_id, bool broadcast); /* Called by the frontend to signify that a multiplayer session has started. * If client_id is 0 the local player is the host of the session and at this @@ -3087,12 +3095,8 @@ typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(const void* buf, size_t le typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn); /* Called by the frontend when a new packet arrives which has been sent from - * a connected client or the host with retro_netpacket_send_t. - * The client_id argument indicates who has sent the packet. On the host side - * this will always be > 0 (coming from a connected client). - * On a client connected to the host it is always 0 (coming from the host). - * Packets sent with this interface arrive at this callback in a reliable - * manner, meaning in the same order they were sent and without packet loss. + * another peer with retro_netpacket_send_t. The client_id argument indicates + * who has sent the packet. */ typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id); diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 489c5cfc6d24..333141f40f6c 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -675,8 +675,10 @@ static uint32_t simple_rand_uint32(unsigned long *simple_rand_next) return ((part0 << 30) + (part1 << 15) + part2); } -static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, - uint16_t client_id, bool broadcast); +static void netplay_send_cmd_netpacket(netplay_t *netplay, size_t conn_i, + const void* buf, size_t len, uint16_t client_id, bool broadcast); +static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags, + const void* buf, size_t len, uint16_t client_id, bool broadcast); /* * netplay_init_socket_buffer @@ -1883,7 +1885,7 @@ static bool netplay_handshake_pre_sync(netplay_t *netplay, if (networking_driver_st.core_netpacket_interface && networking_driver_st.core_netpacket_interface->start) networking_driver_st.core_netpacket_interface->start - ((uint16_t)netplay->self_client_num, netplay_netpacket_send); + ((uint16_t)netplay->self_client_num, netplay_netpacket_send_cb); /* Ask to switch to playing mode if we should */ if (!settings->bools.netplay_start_as_spectator) @@ -6160,7 +6162,10 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_NETPACKET: + case NETPLAY_CMD_NETPACKET_BROADCAST: { + uint32_t pkt_client_id; + void* buf = netplay->zbuffer; if (!networking_driver_st.core_netpacket_interface) { RARCH_ERR("[Netplay] NETPLAY_CMD_NETPACKET while core netpacket interface is not set.\n"); @@ -6172,15 +6177,46 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } - RECV(netplay->zbuffer, cmd_size) + RECV(&pkt_client_id, sizeof(pkt_client_id)) + return false; + pkt_client_id = ntohl(pkt_client_id); + RECV(buf, cmd_size) return false; - if (networking_driver_st.core_netpacket_interface->receive) + if (!netplay->is_server) { - uint16_t client_id = (!netplay->is_server ? (uint16_t)0 : - (uint16_t)(connection - netplay->connections + 1)); - networking_driver_st.core_netpacket_interface->receive - (netplay->zbuffer, cmd_size, client_id); + /* packets arriving at a client are always meant for us */ + if (networking_driver_st.core_netpacket_interface->receive) + networking_driver_st.core_netpacket_interface->receive + (buf, cmd_size, (uint16_t)pkt_client_id); + } + else + { + bool broadcast = (cmd == NETPLAY_CMD_NETPACKET_BROADCAST); + uint16_t incoming_client_id = + (uint16_t)(connection - netplay->connections + 1); + + /* check if this is a packet for the host */ + if ((broadcast ? pkt_client_id : !pkt_client_id) + && networking_driver_st.core_netpacket_interface->receive) + networking_driver_st.core_netpacket_interface->receive + (buf, cmd_size, incoming_client_id); + + if (broadcast) + { + /* relay to all but designated client and incoming client */ + size_t i, skip1 = pkt_client_id, skip2 = incoming_client_id; + for (i = 0; i < netplay->connections_size; i++) + if (i+1 != skip1 && i+1 != skip2) + netplay_send_cmd_netpacket(netplay, i, + buf, cmd_size, incoming_client_id, false); + } + else if (pkt_client_id && pkt_client_id != incoming_client_id) + { + /* relay unless target is the host or the incoming client */ + netplay_send_cmd_netpacket(netplay, pkt_client_id-1, + buf, cmd_size, incoming_client_id, false); + } } break; } @@ -8705,7 +8741,7 @@ bool init_netplay(const char *server, unsigned port, const char *mitm_session) /* Tell a core that uses the netpacket interface that the host is ready */ if (netplay->is_server && net_st->core_netpacket_interface && net_st->core_netpacket_interface->start) - net_st->core_netpacket_interface->start(0, netplay_netpacket_send); + net_st->core_netpacket_interface->start(0, netplay_netpacket_send_cb); return true; @@ -9292,42 +9328,68 @@ bool netplay_decode_hostname(const char *hostname, return true; } -static void RETRO_CALLCONV netplay_netpacket_send(const void* buf, size_t len, - uint16_t client_id, bool broadcast) +/** + * netplay_send_cmd_netpacket + * @netplay : pointer to netplay object + * @conn_idx : connection index to send cmd to + * @buf : packet data pointer + * @len : packet data size + * @pkt_client_id : source id if host sending to client, otherwise recipient id or excepted id if broadcast + * @broadcast : pass as true from client if host should relay this to everyone else + * + * Send a netpacket command to a connected peer. + */ +static void netplay_send_cmd_netpacket(netplay_t *netplay, size_t conn_idx, + const void* buf, size_t len, uint16_t pkt_client_id, bool broadcast) +{ + struct netplay_connection *connection; + struct socket_buffer *sbuf; + uint32_t cmdbuf[3]; + bool need_flush; + + if (conn_idx >= netplay->connections_size) return; + connection = &netplay->connections[conn_idx]; + if (!(connection->flags & NETPLAY_CONN_FLAG_ACTIVE)) return; + if (connection->mode != NETPLAY_CONNECTION_PLAYING) return; + + cmdbuf[0] = htonl(broadcast + ? NETPLAY_CMD_NETPACKET_BROADCAST : NETPLAY_CMD_NETPACKET); + cmdbuf[1] = htonl(len); + cmdbuf[2] = htonl(pkt_client_id); + + sbuf = &connection->send_packet_buffer; + need_flush = (buf_remaining(sbuf) < sizeof(cmdbuf)+len); + + if ( (need_flush && !netplay_send_flush(sbuf, connection->fd, true)) + || (!netplay_send(sbuf, connection->fd, cmdbuf, sizeof(cmdbuf))) + || (len && !netplay_send(sbuf, connection->fd, buf, len))) + netplay_hangup(netplay, connection); +} + +static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags, + const void* buf, size_t len, uint16_t client_id, bool broadcast) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay) return; - if (broadcast && netplay->is_server) + if (!netplay->is_server) { - size_t i, skip = client_id; + /* client always sends packet to host, host will relay it if needed */ + netplay_send_cmd_netpacket(netplay, 0, buf, len, client_id, broadcast); + } + else if (broadcast) + { + /* send packet to all clients (client_id can be set as exception) */ + size_t i; for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (i+1 != skip && (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) - && (connection->mode == NETPLAY_CONNECTION_PLAYING) - && !netplay_send_raw_cmd(netplay, connection, - NETPLAY_CMD_NETPACKET, buf, len)) - netplay_hangup(netplay, connection); - } + if (i+1 != client_id) + netplay_send_cmd_netpacket(netplay, i, buf, len, 0, false); } - else + else if (client_id) { - struct netplay_connection *connection; - size_t i = client_id - (netplay->is_server ? 1 : 0); - if (i >= netplay->connections_size) - { - RARCH_ERR("[Netplay] Unable to send netpacket to client id %d.\n", - client_id); - return; - } - connection = &netplay->connections[i]; - if ( (connection->flags & NETPLAY_CONN_FLAG_ACTIVE) - && (connection->mode == NETPLAY_CONNECTION_PLAYING) - && !netplay_send_raw_cmd(netplay, connection, - NETPLAY_CMD_NETPACKET, buf, len)) - netplay_hangup(netplay, connection); + /* send packet to specific client */ + netplay_send_cmd_netpacket(netplay, client_id-1, buf, len, 0, false); } } diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 7b2c34e223e2..b8567479f849 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -165,6 +165,9 @@ enum netplay_cmd /* Send a network packet from the raw packet core interface */ NETPLAY_CMD_NETPACKET = 0x0048, + /* Used by clients to have the host also forward it to other clients */ + NETPLAY_CMD_NETPACKET_BROADCAST = 0x0049, + /* Misc. commands */ /* Sends multiple config requests over, From a4440cec3cf78297ff4dcbc08163ca3029262a38 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sun, 25 Jun 2023 03:14:44 +0900 Subject: [PATCH 07/11] Change tab to spaces --- network/netplay/netplay_private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index b8567479f849..37bd925f5323 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -281,7 +281,7 @@ enum netplay_modus /* Netplay operates by having the active core send and receive custom packets once connection setup and handshake has been completed. Time skips (pausing, fast forward, save state loading) are refused. */ - NETPLAY_MODUS_CORE_PACKET_INTERFACE = 1 + NETPLAY_MODUS_CORE_PACKET_INTERFACE = 1 }; /* Input state for a particular client-device pair */ From 356f31b67dc278a13ef0f67e06be46be91a2948a Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:13:41 +0900 Subject: [PATCH 08/11] Enhance netpacket interface - Enable core host to refuse connecting new players to limit the number of connected players - Enable a core to flush outgoing packets and read incoming packets without waiting for the next frame (can be used for lower latency or blocking reads) --- libretro-common/include/libretro.h | 59 ++++++++++++--------- network/netplay/netplay_frontend.c | 83 ++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 51 deletions(-) diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 15ad558147a7..8d6a1664a49b 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1811,26 +1811,26 @@ enum retro_mod #define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 76 /* const struct retro_netpacket_callback * -- - * When set, a core gets control over network packets sent and - * received during a multiplayer session. This can be used to emulate - * multiplayer games that were originally played on 2 or more separate - * consoles or computers connected together. + * When set, a core gains control over network packets sent and + * received during a multiplayer session. This can be used to + * emulate multiplayer games that were originally played on two + * or more separate consoles or computers connected together. * - * The frontend will take care of connecting players together. - * The core only needs to send the actual data as needed for the - * emulation while handshake and connection management happens in - * the background. + * The frontend will take care of connecting players together, + * and the core only needs to send the actual data as needed for + * the emulation, while handshake and connection management happen + * in the background. * - * When 2 or more players are connected and this interface has been - * set, time manipulation features (pausing, slow motion, fast forward, - * rewinding, save state loading, etc.) are disabled to not interrupt - * communication. + * When two or more players are connected and this interface has + * been set, time manipulation features (such as pausing, slow motion, + * fast forward, rewinding, save state loading, etc.) are disabled to + * avoid interrupting communication. * * Should be set in either retro_init or retro_load_game, but not both. * - * When not set, a frontend may use state serialization based - * multiplayer where a deterministic core supporting multiple - * input devices does not need to do anything on its own. + * When not set, a frontend may use state serialization-based + * multiplayer, where a deterministic core supporting multiple + * input devices does not need to take any action on its own. */ /* VFS functionality */ @@ -3065,16 +3065,23 @@ struct retro_disk_control_ext_callback #define RETRO_NETPACKET_UNSEQUENCED (1 << 1) /* Packet will not be sequenced with other packets and may arrive out of order. Cannot be set on reliable packets. */ /* Used by the core to send a packet to one or more connected players. - * A single packet sent via this interface can contain up to 64kb of data. + * A single packet sent via this interface can contain up to 64 KB of data. * * The broadcast flag can be set to true to send to multiple connected clients. - * On a broadcast, the client_id argument indicates 1 client NOT to send the + * In a broadcast, the client_id argument indicates 1 client NOT to send the * packet to (pass 0xFFFF to send to everyone). Otherwise, the client_id * argument indicates a single client to send the packet to. * - * A frontend must support sending of reliable packets (RETRO_NETPACKET_RELIABLE). - * Unreliable packets might not be supported by the frontend but the flags can - * still be specified, reliable transmission will be used instead. + * A frontend must support sending reliable packets (RETRO_NETPACKET_RELIABLE). + * Unreliable packets might not be supported by the frontend, but the flags can + * still be specified. Reliable transmission will be used instead. + * + * If this function is called passing NULL for buf, it will instead flush all + * previously buffered outgoing packets and instantly read any incoming packets. + * During such a call, retro_netpacket_receive_t and retro_netpacket_stop_t can + * be called. The core can perform this in a loop to do a blocking read, i.e., + * wait for incoming data, but needs to handle stop getting called and also + * give up after a short while to avoid freezing on a connection problem. * * This function is not guaranteed to be thread-safe and must be called during * retro_run or any of the netpacket callbacks passed with this interface. @@ -3088,14 +3095,14 @@ typedef void (RETRO_CALLCONV *retro_netpacket_send_t)(int flags, const void* buf * If client_id is > 0 the local player is a client connected to a host and * at this point is already fully connected to the host. * - * The core will have to store the retro_netpacket_send_t function pointer - * passed here and use it whenever it wants to send a packet. That send - * function pointer is valid until the frontend calls retro_netpacket_stop_t. + * The core must store the retro_netpacket_send_t function pointer provided + * here and use it whenever it wants to send a packet. This function pointer + * remains valid until the frontend calls retro_netpacket_stop_t. */ typedef void (RETRO_CALLCONV *retro_netpacket_start_t)(uint16_t client_id, retro_netpacket_send_t send_fn); /* Called by the frontend when a new packet arrives which has been sent from - * another peer with retro_netpacket_send_t. The client_id argument indicates + * another player with retro_netpacket_send_t. The client_id argument indicates * who has sent the packet. */ typedef void (RETRO_CALLCONV *retro_netpacket_receive_t)(const void* buf, size_t len, uint16_t client_id); @@ -3114,8 +3121,10 @@ typedef void (RETRO_CALLCONV *retro_netpacket_poll_t)(void); /* Called by the frontend when a new player connects to the hosted session. * This is only called on the host side, not for clients connected to the host. + * If this function returns false, the newly connected player gets dropped. + * This can be used for example to limit the number of players. */ -typedef void (RETRO_CALLCONV *retro_netpacket_connected_t)(uint16_t client_id); +typedef bool (RETRO_CALLCONV *retro_netpacket_connected_t)(uint16_t client_id); /* Called by the frontend when a player leaves or disconnects from the hosted session. * This is only called on the host side, not for clients connected to the host. diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 333141f40f6c..2ebb47aef504 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -4451,7 +4451,7 @@ static uint8_t netplay_settings_share_mode( static void announce_play_spectate(netplay_t *netplay, const char *nick, enum rarch_netplay_connection_mode mode, uint32_t devices, - int32_t ping) + int32_t ping, uint32_t client_num) { char msg[512]; const char *dmsg = NULL; @@ -4478,16 +4478,23 @@ static void announce_play_spectate(netplay_t *netplay, uint32_t one_device = (uint32_t) -1; char *pdevice_str = NULL; - for (device = 0; device < MAX_INPUT_DEVICES; device++) + if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE) { - if (!(devices & (1<nick, - NETPLAY_CONNECTION_SPECTATING, 0, -1); + NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num); } else { @@ -4609,7 +4616,7 @@ static void handle_play_spectate(netplay_t *netplay, netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; announce_play_spectate(netplay, NULL, - NETPLAY_CONNECTION_SPECTATING, 0, -1); + NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num); /* It was the server, so tell everyone else */ netplay_send_raw_cmd_all(netplay, NULL, @@ -4643,7 +4650,12 @@ static void handle_play_spectate(netplay_t *netplay, share_mode &= ~NETPLAY_SHARE_NO_PREFERENCE; } - if (devices) + if (netplay->modus == NETPLAY_MODUS_CORE_PACKET_INTERFACE) + { + /* no device needs to be assigned with netpacket interface */ + devices = 0; + } + else if (devices) { /* Make sure the devices are available and/or shareable */ for (i = 0; i < MAX_INPUT_DEVICES; i++) @@ -4800,17 +4812,27 @@ static void handle_play_spectate(netplay_t *netplay, payload.mode = htonl(mode | NETPLAY_CMD_MODE_BIT_YOU); + if (networking_driver_st.core_netpacket_interface + && networking_driver_st.core_netpacket_interface->connected + && !networking_driver_st.core_netpacket_interface->connected( + (uint16_t)(connection - netplay->connections + 1))) + { + /* core wants us to drop this new client */ + connection->mode = NETPLAY_CONNECTION_CONNECTED; + uint32_t reason = htonl( + NETPLAY_CMD_MODE_REFUSED_REASON_OTHER); + netplay_send_raw_cmd(netplay, connection, + NETPLAY_CMD_MODE_REFUSED, &reason, sizeof(reason)); + netplay_hangup(netplay, connection); + return; + } + /* Tell the player */ netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, &payload, sizeof(payload)); announce_play_spectate(netplay, connection->nick, - connection->mode, devices, connection->ping); - - if (networking_driver_st.core_netpacket_interface - && networking_driver_st.core_netpacket_interface->connected) - networking_driver_st.core_netpacket_interface->connected - ((uint16_t)(connection - netplay->connections + 1)); + connection->mode, devices, connection->ping, client_num); } else { @@ -4828,7 +4850,7 @@ static void handle_play_spectate(netplay_t *netplay, netplay->self_mode = NETPLAY_CONNECTION_PLAYING; announce_play_spectate(netplay, NULL, - netplay->self_mode, devices, -1); + netplay->self_mode, devices, -1, client_num); } payload.mode = htonl(mode); @@ -5632,7 +5654,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Announce it */ announce_play_spectate(netplay, NULL, netplay->self_mode, devices, - connection->ping); + connection->ping, client_num); #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[Netplay] Received mode change self->%X\n", devices); @@ -5677,7 +5699,7 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->read_frame_count[client_num] = netplay->server_frame_count; /* Announce it */ - announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices, -1); + announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices, -1, client_num); #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[Netplay] Received mode change %u->%u\n", client_num, devices); @@ -5692,7 +5714,7 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->device_clients[device] &= ~(1<spectator\n", client_num); @@ -5970,7 +5992,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_RESET: - { + {DBP_ASSERT_MODUS(NETPLAY_MODUS_INPUT_FRAME_SYNC) uint32_t i; uint32_t frame; size_t reset_ptr; @@ -7418,7 +7440,8 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) if (!netplay->is_server) { int i; - uint32_t client_mask = ~(1 << netplay->self_client_num); + uint32_t client_num = netplay->self_client_num; + uint32_t client_mask = ~(1 << client_num); netplay->connected_players &= client_mask; @@ -7430,7 +7453,7 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; announce_play_spectate(netplay, NULL, - NETPLAY_CONNECTION_SPECTATING, 0, -1); + NETPLAY_CONNECTION_SPECTATING, 0, -1, client_num); } netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING); @@ -9335,7 +9358,7 @@ bool netplay_decode_hostname(const char *hostname, * @buf : packet data pointer * @len : packet data size * @pkt_client_id : source id if host sending to client, otherwise recipient id or excepted id if broadcast - * @broadcast : pass as true from client if host should relay this to everyone else + * @broadcast : pass true on client if host should relay this to everyone else * * Send a netpacket command to a connected peer. */ @@ -9373,6 +9396,14 @@ static void RETRO_CALLCONV netplay_netpacket_send_cb(int flags, netplay_t *netplay = net_st->data; if (!netplay) return; + if (buf == NULL) + { + /* With NULL this function instead flushes packets and checks incoming */ + netplay_send_flush_all(netplay, NULL); + input_poll_net(netplay); + return; + } + if (!netplay->is_server) { /* client always sends packet to host, host will relay it if needed */ From 84f72c80653ed1bbeb3656af235b07d4e94ddcfa Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:39:40 +0900 Subject: [PATCH 09/11] Remove invalid test assert macro --- network/netplay/netplay_frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 2ebb47aef504..d49252665595 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -5992,7 +5992,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_RESET: - {DBP_ASSERT_MODUS(NETPLAY_MODUS_INPUT_FRAME_SYNC) + { uint32_t i; uint32_t frame; size_t reset_ptr; From d032cb4da05fd5f69dfaff2493e48c0f00947047 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:46:10 +0900 Subject: [PATCH 10/11] C90 fix --- network/netplay/netplay_frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index d49252665595..7297d9a1938f 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -4818,9 +4818,9 @@ static void handle_play_spectate(netplay_t *netplay, (uint16_t)(connection - netplay->connections + 1))) { /* core wants us to drop this new client */ - connection->mode = NETPLAY_CONNECTION_CONNECTED; uint32_t reason = htonl( NETPLAY_CMD_MODE_REFUSED_REASON_OTHER); + connection->mode = NETPLAY_CONNECTION_CONNECTED; netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, &reason, sizeof(reason)); netplay_hangup(netplay, connection); From ba28da397d78788e1d9335c83ae23c454de06435 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:41:59 +0900 Subject: [PATCH 11/11] Do nothing if the same interface gets set twice --- network/netplay/netplay_frontend.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 7297d9a1938f..1b1dda3c28f1 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -9229,6 +9229,10 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) if (net_st->core_netpacket_interface) { + if (data && !memcmp(net_st->core_netpacket_interface, + data, sizeof(*net_st->core_netpacket_interface))) + break; /* interface is not modified */ + free(net_st->core_netpacket_interface); net_st->core_netpacket_interface = NULL; }