mirror of
https://github.com/libretro/RetroArch
synced 2025-02-28 22:13:51 +00:00
(Netplay) Disallow clients from loading states and resetting (#14114)
This commit is contained in:
parent
8583038d7f
commit
a5ebfc442a
@ -5655,19 +5655,36 @@ static bool netplay_get_cmd(netplay_t *netplay,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NETPLAY_CMD_LOAD_SAVESTATE:
|
case NETPLAY_CMD_LOAD_SAVESTATE:
|
||||||
case NETPLAY_CMD_RESET:
|
|
||||||
{
|
{
|
||||||
|
uint32_t i;
|
||||||
uint32_t frame;
|
uint32_t frame;
|
||||||
uint32_t isize;
|
uint32_t state_size, state_size_raw;
|
||||||
uint32_t rd, wn;
|
size_t load_ptr;
|
||||||
uint32_t client;
|
|
||||||
uint32_t load_frame_count;
|
uint32_t load_frame_count;
|
||||||
size_t load_ptr;
|
uint32_t rd, wn;
|
||||||
struct compression_transcoder *ctrans = NULL;
|
struct compression_transcoder *ctrans = NULL;
|
||||||
uint32_t client_num = (uint32_t)
|
|
||||||
(connection - netplay->connections + 1);
|
|
||||||
|
|
||||||
/* Make sure we're ready for it */
|
if (netplay->is_server)
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] NETPLAY_CMD_LOAD_SAVESTATE from client.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_size < sizeof(frame) + sizeof(state_size))
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] Received invalid payload size for NETPLAY_CMD_LOAD_SAVESTATE.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only players may load states. */
|
||||||
|
if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
|
||||||
|
connection->mode != NETPLAY_CONNECTION_SLAVE)
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] Netplay state load from a spectator.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure we're ready for it. */
|
||||||
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
|
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
|
||||||
{
|
{
|
||||||
if (!netplay->is_replay)
|
if (!netplay->is_replay)
|
||||||
@ -5676,131 +5693,92 @@ static bool netplay_get_cmd(netplay_t *netplay,
|
|||||||
netplay->replay_ptr = netplay->run_ptr;
|
netplay->replay_ptr = netplay->run_ptr;
|
||||||
netplay->replay_frame_count = netplay->run_frame_count;
|
netplay->replay_frame_count = netplay->run_frame_count;
|
||||||
netplay_wait_and_init_serialization(netplay);
|
netplay_wait_and_init_serialization(netplay);
|
||||||
netplay->is_replay = false;
|
netplay->is_replay = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
netplay_wait_and_init_serialization(netplay);
|
netplay_wait_and_init_serialization(netplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only players may load states */
|
/* There is a subtlety in whether the load comes before or after
|
||||||
if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
|
* the current frame:
|
||||||
connection->mode != NETPLAY_CONNECTION_SLAVE)
|
|
||||||
{
|
|
||||||
RARCH_ERR("[Netplay] Netplay state load from a spectator.\n");
|
|
||||||
return netplay_cmd_nak(netplay, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We only allow players to load state if we're in a simple
|
|
||||||
* two-player situation */
|
|
||||||
if (netplay->is_server && netplay->connections_size > 1)
|
|
||||||
{
|
|
||||||
RARCH_ERR("[Netplay] Netplay state load from a client with other clients connected disallowed.\n");
|
|
||||||
return netplay_cmd_nak(netplay, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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
|
* If it comes before the current frame, then we need to force a
|
||||||
* rewind to that point.
|
* rewind to that point.
|
||||||
*
|
*
|
||||||
* If it comes after the current frame, we need to jump ahead, then
|
* If it comes after the current frame, we need to jump ahead,
|
||||||
* (strangely) force a rewind to the frame we're already on, so it
|
* then (strangely) force a rewind to the frame we're already on,
|
||||||
* gets loaded. This is just to avoid having reloading implemented in
|
* so it gets loaded.
|
||||||
* too many places. */
|
* This is just to avoid having reloading implemented
|
||||||
|
* in too many places. */
|
||||||
/* 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(frame)))
|
|
||||||
{
|
|
||||||
RARCH_ERR("[Netplay] CMD_LOAD_SAVESTATE received an unexpected payload size.\n");
|
|
||||||
return netplay_cmd_nak(netplay, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
RECV(&frame, sizeof(frame))
|
RECV(&frame, sizeof(frame))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
frame = ntohl(frame);
|
frame = ntohl(frame);
|
||||||
|
|
||||||
if (netplay->is_server)
|
load_ptr = netplay->server_ptr;
|
||||||
{
|
load_frame_count = netplay->server_frame_count;
|
||||||
load_ptr = netplay->read_ptr[client_num];
|
|
||||||
load_frame_count = netplay->read_frame_count[client_num];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
load_ptr = netplay->server_ptr;
|
|
||||||
load_frame_count = netplay->server_frame_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame != load_frame_count)
|
if (frame != load_frame_count)
|
||||||
{
|
{
|
||||||
RARCH_ERR("[Netplay] CMD_LOAD_SAVESTATE loading a state out of order!\n");
|
RARCH_ERR("[Netplay] Netplay state load out of order!\n");
|
||||||
return netplay_cmd_nak(netplay, connection);
|
return netplay_cmd_nak(netplay, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!netplay_delta_frame_ready(netplay, &netplay->buffer[load_ptr], load_frame_count))
|
if (!netplay_delta_frame_ready(netplay,
|
||||||
{
|
&netplay->buffer[load_ptr], load_frame_count))
|
||||||
/* Hopefully it will be after another round of input */
|
/* Hopefully it will be ready after another round of input. */
|
||||||
goto shrt;
|
goto shrt;
|
||||||
|
|
||||||
|
RECV(&state_size, sizeof(state_size))
|
||||||
|
return false;
|
||||||
|
state_size = ntohl(state_size);
|
||||||
|
state_size_raw = cmd_size - (sizeof(frame) + sizeof(state_size));
|
||||||
|
|
||||||
|
if (state_size != netplay->state_size ||
|
||||||
|
state_size_raw > netplay->zbuffer_size)
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] Netplay state load with an unexpected save state size.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now we switch based on whether we're loading a state or resetting */
|
RECV(netplay->zbuffer, state_size_raw)
|
||||||
if (cmd == NETPLAY_CMD_LOAD_SAVESTATE)
|
return false;
|
||||||
|
|
||||||
|
switch (connection->compression_supported)
|
||||||
{
|
{
|
||||||
RECV(&isize, sizeof(isize))
|
case NETPLAY_COMPRESSION_ZLIB:
|
||||||
return false;
|
ctrans = &netplay->compress_zlib;
|
||||||
|
break;
|
||||||
isize = ntohl(isize);
|
default:
|
||||||
|
ctrans = &netplay->compress_nil;
|
||||||
if (isize != netplay->state_size)
|
break;
|
||||||
{
|
|
||||||
RARCH_ERR("[Netplay] 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))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* 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[load_ptr].state,
|
|
||||||
(unsigned)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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skip ahead if it's past where we are */
|
ctrans->decompression_backend->set_in(
|
||||||
if (load_frame_count > netplay->run_frame_count ||
|
ctrans->decompression_stream,
|
||||||
cmd == NETPLAY_CMD_RESET)
|
netplay->zbuffer, state_size_raw);
|
||||||
|
ctrans->decompression_backend->set_out(
|
||||||
|
ctrans->decompression_stream,
|
||||||
|
(uint8_t*)netplay->buffer[load_ptr].state, state_size);
|
||||||
|
ctrans->decompression_backend->trans(
|
||||||
|
ctrans->decompression_stream,
|
||||||
|
true, &rd, &wn, NULL);
|
||||||
|
|
||||||
|
/* Force a rewind to the relevant frame. */
|
||||||
|
netplay->force_rewind = true;
|
||||||
|
|
||||||
|
/* Skip ahead if it's past where we are. */
|
||||||
|
if (load_frame_count > netplay->run_frame_count)
|
||||||
{
|
{
|
||||||
/* This is squirrely: We need to assure that when we advance the
|
/* This is squirrely:
|
||||||
* frame in post_frame, THEN we're referring to the frame to
|
* We need to assure that when we advance the frame in post_frame,
|
||||||
* load into. If we refer directly to read_ptr, then we'll end
|
* THEN we're referring to the frame to load into.
|
||||||
* up never reading the input for read_frame_count itself, which
|
* If we refer directly to read_ptr,
|
||||||
* will make the other side unhappy. */
|
* then we'll end up never reading the input for read_frame_count itself,
|
||||||
netplay->run_ptr = PREV_PTR(load_ptr);
|
* which will make the other side unhappy. */
|
||||||
netplay->run_frame_count = load_frame_count - 1;
|
netplay->run_ptr = PREV_PTR(load_ptr);
|
||||||
|
netplay->run_frame_count = load_frame_count - 1;
|
||||||
|
|
||||||
if (frame > netplay->self_frame_count)
|
if (frame > netplay->self_frame_count)
|
||||||
{
|
{
|
||||||
netplay->self_ptr = netplay->run_ptr;
|
netplay->self_ptr = netplay->run_ptr;
|
||||||
@ -5808,28 +5786,122 @@ static bool netplay_get_cmd(netplay_t *netplay,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't expect earlier data from other clients */
|
/* Don't expect earlier data from other clients. */
|
||||||
for (client = 0; client < MAX_CLIENTS; client++)
|
for (i = 0; i < MAX_CLIENTS; i++)
|
||||||
{
|
{
|
||||||
if (!(netplay->connected_players & (1<<client)))
|
if (!(netplay->connected_players & (1 << i)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (frame > netplay->read_frame_count[client])
|
if (frame > netplay->read_frame_count[i])
|
||||||
{
|
{
|
||||||
netplay->read_ptr[client] = load_ptr;
|
netplay->read_ptr[i] = load_ptr;
|
||||||
netplay->read_frame_count[client] = load_frame_count;
|
netplay->read_frame_count[i] = load_frame_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure our states are correct */
|
/* Make sure our states are correct. */
|
||||||
netplay->savestate_request_outstanding = false;
|
netplay->savestate_request_outstanding = false;
|
||||||
netplay->other_ptr = load_ptr;
|
netplay->other_ptr = load_ptr;
|
||||||
netplay->other_frame_count = load_frame_count;
|
netplay->other_frame_count = load_frame_count;
|
||||||
|
|
||||||
#ifdef DEBUG_NETPLAY_STEPS
|
break;
|
||||||
RARCH_LOG("[Netplay] Loading state at %u\n", load_frame_count);
|
}
|
||||||
print_state(netplay);
|
|
||||||
#endif
|
case NETPLAY_CMD_RESET:
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
uint32_t frame;
|
||||||
|
size_t reset_ptr;
|
||||||
|
uint32_t reset_frame_count;
|
||||||
|
|
||||||
|
if (netplay->is_server)
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] NETPLAY_CMD_RESET from client.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_size != sizeof(frame))
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] Received invalid payload size for NETPLAY_CMD_RESET.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only players may reset the core. */
|
||||||
|
if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
|
||||||
|
connection->mode != NETPLAY_CONNECTION_SLAVE)
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] Netplay core reset from a spectator.\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure we're ready for it. */
|
||||||
|
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
|
||||||
|
{
|
||||||
|
if (!netplay->is_replay)
|
||||||
|
{
|
||||||
|
netplay->is_replay = true;
|
||||||
|
netplay->replay_ptr = netplay->run_ptr;
|
||||||
|
netplay->replay_frame_count = netplay->run_frame_count;
|
||||||
|
netplay_wait_and_init_serialization(netplay);
|
||||||
|
netplay->is_replay = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
netplay_wait_and_init_serialization(netplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECV(&frame, sizeof(frame))
|
||||||
|
return false;
|
||||||
|
frame = ntohl(frame);
|
||||||
|
|
||||||
|
reset_ptr = netplay->server_ptr;
|
||||||
|
reset_frame_count = netplay->server_frame_count;
|
||||||
|
|
||||||
|
if (frame != reset_frame_count)
|
||||||
|
{
|
||||||
|
RARCH_ERR("[Netplay] Netplay core reset out of order!\n");
|
||||||
|
return netplay_cmd_nak(netplay, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!netplay_delta_frame_ready(netplay,
|
||||||
|
&netplay->buffer[reset_ptr], reset_frame_count))
|
||||||
|
/* Hopefully it will be ready after another round of input. */
|
||||||
|
goto shrt;
|
||||||
|
|
||||||
|
netplay->force_reset = true;
|
||||||
|
|
||||||
|
/* This is squirrely:
|
||||||
|
* We need to assure that when we advance the frame in post_frame,
|
||||||
|
* THEN we're referring to the frame to load into.
|
||||||
|
* If we refer directly to read_ptr,
|
||||||
|
* then we'll end up never reading the input for read_frame_count itself,
|
||||||
|
* which will make the other side unhappy. */
|
||||||
|
|
||||||
|
netplay->run_ptr = PREV_PTR(reset_ptr);
|
||||||
|
netplay->run_frame_count = reset_frame_count - 1;
|
||||||
|
|
||||||
|
if (frame > netplay->self_frame_count)
|
||||||
|
{
|
||||||
|
netplay->self_ptr = netplay->run_ptr;
|
||||||
|
netplay->self_frame_count = netplay->run_frame_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't expect earlier data from other clients. */
|
||||||
|
for (i = 0; i < MAX_CLIENTS; i++)
|
||||||
|
{
|
||||||
|
if (!(netplay->connected_players & (1 << i)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (frame > netplay->read_frame_count[i])
|
||||||
|
{
|
||||||
|
netplay->read_ptr[i] = reset_ptr;
|
||||||
|
netplay->read_frame_count[i] = reset_frame_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure our states are correct. */
|
||||||
|
netplay->savestate_request_outstanding = false;
|
||||||
|
netplay->other_ptr = reset_ptr;
|
||||||
|
netplay->other_frame_count = reset_frame_count;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user