diff --git a/command.c b/command.c index 86a42a3c06..9dc14b11cd 100644 --- a/command.c +++ b/command.c @@ -1918,6 +1918,9 @@ bool command_event(enum event_command cmd, void *data) core_reset(); #ifdef HAVE_CHEEVOS cheevos_reset_game(); +#endif +#if HAVE_NETWORKING + netplay_driver_ctl(RARCH_NETPLAY_CTL_RESET, NULL); #endif break; case CMD_EVENT_SAVE_STATE: diff --git a/network/netplay/README b/network/netplay/README index 168dd8b4e7..460dbc91cc 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -316,6 +316,14 @@ Payload: Description: Request that a client stall for the given number of frames. +Command: RESET +Payload: + { + frame: uint32 + } +Description: + Indicate that the core was reset at the beginning of the given frame. + Command: CHEATS Unused diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index e6fd5f6488..d873e62e99 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -44,6 +44,7 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_PAUSE, RARCH_NETPLAY_CTL_UNPAUSE, RARCH_NETPLAY_CTL_LOAD_SAVESTATE, + RARCH_NETPLAY_CTL_RESET, RARCH_NETPLAY_CTL_DISCONNECT }; diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index f999053822..0afbbcb353 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -740,6 +740,48 @@ void netplay_post_frame(netplay_t *netplay) } } +/** + * netplay_force_future + * @netplay : pointer to netplay object + * + * Force netplay to ignore all past input, typically because we've just loaded + * a state or reset. + */ +static void netplay_force_future(netplay_t *netplay) +{ + /* Wherever we're inputting, that's where we consider our state to be loaded */ + netplay->run_ptr = netplay->self_ptr; + netplay->run_frame_count = netplay->self_frame_count; + + /* We need to ignore any intervening data from the other side, + * and never rewind past this */ + netplay_update_unread_ptr(netplay); + if (netplay->unread_frame_count < netplay->run_frame_count) + { + uint32_t player; + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1<read_frame_count[player] < netplay->run_frame_count) + { + netplay->read_ptr[player] = netplay->run_ptr; + netplay->read_frame_count[player] = netplay->run_frame_count; + } + } + if (netplay->server_frame_count < netplay->run_frame_count) + { + netplay->server_ptr = netplay->run_ptr; + netplay->server_frame_count = netplay->run_frame_count; + } + netplay_update_unread_ptr(netplay); + } + if (netplay->other_frame_count < netplay->run_frame_count) + { + netplay->other_ptr = netplay->run_ptr; + netplay->other_frame_count = netplay->run_frame_count; + } +} + /** * netplay_send_savestate * @netplay : pointer to netplay object @@ -808,10 +850,7 @@ void netplay_load_savestate(netplay_t *netplay, { retro_ctx_serialize_info_t tmp_serial_info; - /* Wherever we're inputting, that's where we consider our state to be loaded - * (FIXME: Need to be more careful about saving it?) */ - netplay->run_ptr = netplay->self_ptr; - netplay->run_frame_count = netplay->self_frame_count; + netplay_force_future(netplay); /* Record it in our own buffer */ if (save || !serial_info) @@ -844,34 +883,6 @@ void netplay_load_savestate(netplay_t *netplay, } } - /* We need to ignore any intervening data from the other side, - * and never rewind past this */ - netplay_update_unread_ptr(netplay); - if (netplay->unread_frame_count < netplay->run_frame_count) - { - uint32_t player; - for (player = 0; player < MAX_USERS; player++) - { - if (!(netplay->connected_players & (1<read_frame_count[player] < netplay->run_frame_count) - { - netplay->read_ptr[player] = netplay->run_ptr; - netplay->read_frame_count[player] = netplay->run_frame_count; - } - } - if (netplay->server_frame_count < netplay->run_frame_count) - { - netplay->server_ptr = netplay->run_ptr; - netplay->server_frame_count = netplay->run_frame_count; - } - netplay_update_unread_ptr(netplay); - } - if (netplay->other_frame_count < netplay->run_frame_count) - { - netplay->other_ptr = netplay->run_ptr; - netplay->other_frame_count = netplay->run_frame_count; - } - /* If we can't send it to the peer, loading a state was a bad idea */ if (netplay->quirks & ( NETPLAY_QUIRK_NO_SAVESTATES @@ -886,6 +897,37 @@ void netplay_load_savestate(netplay_t *netplay, &netplay->compress_zlib); } +/** + * netplay_core_reset + * @netplay : pointer to netplay object + * + * Indicate that the core has been reset to netplay peers + **/ +static void netplay_core_reset(netplay_t *netplay) +{ + uint32_t cmd[3]; + size_t i; + + /* Ignore past input */ + netplay_force_future(netplay); + + /* Request that our peers reset */ + cmd[0] = htonl(NETPLAY_CMD_RESET); + cmd[1] = htonl(sizeof(uint32_t)); + cmd[2] = htonl(netplay->self_frame_count); + + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (!connection->active || + connection->mode < NETPLAY_CONNECTION_CONNECTED) continue; + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, + sizeof(cmd))) + netplay_hangup(netplay, connection); + } +} + /** * netplay_toggle_play_spectate * @@ -1145,6 +1187,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true); break; + case RARCH_NETPLAY_CTL_RESET: + netplay_core_reset(netplay_data); + break; case RARCH_NETPLAY_CTL_DISCONNECT: ret = netplay_disconnect(netplay_data); goto done; diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index c8c106a491..4de8e02375 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -1022,6 +1022,7 @@ static bool netplay_get_cmd(netplay_t *netplay, break; case NETPLAY_CMD_LOAD_SAVESTATE: + case NETPLAY_CMD_RESET: { uint32_t frame; uint32_t isize; @@ -1071,7 +1072,11 @@ static bool netplay_get_cmd(netplay_t *netplay, * (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->zbuffer_size + 2*sizeof(uint32_t)) + + /* Check the payload size */ + if ((cmd == NETPLAY_CMD_LOAD_SAVESTATE && + (cmd_size < 2*sizeof(uint32_t) || cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t))) || + (cmd == NETPLAY_CMD_RESET && cmd_size != sizeof(uint32_t))) { RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); @@ -1097,44 +1102,58 @@ static bool netplay_get_cmd(netplay_t *netplay, goto shrt; } - RECV(&isize, sizeof(isize)) + /* Now we switch based on whether we're loading a state or resetting */ + if (cmd == NETPLAY_CMD_LOAD_SAVESTATE) { - RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); - return netplay_cmd_nak(netplay, connection); - } - isize = ntohl(isize); + RECV(&isize, sizeof(isize)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); + return netplay_cmd_nak(netplay, connection); + } + isize = ntohl(isize); - if (isize != netplay->state_size) - { - RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); - return netplay_cmd_nak(netplay, connection); - } + if (isize != netplay->state_size) + { + RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); + return netplay_cmd_nak(netplay, connection); + } - RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)) - { - RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); - return netplay_cmd_nak(netplay, connection); - } + RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); + return netplay_cmd_nak(netplay, connection); + } + + /* And decompress it */ + switch (connection->compression_supported) + { + case NETPLAY_COMPRESSION_ZLIB: + ctrans = &netplay->compress_zlib; + break; + default: + ctrans = &netplay->compress_nil; + } + ctrans->decompression_backend->set_in(ctrans->decompression_stream, + netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); + ctrans->decompression_backend->set_out(ctrans->decompression_stream, + (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state, + netplay->state_size); + ctrans->decompression_backend->trans(ctrans->decompression_stream, + true, &rd, &wn, NULL); + + /* Force a rewind to the relevant frame */ + netplay->force_rewind = true; + } + else + { + /* Resetting */ + netplay->force_reset = true; - /* And decompress it */ - switch (connection->compression_supported) - { - case NETPLAY_COMPRESSION_ZLIB: - ctrans = &netplay->compress_zlib; - break; - default: - ctrans = &netplay->compress_nil; } - ctrans->decompression_backend->set_in(ctrans->decompression_stream, - netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); - ctrans->decompression_backend->set_out(ctrans->decompression_stream, - (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state, - netplay->state_size); - ctrans->decompression_backend->trans(ctrans->decompression_stream, - true, &rd, &wn, NULL); /* Skip ahead if it's past where we are */ - if (frame > netplay->run_frame_count) + if (frame > netplay->run_frame_count || + cmd == NETPLAY_CMD_RESET) { /* This is squirrely: We need to assure that when we advance the * frame in post_frame, THEN we're referring to the frame to @@ -1161,8 +1180,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } } - /* And force rewind to it */ - netplay->force_rewind = true; + /* Make sure our states are correct */ netplay->savestate_request_outstanding = false; netplay->other_ptr = netplay->read_ptr[connection->player]; netplay->other_frame_count = frame; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 9d11e133b1..03b57f34d4 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -153,8 +153,11 @@ enum netplay_cmd /* Request that a client stall because it's running fast */ NETPLAY_CMD_STALL = 0x0045, + /* Request a core reset */ + NETPLAY_CMD_RESET = 0x0046, + /* Sends over cheats enabled on client (unsupported) */ - NETPLAY_CMD_CHEATS = 0x0046, + NETPLAY_CMD_CHEATS = 0x0047, /* Misc. commands */ @@ -404,6 +407,9 @@ struct netplay * events, such as player flipping or savestate loading. */ bool force_rewind; + /* Force a reset */ + bool force_reset; + /* Quirks in the savestate implementation */ uint64_t quirks; diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index bfc8caa2a4..02f949aa0c 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -396,6 +396,13 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) return; } + /* Reset if it was requested */ + if (netplay->force_reset) + { + core_reset(); + netplay->force_reset = false; + } + #ifndef DEBUG_NONDETERMINISTIC_CORES if (!netplay->force_rewind) {