diff --git a/command.c b/command.c index e265254cdf..321b14e883 100644 --- a/command.c +++ b/command.c @@ -1919,11 +1919,6 @@ bool command_event(enum event_command cmd, void *data) if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) return false; -#ifdef HAVE_NETPLAY - if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) - return false; -#endif - #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index bf6fd4add0..64a31c817f 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -242,7 +242,12 @@ static bool netplay_get_cmd(netplay_t *netplay) for (i = 0; i < WORDS_PER_FRAME; i++) buffer[i] = ntohl(buffer[i]); - if (buffer[0] != netplay->read_frame_count) + if (buffer[0] < netplay->read_frame_count) + { + /* We already had this, so ignore the new transmission */ + return true; + } + else if (buffer[0] > netplay->read_frame_count) { /* Out of order = out of luck */ return netplay_cmd_nak(netplay); @@ -295,8 +300,58 @@ static bool netplay_get_cmd(netplay_t *netplay) return true; case NETPLAY_CMD_LOAD_SAVESTATE: - RARCH_ERR("NETPLAY_CMD_LOAD_SAVESTATE unimplemented.\n"); - return netplay_cmd_nak(netplay); + { + uint32_t frame; + + /* There is a subtlty in whether the load comes before or after the + * current frame: + * + * If it comes before the current frame, then we need to force a + * rewind to that point. + * + * If it comes after the current frame, we need to jump ahead, then + * (strangely) force a rewind to the frame we're already on, so it + * gets loaded. This is just to avoid having reloading implemented in + * too many places. */ + if (cmd_size > netplay->state_size + sizeof(uint32_t)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, &frame, sizeof(frame))) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n"); + return netplay_cmd_nak(netplay); + } + frame = ntohl(frame); + + if (frame != netplay->read_frame_count) + { + RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, + netplay->buffer[netplay->read_ptr].state, cmd_size - sizeof(uint32_t))) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); + return netplay_cmd_nak(netplay); + } + + /* Skip ahead if it's past where we are */ + if (frame > netplay->self_frame_count) + { + netplay->self_ptr = netplay->read_ptr; + netplay->self_frame_count = frame; + } + + /* And force rewind to it */ + netplay->force_rewind = true; + netplay->other_ptr = netplay->read_ptr; + netplay->other_frame_count = frame; + return true; + } case NETPLAY_CMD_PAUSE: netplay->remote_paused = true; @@ -1018,6 +1073,61 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); } +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded + * + * 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) +{ + uint32_t header[3]; + + if (!netplay->has_connection) + return; + + /* Record it in our own buffer */ + if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + { + if (serial_info->size <= netplay->state_size) + { + memcpy(netplay->buffer[netplay->self_ptr].state, + serial_info->data_const, serial_info->size); + } + } + + /* We need to ignore any intervening data from the other side, and never rewind past this */ + if (netplay->read_frame_count < netplay->self_frame_count) + { + netplay->read_ptr = netplay->self_ptr; + netplay->read_frame_count = netplay->self_frame_count; + } + if (netplay->other_frame_count < netplay->self_frame_count) + { + netplay->other_ptr = netplay->self_ptr; + netplay->other_frame_count = netplay->self_frame_count; + } + + /* And send it to the peer (FIXME: this is an ugly way to do this) */ + header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); + header[1] = htonl(serial_info->size + sizeof(uint32_t)); + header[2] = htonl(netplay->self_frame_count); + if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false)) + { + warn_hangup(); + netplay->has_connection = false; + return; + } + + if (!socket_send_all_blocking(netplay->fd, serial_info->data_const, serial_info->size, false)) + { + warn_hangup(); + netplay->has_connection = false; + return; + } +} + void deinit_netplay(void) { netplay_t *netplay = (netplay_t*)netplay_data; @@ -1119,6 +1229,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) case RARCH_NETPLAY_CTL_UNPAUSE: netplay_frontend_paused((netplay_t*)netplay_data, false); break; + case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: + netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data); + break; default: case RARCH_NETPLAY_CTL_NONE: break; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 54a32fb789..94524ec5c6 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -37,7 +37,8 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_PRE_FRAME, RARCH_NETPLAY_CTL_IS_DATA_INITED, RARCH_NETPLAY_CTL_PAUSE, - RARCH_NETPLAY_CTL_UNPAUSE + RARCH_NETPLAY_CTL_UNPAUSE, + RARCH_NETPLAY_CTL_LOAD_SAVESTATE }; enum netplay_cmd @@ -77,6 +78,7 @@ enum netplay_cmd /* Send a savestate for the client to load */ NETPLAY_CMD_LOAD_SAVESTATE = 0x0012, + /* Sends over cheats enabled on client */ NETPLAY_CMD_CHEATS = 0x0013, @@ -181,6 +183,15 @@ void netplay_post_frame(netplay_t *handle); **/ void netplay_frontend_paused(netplay_t *netplay, bool paused); +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded + * + * 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); + /** * init_netplay: * diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 05c106f517..ec91b3f261 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -69,24 +69,28 @@ static void netplay_net_post_frame(netplay_t *netplay) if (!netplay->has_connection) return; - /* 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) + if (!netplay->force_rewind) { - const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; + /* 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) + { + const 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->other_ptr = NEXT_PTR(netplay->other_ptr); - netplay->other_frame_count++; + if (memcmp(ptr->simulated_input_state, ptr->real_input_state, + sizeof(ptr->real_input_state)) != 0 + && !ptr->used_real) + break; + netplay->other_ptr = NEXT_PTR(netplay->other_ptr); + netplay->other_frame_count++; + } } /* Now replay the real input if we've gotten ahead of it */ - if (netplay->other_frame_count < netplay->read_frame_count && - netplay->other_frame_count < netplay->self_frame_count) + 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; @@ -134,6 +138,7 @@ static void netplay_net_post_frame(netplay_t *netplay) 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 diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 56cd84c29a..f90be04353 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -103,10 +103,13 @@ struct netplay /* Are we replaying old frames? */ bool is_replay; + /* We don't want to poll several times on a frame. */ bool can_poll; - /* If we end up having to drop remote frame data because it's ahead of us, fast-forward is URGENT */ - bool must_fast_forward; + + /* Force a rewind to other_frame_count/other_ptr. This is for synchronized + * events, such as player flipping or savestate loading. */ + bool force_rewind; /* A buffer for outgoing input packets. */ uint32_t packet_buffer[2 + WORDS_PER_FRAME];