diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index c67023a334..0fc9e9ca10 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -192,6 +192,22 @@ static bool netplay_cmd_nak(netplay_t *netplay) return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); } +bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) +{ + uint32_t payload[2]; + payload[0] = htonl(delta->frame); + payload[1] = htonl(delta->crc); + return netplay_send_raw_cmd(netplay, NETPLAY_CMD_CRC, payload, sizeof(payload)); +} + +bool netplay_cmd_request_savestate(netplay_t *netplay) +{ + if (netplay->savestate_request_outstanding) + return true; + netplay->savestate_request_outstanding = true; + return netplay_send_raw_cmd(netplay, NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); +} + static bool netplay_get_cmd(netplay_t *netplay) { uint32_t cmd; @@ -299,6 +315,72 @@ static bool netplay_get_cmd(netplay_t *netplay) warn_hangup(); return true; + case NETPLAY_CMD_CRC: + { + uint32_t buffer[2]; + size_t tmp_ptr = netplay->self_ptr; + bool found = false; + + if (cmd_size != sizeof(buffer)) + { + RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) + { + RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n"); + return netplay_cmd_nak(netplay); + } + + buffer[0] = ntohl(buffer[0]); + buffer[1] = ntohl(buffer[1]); + + /* Received a CRC for some frame. If we still have it, check if it + * matched. This approach could be improved with some quick modular + * arithmetic. */ + do { + if (netplay->buffer[tmp_ptr].frame == buffer[0]) + { + found = true; + break; + } + + tmp_ptr = PREV_PTR(tmp_ptr); + } while (tmp_ptr != netplay->self_ptr); + + if (!found) + { + /* Oh well, we got rid of it! */ + return true; + } + + if (buffer[0] <= netplay->other_frame_count) + { + /* We've already replayed up to this frame, so we can check it + * directly */ + uint32_t local_crc = netplay_delta_frame_crc(netplay, &netplay->buffer[tmp_ptr]); + if (buffer[1] != local_crc) + { + /* Problem! */ + netplay_cmd_request_savestate(netplay); + } + } + else + { + /* We'll have to check it when we catch up */ + netplay->buffer[tmp_ptr].crc = buffer[1]; + } + + return true; + } + + case NETPLAY_CMD_REQUEST_SAVESTATE: + /* Delay until next frame so we don't send the savestate after the + * input */ + netplay->force_send_savestate = true; + return true; + case NETPLAY_CMD_LOAD_SAVESTATE: { uint32_t frame; @@ -353,6 +435,7 @@ static bool netplay_get_cmd(netplay_t *netplay) /* And force rewind to it */ netplay->force_rewind = true; + netplay->savestate_request_outstanding = false; netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = frame; return true; @@ -1072,7 +1155,6 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) if (netplay->local_paused == paused) return; - fprintf(stderr, "Paused? %d\n", paused); netplay->local_paused = paused; if (netplay->has_connection) netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); @@ -1082,10 +1164,11 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) * netplay_load_savestate * @netplay : pointer to netplay object * @serial_info : the savestate being loaded + * @save : whether to save the provided serial_info into the frame buffer * * Inform Netplay of a savestate load and send it to the other side **/ -void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info) +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save) { uint32_t header[3]; @@ -1093,7 +1176,7 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *seri return; /* Record it in our own buffer */ - if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + if (save && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) { if (serial_info->size <= netplay->state_size) { @@ -1235,7 +1318,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) 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); + netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data, true); break; default: case RARCH_NETPLAY_CTL_NONE: diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 94524ec5c6..eeb3783840 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -76,6 +76,12 @@ enum netplay_cmd /* Loading and synchronization */ + /* Send the CRC hash of a frame's state */ + NETPLAY_CMD_CRC = 0x0010, + + /* Request a savestate */ + NETPLAY_CMD_REQUEST_SAVESTATE = 0x0011, + /* Send a savestate for the client to load */ NETPLAY_CMD_LOAD_SAVESTATE = 0x0012, @@ -187,10 +193,11 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused); * netplay_load_savestate * @netplay : pointer to netplay object * @serial_info : the savestate being loaded + * @save : whether to save the provided serial_info into the frame buffer * * Inform Netplay of a savestate load and send it to the other side **/ -void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info); +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save); /** * init_netplay: diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index a1b26e1154..1f4b89fca2 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -20,6 +20,8 @@ #include "netplay_private.h" #include +#include "compat/zlib.h" + #include "../../movie.h" #include "../../msg_hash.h" #include "../../content.h" @@ -365,3 +367,10 @@ bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, ui delta->state = remember_state; return true; } + +uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) +{ + if (!netplay->state_size) + return 0; + return crc32(0L, delta->state, netplay->state_size); +} diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index d2b4632378..bb7851a08c 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -24,6 +24,25 @@ #include "../../autosave.h" +static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta) +{ + if (netplay_is_server(netplay)) + { + delta->crc = netplay_delta_frame_crc(netplay, delta); + netplay_cmd_crc(netplay, delta); + } + else if (delta->crc) + { + /* We have a remote CRC, so check it */ + uint32_t local_crc = netplay_delta_frame_crc(netplay, delta); + if (local_crc != delta->crc) + { + /* Fix this! */ + netplay_cmd_request_savestate(netplay); + } + } +} + /** * netplay_net_pre_frame: * @netplay : pointer to netplay object @@ -40,7 +59,17 @@ static void netplay_net_pre_frame(netplay_t *netplay) serial_info.data = netplay->buffer[netplay->self_ptr].state; serial_info.size = netplay->state_size; - if (!core_serialize(&serial_info)) + if (core_serialize(&serial_info)) + { + if (netplay->force_send_savestate) + { + /* Send this along to the other side */ + serial_info.data_const = netplay->buffer[netplay->self_ptr].state; + netplay_load_savestate(netplay, &serial_info, false); + netplay->force_send_savestate = false; + } + } + else { /* If the core can't serialize properly, we must stall for the * remote input on EVERY frame, because we can't recover */ @@ -76,12 +105,13 @@ static void netplay_net_post_frame(netplay_t *netplay) 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]; + struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; if (memcmp(ptr->simulated_input_state, ptr->real_input_state, sizeof(ptr->real_input_state)) != 0 && !ptr->used_real) break; + netplay_handle_frame_hash(netplay, ptr); netplay->other_ptr = NEXT_PTR(netplay->other_ptr); netplay->other_frame_count++; } @@ -99,23 +129,23 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->replay_ptr = netplay->other_ptr; netplay->replay_frame_count = netplay->other_frame_count; - if (netplay->replay_frame_count < netplay->self_frame_count) - { - serial_info.data = NULL; - serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; - serial_info.size = netplay->state_size; + serial_info.data = NULL; + serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; + serial_info.size = netplay->state_size; - core_unserialize(&serial_info); - } + core_unserialize(&serial_info); while (netplay->replay_frame_count < netplay->self_frame_count) { - serial_info.data = netplay->buffer[netplay->replay_ptr].state; + struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr]; + serial_info.data = ptr->state; serial_info.size = netplay->state_size; serial_info.data_const = NULL; core_serialize(&serial_info); + netplay_handle_frame_hash(netplay, ptr); + #if defined(HAVE_THREADS) autosave_lock(); #endif @@ -177,7 +207,7 @@ static bool netplay_net_init_buffers(netplay_t *netplay) for (i = 0; i < netplay->buffer_size; i++) { - netplay->buffer[i].state = malloc(netplay->state_size); + netplay->buffer[i].state = calloc(netplay->state_size, 1); if (!netplay->buffer[i].state) return false; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index f90be04353..3f9a335ef5 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -45,8 +45,12 @@ struct delta_frame bool used; /* a bit derpy, but this is how we know if the delta's been used at all */ uint32_t frame; + /* The serialized state of the core at this frame, before input */ void *state; + /* The CRC-32 of the serialized state if we've calculated it, else 0 */ + uint32_t crc; + uint32_t real_input_state[WORDS_PER_FRAME - 1]; uint32_t simulated_input_state[WORDS_PER_FRAME - 1]; uint32_t self_state[WORDS_PER_FRAME - 1]; @@ -111,6 +115,13 @@ struct netplay * events, such as player flipping or savestate loading. */ bool force_rewind; + /* Force our state to be sent to the other side. Used when they request a + * savestate, to send at the next pre-frame. */ + bool force_send_savestate; + + /* Have we requested a savestate as a sync point? */ + bool savestate_request_outstanding; + /* A buffer for outgoing input packets. */ uint32_t packet_buffer[2 + WORDS_PER_FRAME]; uint32_t self_frame_count; @@ -184,4 +195,10 @@ bool netplay_is_spectate(netplay_t* netplay); bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame); +uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta); + +bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta); + +bool netplay_cmd_request_savestate(netplay_t *netplay); + #endif