mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-30 19:50:24 +00:00
1569 lines
50 KiB
C
1569 lines
50 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2016-2017 - Gregor Richards
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <boolean.h>
|
|
#include <compat/strl.h>
|
|
|
|
#include "netplay_private.h"
|
|
|
|
#include "../../configuration.h"
|
|
#include "../../retroarch.h"
|
|
#include "../../tasks/tasks_internal.h"
|
|
|
|
#if 0
|
|
#define DEBUG_NETPLAY_STEPS 1
|
|
|
|
static void print_state(netplay_t *netplay)
|
|
{
|
|
char msg[512];
|
|
size_t cur = 0;
|
|
uint32_t player;
|
|
|
|
#define APPEND(out) cur += snprintf out
|
|
#define M msg + cur, sizeof(msg) - cur
|
|
|
|
APPEND((M, "NETPLAY: S:%u U:%u O:%u", netplay->self_frame_count, netplay->unread_frame_count, netplay->other_frame_count));
|
|
if (!netplay->is_server)
|
|
APPEND((M, " H:%u", netplay->server_frame_count));
|
|
for (player = 0; player < MAX_USERS; player++)
|
|
{
|
|
if ((netplay->connected_players & (1<<player)))
|
|
APPEND((M, " %u:%u", player, netplay->read_frame_count[player]));
|
|
}
|
|
APPEND((M, "\n"));
|
|
msg[sizeof(msg)-1] = '\0';
|
|
|
|
RARCH_LOG("%s\n", msg);
|
|
|
|
#undef APPEND
|
|
#undef M
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* remote_unpaused
|
|
*
|
|
* Mark a particular remote connection as unpaused and, if relevant, inform
|
|
* every one else that they may resume.
|
|
*/
|
|
static void remote_unpaused(netplay_t *netplay, struct netplay_connection *connection)
|
|
{
|
|
size_t i;
|
|
connection->paused = false;
|
|
netplay->remote_paused = false;
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *sc = &netplay->connections[i];
|
|
if (sc->active && sc->paused)
|
|
{
|
|
netplay->remote_paused = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!netplay->remote_paused && !netplay->local_paused)
|
|
netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_RESUME, NULL, 0);
|
|
}
|
|
|
|
/**
|
|
* netplay_hangup:
|
|
*
|
|
* Disconnects an active Netplay connection due to an error
|
|
*/
|
|
void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection)
|
|
{
|
|
char msg[512];
|
|
const char *dmsg;
|
|
|
|
if (!netplay)
|
|
return;
|
|
if (!connection->active)
|
|
return;
|
|
|
|
msg[0] = msg[sizeof(msg)-1] = '\0';
|
|
dmsg = msg;
|
|
|
|
/* Report this disconnection */
|
|
if (netplay->is_server)
|
|
{
|
|
if (connection->nick[0])
|
|
snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_SERVER_NAMED_HANGUP), connection->nick);
|
|
else
|
|
dmsg = msg_hash_to_str(MSG_NETPLAY_SERVER_HANGUP);
|
|
}
|
|
else
|
|
{
|
|
dmsg = msg_hash_to_str(MSG_NETPLAY_CLIENT_HANGUP);
|
|
netplay->is_connected = false;
|
|
}
|
|
RARCH_LOG("%s\n", dmsg);
|
|
runloop_msg_queue_push(dmsg, 1, 180, false);
|
|
|
|
socket_close(connection->fd);
|
|
connection->active = false;
|
|
netplay_deinit_socket_buffer(&connection->send_packet_buffer);
|
|
netplay_deinit_socket_buffer(&connection->recv_packet_buffer);
|
|
|
|
if (!netplay->is_server)
|
|
{
|
|
netplay->self_mode = NETPLAY_CONNECTION_NONE;
|
|
netplay->connected_players = 0;
|
|
netplay->stall = NETPLAY_STALL_NONE;
|
|
|
|
}
|
|
else
|
|
{
|
|
/* Remove this player */
|
|
if (connection->mode == NETPLAY_CONNECTION_PLAYING ||
|
|
connection->mode == NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
netplay->connected_players &= ~(1<<connection->player);
|
|
netplay->connected_slaves &= ~(1<<connection->player);
|
|
|
|
/* FIXME: Duplication */
|
|
if (netplay->is_server)
|
|
{
|
|
uint32_t payload[2];
|
|
payload[0] = htonl(netplay->read_frame_count[connection->player]);
|
|
payload[1] = htonl(connection->player);
|
|
netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Unpause them */
|
|
if (connection->paused)
|
|
remote_unpaused(netplay, connection);
|
|
}
|
|
|
|
/* Send the specified input data */
|
|
static bool send_input_frame(netplay_t *netplay,
|
|
struct netplay_connection *only, struct netplay_connection *except,
|
|
uint32_t frame, uint32_t player, uint32_t *state)
|
|
{
|
|
uint32_t buffer[2 + WORDS_PER_FRAME];
|
|
size_t i;
|
|
|
|
buffer[0] = htonl(NETPLAY_CMD_INPUT);
|
|
buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t));
|
|
buffer[2] = htonl(frame);
|
|
buffer[3] = htonl(player);
|
|
buffer[4] = htonl(state[0]);
|
|
buffer[5] = htonl(state[1]);
|
|
buffer[6] = htonl(state[2]);
|
|
|
|
if (only)
|
|
{
|
|
if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer)))
|
|
{
|
|
netplay_hangup(netplay, only);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *connection = &netplay->connections[i];
|
|
if (connection == except) continue;
|
|
if (connection->active &&
|
|
connection->mode >= NETPLAY_CONNECTION_CONNECTED &&
|
|
(connection->mode != NETPLAY_CONNECTION_PLAYING ||
|
|
connection->player != player))
|
|
{
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd,
|
|
buffer, sizeof(buffer)))
|
|
netplay_hangup(netplay, connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* netplay_send_cur_input
|
|
*
|
|
* Send the current input frame to a given connection.
|
|
*
|
|
* Returns true if successful, false otherwise.
|
|
*/
|
|
bool netplay_send_cur_input(netplay_t *netplay,
|
|
struct netplay_connection *connection)
|
|
{
|
|
struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr];
|
|
uint32_t player;
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
/* Send the other players' input data */
|
|
for (player = 0; player < MAX_USERS; player++)
|
|
{
|
|
if (connection->mode == NETPLAY_CONNECTION_PLAYING &&
|
|
connection->player == player)
|
|
continue;
|
|
if ((netplay->connected_players & (1<<player)))
|
|
{
|
|
if (dframe->have_real[player])
|
|
{
|
|
if (!send_input_frame(netplay, connection, NULL,
|
|
netplay->self_frame_count, player,
|
|
dframe->real_input_state[player]))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we're not playing, send a NOINPUT */
|
|
if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING)
|
|
{
|
|
uint32_t payload = htonl(netplay->self_frame_count);
|
|
if (!netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NOINPUT,
|
|
&payload, sizeof(payload)))
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/* Send our own data */
|
|
if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING ||
|
|
netplay->self_mode == NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
if (!send_input_frame(netplay, connection, NULL,
|
|
netplay->self_frame_count,
|
|
(netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player,
|
|
dframe->self_state))
|
|
return false;
|
|
}
|
|
|
|
if (!netplay_send_flush(&connection->send_packet_buffer, connection->fd,
|
|
false))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* netplay_send_raw_cmd
|
|
*
|
|
* Send a raw Netplay command to the given connection.
|
|
*
|
|
* Returns true on success, false on failure.
|
|
*/
|
|
bool netplay_send_raw_cmd(netplay_t *netplay,
|
|
struct netplay_connection *connection, uint32_t cmd, const void *data,
|
|
size_t size)
|
|
{
|
|
uint32_t cmdbuf[2];
|
|
|
|
cmdbuf[0] = htonl(cmd);
|
|
cmdbuf[1] = htonl(size);
|
|
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmdbuf,
|
|
sizeof(cmdbuf)))
|
|
return false;
|
|
|
|
if (size > 0)
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd, data, size))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* netplay_send_raw_cmd_all
|
|
*
|
|
* Send a raw Netplay command to all connections, optionally excluding one
|
|
* (typically the client that the relevant command came from)
|
|
*/
|
|
void netplay_send_raw_cmd_all(netplay_t *netplay,
|
|
struct netplay_connection *except, uint32_t cmd, const void *data,
|
|
size_t size)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *connection = &netplay->connections[i];
|
|
if (connection == except)
|
|
continue;
|
|
if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED)
|
|
{
|
|
if (!netplay_send_raw_cmd(netplay, connection, cmd, data, size))
|
|
netplay_hangup(netplay, connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool netplay_cmd_nak(netplay_t *netplay,
|
|
struct netplay_connection *connection)
|
|
{
|
|
netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NAK, NULL, 0);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* netplay_cmd_crc
|
|
*
|
|
* Send a CRC command to all active clients.
|
|
*/
|
|
bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta)
|
|
{
|
|
uint32_t payload[2];
|
|
bool success = true;
|
|
size_t i;
|
|
payload[0] = htonl(delta->frame);
|
|
payload[1] = htonl(delta->crc);
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
if (netplay->connections[i].active &&
|
|
netplay->connections[i].mode >= NETPLAY_CONNECTION_CONNECTED)
|
|
success = netplay_send_raw_cmd(netplay, &netplay->connections[i],
|
|
NETPLAY_CMD_CRC, payload, sizeof(payload)) && success;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* netplay_cmd_request_savestate
|
|
*
|
|
* Send a savestate request command.
|
|
*/
|
|
bool netplay_cmd_request_savestate(netplay_t *netplay)
|
|
{
|
|
if (netplay->connections_size == 0 ||
|
|
!netplay->connections[0].active ||
|
|
netplay->connections[0].mode < NETPLAY_CONNECTION_CONNECTED)
|
|
return false;
|
|
if (netplay->savestate_request_outstanding)
|
|
return true;
|
|
netplay->savestate_request_outstanding = true;
|
|
return netplay_send_raw_cmd(netplay, &netplay->connections[0],
|
|
NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0);
|
|
}
|
|
|
|
/**
|
|
* netplay_cmd_mode
|
|
*
|
|
* Send a mode request command to either play or spectate.
|
|
*/
|
|
bool netplay_cmd_mode(netplay_t *netplay,
|
|
struct netplay_connection *connection,
|
|
enum rarch_netplay_connection_mode mode)
|
|
{
|
|
uint32_t cmd;
|
|
uint32_t payloadBuf, *payload = NULL;
|
|
switch (mode)
|
|
{
|
|
case NETPLAY_CONNECTION_SPECTATING:
|
|
cmd = NETPLAY_CMD_SPECTATE;
|
|
break;
|
|
|
|
case NETPLAY_CONNECTION_SLAVE:
|
|
payload = &payloadBuf;
|
|
payloadBuf = htonl(NETPLAY_CMD_PLAY_BIT_SLAVE);
|
|
/* Intentional fallthrough */
|
|
|
|
case NETPLAY_CONNECTION_PLAYING:
|
|
cmd = NETPLAY_CMD_PLAY;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
return netplay_send_raw_cmd(netplay, connection, cmd, payload,
|
|
payload ? sizeof(uint32_t) : 0);
|
|
}
|
|
|
|
/**
|
|
* netplay_cmd_stall
|
|
*
|
|
* Send a stall command.
|
|
*/
|
|
bool netplay_cmd_stall(netplay_t *netplay,
|
|
struct netplay_connection *connection,
|
|
uint32_t frames)
|
|
{
|
|
frames = htonl(frames);
|
|
return netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_STALL, &frames, sizeof(frames));
|
|
}
|
|
|
|
#undef RECV
|
|
#define RECV(buf, sz) \
|
|
recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \
|
|
(sz), false); \
|
|
if (recvd >= 0 && recvd < (ssize_t) (sz)) goto shrt; \
|
|
else if (recvd < 0)
|
|
|
|
static bool netplay_get_cmd(netplay_t *netplay,
|
|
struct netplay_connection *connection, bool *had_input)
|
|
{
|
|
uint32_t cmd;
|
|
uint32_t flip_frame;
|
|
uint32_t cmd_size;
|
|
ssize_t recvd;
|
|
char msg[512];
|
|
|
|
/* We don't handle the initial handshake here */
|
|
if (connection->mode < NETPLAY_CONNECTION_CONNECTED)
|
|
return netplay_handshake(netplay, connection, had_input);
|
|
|
|
RECV(&cmd, sizeof(cmd))
|
|
return false;
|
|
|
|
cmd = ntohl(cmd);
|
|
|
|
RECV(&cmd_size, sizeof(cmd_size))
|
|
return false;
|
|
|
|
cmd_size = ntohl(cmd_size);
|
|
|
|
netplay->timeout_cnt = 0;
|
|
|
|
switch (cmd)
|
|
{
|
|
case NETPLAY_CMD_ACK:
|
|
/* Why are we even bothering? */
|
|
break;
|
|
|
|
case NETPLAY_CMD_NAK:
|
|
/* Disconnect now! */
|
|
return false;
|
|
|
|
case NETPLAY_CMD_INPUT:
|
|
{
|
|
uint32_t buffer[WORDS_PER_FRAME];
|
|
uint32_t player;
|
|
unsigned i;
|
|
struct delta_frame *dframe;
|
|
|
|
if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t))
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(buffer, sizeof(buffer))
|
|
{
|
|
RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
for (i = 0; i < WORDS_PER_FRAME; i++)
|
|
buffer[i] = ntohl(buffer[i]);
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
/* Ignore the claimed player #, must be this client */
|
|
if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
|
|
connection->mode != NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
RARCH_ERR("Netplay input from non-participating player.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
player = connection->player;
|
|
}
|
|
else
|
|
{
|
|
player = buffer[1] & ~NETPLAY_CMD_INPUT_BIT_SERVER;
|
|
}
|
|
|
|
if (player >= MAX_USERS || !(netplay->connected_players & (1<<player)))
|
|
{
|
|
RARCH_ERR("Invalid NETPLAY_CMD_INPUT player number.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
/* Check the frame number only if they're not in slave mode */
|
|
if (connection->mode == NETPLAY_CONNECTION_PLAYING)
|
|
{
|
|
if (buffer[0] < netplay->read_frame_count[player])
|
|
{
|
|
/* We already had this, so ignore the new transmission */
|
|
break;
|
|
}
|
|
else if (buffer[0] > netplay->read_frame_count[player])
|
|
{
|
|
/* Out of order = out of luck */
|
|
RARCH_ERR("Netplay input out of order.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
}
|
|
|
|
/* The data's good! */
|
|
dframe = &netplay->buffer[netplay->read_ptr[player]];
|
|
if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player]))
|
|
{
|
|
/* Hopefully we'll be ready after another round of input */
|
|
goto shrt;
|
|
}
|
|
memcpy(dframe->real_input_state[player], buffer + 2,
|
|
WORDS_PER_INPUT*sizeof(uint32_t));
|
|
dframe->have_real[player] = true;
|
|
|
|
/* Slaves may go through several packets of data in the same frame
|
|
* if latency is choppy, so we advance and send their data after
|
|
* handling all network data this frame */
|
|
if (connection->mode == NETPLAY_CONNECTION_PLAYING)
|
|
{
|
|
netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]);
|
|
netplay->read_frame_count[player]++;
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
/* Forward it on if it's past data*/
|
|
if (dframe->frame <= netplay->self_frame_count)
|
|
send_input_frame(netplay, NULL, connection, buffer[0],
|
|
player, dframe->real_input_state[player]);
|
|
}
|
|
}
|
|
|
|
/* If this was server data, advance our server pointer too */
|
|
if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER))
|
|
{
|
|
netplay->server_ptr = netplay->read_ptr[player];
|
|
netplay->server_frame_count = netplay->read_frame_count[player];
|
|
}
|
|
|
|
#ifdef DEBUG_NETPLAY_STEPS
|
|
RARCH_LOG("Received input from %u\n", player);
|
|
print_state(netplay);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_NOINPUT:
|
|
{
|
|
uint32_t frame;
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_NOINPUT from a client.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(&frame, sizeof(frame))
|
|
{
|
|
RARCH_ERR("Failed to receive NETPLAY_CMD_NOINPUT payload.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
frame = ntohl(frame);
|
|
|
|
if (frame < netplay->server_frame_count)
|
|
{
|
|
/* We already had this, so ignore the new transmission */
|
|
break;
|
|
}
|
|
|
|
if (frame != netplay->server_frame_count)
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_NOINPUT for invalid frame.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
netplay->server_ptr = NEXT_PTR(netplay->server_ptr);
|
|
netplay->server_frame_count++;
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_FLIP_PLAYERS:
|
|
if (cmd_size != sizeof(uint32_t))
|
|
{
|
|
RARCH_ERR("CMD_FLIP_PLAYERS received an unexpected command size.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(&flip_frame, sizeof(flip_frame))
|
|
{
|
|
RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_FLIP_PLAYERS from a client.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
flip_frame = ntohl(flip_frame);
|
|
|
|
if (flip_frame < netplay->server_frame_count)
|
|
{
|
|
RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
netplay->flip ^= true;
|
|
netplay->flip_frame = flip_frame;
|
|
|
|
/* Force a rewind to assure the flip happens: This just prevents us
|
|
* from skipping other past the flip because our prediction was
|
|
* correct */
|
|
if (flip_frame < netplay->self_frame_count)
|
|
netplay->force_rewind = true;
|
|
|
|
RARCH_LOG("%s.\n", msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED));
|
|
runloop_msg_queue_push(
|
|
msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED), 1, 180, false);
|
|
|
|
break;
|
|
|
|
case NETPLAY_CMD_SPECTATE:
|
|
{
|
|
uint32_t payload[2];
|
|
|
|
if (!netplay->is_server)
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_SPECTATE from a server.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
if (connection->mode == NETPLAY_CONNECTION_PLAYING ||
|
|
connection->mode == NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
/* The frame we haven't received is their end frame */
|
|
payload[0] = htonl(netplay->read_frame_count[connection->player]);
|
|
|
|
/* Mark them as not playing anymore */
|
|
connection->mode = NETPLAY_CONNECTION_SPECTATING;
|
|
netplay->connected_players &= ~(1<<connection->player);
|
|
netplay->connected_slaves &= ~(1<<connection->player);
|
|
|
|
/* Tell everyone */
|
|
payload[1] = htonl(connection->player);
|
|
netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
|
|
|
|
/* Announce it */
|
|
msg[sizeof(msg)-1] = '\0';
|
|
snprintf(msg, sizeof(msg)-1, "Player %d has left", connection->player+1);
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
}
|
|
else
|
|
{
|
|
payload[0] = htonl(0);
|
|
}
|
|
|
|
/* Tell the player even if they were confused */
|
|
payload[1] = htonl(NETPLAY_CMD_MODE_BIT_YOU | connection->player);
|
|
netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_PLAY:
|
|
{
|
|
uint32_t payload[2];
|
|
uint32_t player = 0;
|
|
bool slave = false;
|
|
settings_t *settings = config_get_ptr();
|
|
|
|
/* Check if they requested slave mode */
|
|
if (cmd_size == sizeof(uint32_t))
|
|
{
|
|
RECV(payload, sizeof(uint32_t))
|
|
{
|
|
RARCH_ERR("Failed to receive NETPLAY_CMD_PLAY payload.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
payload[0] = ntohl(payload[0]);
|
|
if (payload[0] & NETPLAY_CMD_PLAY_BIT_SLAVE)
|
|
slave = true;
|
|
}
|
|
else if (cmd_size != 0)
|
|
{
|
|
RARCH_ERR("Invalid payload size for NETPLAY_CMD_PLAY.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
/* Check if their slave mode request corresponds with what we allow */
|
|
if (settings->bools.netplay_require_slaves)
|
|
slave = true;
|
|
else if (!settings->bools.netplay_allow_slaves)
|
|
slave = false;
|
|
|
|
payload[0] = htonl(netplay->self_frame_count + 1);
|
|
|
|
if (!netplay->is_server)
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_PLAY from a server.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
if (!connection->can_play)
|
|
{
|
|
/* Not allowed to play */
|
|
payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED);
|
|
netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t));
|
|
break;
|
|
}
|
|
|
|
/* Find an available player slot */
|
|
for (player = 0; player <= netplay->player_max; player++)
|
|
{
|
|
if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING &&
|
|
netplay->self_player == player) &&
|
|
!(netplay->connected_players & (1<<player)))
|
|
break;
|
|
}
|
|
if (player > netplay->player_max)
|
|
{
|
|
/* No slots free! */
|
|
payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS);
|
|
netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t));
|
|
break;
|
|
}
|
|
|
|
if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
|
|
connection->mode != NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
/* Mark them as playing */
|
|
connection->mode = slave ? NETPLAY_CONNECTION_SLAVE :
|
|
NETPLAY_CONNECTION_PLAYING;
|
|
connection->player = player;
|
|
netplay->connected_players |= 1<<player;
|
|
if (slave)
|
|
netplay->connected_slaves |= 1<<player;
|
|
|
|
/* Tell everyone */
|
|
payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING |
|
|
(slave?NETPLAY_CMD_MODE_BIT_SLAVE:0) |
|
|
connection->player);
|
|
netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
|
|
|
|
/* Announce it */
|
|
msg[sizeof(msg)-1] = '\0';
|
|
snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1);
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
}
|
|
|
|
/* Tell the player even if they were confused */
|
|
payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING |
|
|
((connection->mode == NETPLAY_CONNECTION_SLAVE)?
|
|
NETPLAY_CMD_MODE_BIT_SLAVE:0) |
|
|
NETPLAY_CMD_MODE_BIT_YOU |
|
|
connection->player);
|
|
netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload));
|
|
|
|
/* And expect their data */
|
|
netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr);
|
|
netplay->read_frame_count[player] = netplay->self_frame_count + 1;
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_MODE:
|
|
{
|
|
uint32_t payload[2];
|
|
uint32_t frame, mode, player;
|
|
size_t ptr;
|
|
struct delta_frame *dframe;
|
|
|
|
#define START(which) \
|
|
do { \
|
|
ptr = which; \
|
|
dframe = &netplay->buffer[ptr]; \
|
|
} while(0)
|
|
#define NEXT() \
|
|
do { \
|
|
ptr = NEXT_PTR(ptr); \
|
|
dframe = &netplay->buffer[ptr]; \
|
|
} while(0)
|
|
|
|
if (cmd_size != sizeof(payload) ||
|
|
netplay->is_server)
|
|
{
|
|
RARCH_ERR("Invalid payload size for NETPLAY_CMD_MODE.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(payload, sizeof(payload))
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_MODE failed to receive payload.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
frame = ntohl(payload[0]);
|
|
|
|
/* We're changing past input, so must replay it */
|
|
if (frame < netplay->self_frame_count)
|
|
netplay->force_rewind = true;
|
|
|
|
mode = ntohl(payload[1]);
|
|
player = mode & 0xFFFF;
|
|
if (player >= MAX_USERS)
|
|
{
|
|
RARCH_ERR("Received NETPLAY_CMD_MODE for a higher player number than we support.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
if (mode & NETPLAY_CMD_MODE_BIT_YOU)
|
|
{
|
|
/* A change to me! */
|
|
if (mode & NETPLAY_CMD_MODE_BIT_PLAYING)
|
|
{
|
|
if (frame != netplay->server_frame_count)
|
|
{
|
|
RARCH_ERR("Received mode change out of order.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
/* Hooray, I get to play now! */
|
|
if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
|
|
{
|
|
RARCH_ERR("Received player mode change even though I'm already a player.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
/* Our mode is based on whether we have the slave bit set */
|
|
if (mode & NETPLAY_CMD_MODE_BIT_SLAVE)
|
|
{
|
|
netplay->self_mode = NETPLAY_CONNECTION_SLAVE;
|
|
|
|
/* In slave mode we receive the data from the remote side, so
|
|
* we actually consider ourself a connected player */
|
|
netplay->connected_players |= (1<<player);
|
|
netplay->read_ptr[player] = netplay->server_ptr;
|
|
netplay->read_frame_count[player] = netplay->server_frame_count;
|
|
}
|
|
else
|
|
{
|
|
netplay->self_mode = NETPLAY_CONNECTION_PLAYING;
|
|
}
|
|
netplay->self_player = player;
|
|
|
|
/* Fix up current frame info */
|
|
if (frame <= netplay->self_frame_count)
|
|
{
|
|
/* It wanted past frames, better send 'em! */
|
|
START(netplay->server_ptr);
|
|
while (dframe->used && dframe->frame <= netplay->self_frame_count)
|
|
{
|
|
memcpy(dframe->real_input_state[player], dframe->self_state, sizeof(dframe->self_state));
|
|
dframe->have_real[player] = true;
|
|
send_input_frame(netplay, connection, NULL, dframe->frame, player, dframe->self_state);
|
|
if (dframe->frame == netplay->self_frame_count) break;
|
|
NEXT();
|
|
}
|
|
|
|
}
|
|
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;
|
|
while (true)
|
|
{
|
|
if (!dframe->used)
|
|
{
|
|
/* Make sure it's ready */
|
|
if (!netplay_delta_frame_ready(netplay, dframe, frame_count))
|
|
{
|
|
RARCH_ERR("Received mode change but delta frame isn't ready!\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
}
|
|
|
|
memset(dframe->self_state, 0, sizeof(dframe->self_state));
|
|
memset(dframe->real_input_state[player], 0, sizeof(dframe->self_state));
|
|
dframe->have_local = true;
|
|
|
|
/* Go on to the next delta frame */
|
|
NEXT();
|
|
frame_count++;
|
|
|
|
if (frame_count >= frame)
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/* Announce it */
|
|
msg[sizeof(msg)-1] = '\0';
|
|
snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1);
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
#ifdef DEBUG_NETPLAY_STEPS
|
|
RARCH_LOG("Received mode change self->%u\n", player);
|
|
print_state(netplay);
|
|
#endif
|
|
|
|
}
|
|
else /* YOU && !PLAYING */
|
|
{
|
|
/* I'm no longer playing, but I should already know this */
|
|
if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING)
|
|
{
|
|
RARCH_ERR("Received mode change to spectator unprompted.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
/* Unmark ourself, in case we were in slave mode */
|
|
netplay->connected_players &= ~(1<<player);
|
|
|
|
/* Announce it */
|
|
strlcpy(msg, "You have left the game", sizeof(msg));
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
#ifdef DEBUG_NETPLAY_STEPS
|
|
RARCH_LOG("Received mode change %u self->spectating\n", netplay->self_player);
|
|
print_state(netplay);
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
else /* !YOU */
|
|
{
|
|
/* Somebody else is joining or parting */
|
|
if (mode & NETPLAY_CMD_MODE_BIT_PLAYING)
|
|
{
|
|
if (frame != netplay->server_frame_count)
|
|
{
|
|
RARCH_ERR("Received mode change out of order.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
netplay->connected_players |= (1<<player);
|
|
|
|
netplay->read_ptr[player] = netplay->server_ptr;
|
|
netplay->read_frame_count[player] = netplay->server_frame_count;
|
|
|
|
/* Announce it */
|
|
msg[sizeof(msg)-1] = '\0';
|
|
snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1);
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
#ifdef DEBUG_NETPLAY_STEPS
|
|
RARCH_LOG("Received mode change spectator->%u\n", player);
|
|
print_state(netplay);
|
|
#endif
|
|
|
|
}
|
|
else
|
|
{
|
|
netplay->connected_players &= ~(1<<player);
|
|
|
|
/* Announce it */
|
|
msg[sizeof(msg)-1] = '\0';
|
|
snprintf(msg, sizeof(msg)-1, "Player %d has left", player+1);
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
#ifdef DEBUG_NETPLAY_STEPS
|
|
RARCH_LOG("Received mode change %u->spectator\n", player);
|
|
print_state(netplay);
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
#undef START
|
|
#undef NEXT
|
|
}
|
|
|
|
case NETPLAY_CMD_MODE_REFUSED:
|
|
{
|
|
uint32_t reason;
|
|
const char *dmsg = NULL;
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_MODE_REFUSED from client.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
if (cmd_size != sizeof(uint32_t))
|
|
{
|
|
RARCH_ERR("Received invalid payload size for NETPLAY_CMD_MODE_REFUSED.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(&reason, sizeof(reason))
|
|
{
|
|
RARCH_ERR("Failed to receive NETPLAY_CMD_MODE_REFUSED payload.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
reason = ntohl(reason);
|
|
|
|
switch (reason)
|
|
{
|
|
case NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED:
|
|
dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED);
|
|
break;
|
|
|
|
case NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS:
|
|
dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS);
|
|
break;
|
|
|
|
default:
|
|
dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY);
|
|
}
|
|
|
|
if (dmsg)
|
|
{
|
|
RARCH_LOG("%s\n", dmsg);
|
|
runloop_msg_queue_push(dmsg, 1, 180, false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_DISCONNECT:
|
|
netplay_hangup(netplay, connection);
|
|
return true;
|
|
|
|
case NETPLAY_CMD_CRC:
|
|
{
|
|
uint32_t buffer[2];
|
|
size_t tmp_ptr = netplay->run_ptr;
|
|
bool found = false;
|
|
|
|
if (cmd_size != sizeof(buffer))
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(buffer, sizeof(buffer))
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
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].used
|
|
&& netplay->buffer[tmp_ptr].frame == buffer[0])
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
tmp_ptr = PREV_PTR(tmp_ptr);
|
|
} while (tmp_ptr != netplay->run_ptr);
|
|
|
|
if (!found)
|
|
{
|
|
/* Oh well, we got rid of it! */
|
|
break;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_REQUEST_SAVESTATE:
|
|
/* Delay until next frame so we don't send the savestate after the
|
|
* input */
|
|
netplay->force_send_savestate = true;
|
|
break;
|
|
|
|
case NETPLAY_CMD_LOAD_SAVESTATE:
|
|
case NETPLAY_CMD_RESET:
|
|
{
|
|
uint32_t frame;
|
|
uint32_t isize;
|
|
uint32_t rd, wn;
|
|
uint32_t player;
|
|
struct compression_transcoder *ctrans;
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* Only players may load states */
|
|
if (connection->mode != NETPLAY_CONNECTION_PLAYING &&
|
|
connection->mode != NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
RARCH_ERR("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 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
|
|
* 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. */
|
|
|
|
/* 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);
|
|
}
|
|
|
|
RECV(&frame, sizeof(frame))
|
|
{
|
|
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
frame = ntohl(frame);
|
|
|
|
if ((netplay->is_server && frame != netplay->read_frame_count[connection->player]) ||
|
|
(!netplay->is_server && frame != netplay->server_frame_count))
|
|
{
|
|
RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr[connection->player]], frame))
|
|
{
|
|
/* Hopefully it will be after another round of input */
|
|
goto shrt;
|
|
}
|
|
|
|
/* Now we switch based on whether we're loading a state or resetting */
|
|
if (cmd == NETPLAY_CMD_LOAD_SAVESTATE)
|
|
{
|
|
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);
|
|
}
|
|
|
|
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,
|
|
(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 */
|
|
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
|
|
* 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(netplay->read_ptr[connection->player]);
|
|
netplay->run_frame_count = frame - 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 (player = 0; player < MAX_USERS; player++)
|
|
{
|
|
if (!(netplay->connected_players & (1<<player))) continue;
|
|
if (frame > netplay->read_frame_count[player])
|
|
{
|
|
netplay->read_ptr[player] = netplay->read_ptr[connection->player];
|
|
netplay->read_frame_count[player] = frame;
|
|
}
|
|
}
|
|
|
|
/* Make sure our states are correct */
|
|
netplay->savestate_request_outstanding = false;
|
|
netplay->other_ptr = netplay->read_ptr[connection->player];
|
|
netplay->other_frame_count = frame;
|
|
|
|
#ifdef DEBUG_NETPLAY_STEPS
|
|
RARCH_LOG("Loading state at %u\n", frame);
|
|
print_state(netplay);
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_PAUSE:
|
|
{
|
|
char msg[512], nick[NETPLAY_NICK_LEN];
|
|
msg[sizeof(msg)-1] = '\0';
|
|
|
|
/* Read in the paused nick */
|
|
if (cmd_size != sizeof(nick))
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_PAUSE received invalid payload size.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
RECV(nick, sizeof(nick))
|
|
{
|
|
RARCH_ERR("Failed to receive paused nickname.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
nick[sizeof(nick)-1] = '\0';
|
|
|
|
/* We outright ignore pausing from spectators and slaves */
|
|
if (connection->mode != NETPLAY_CONNECTION_PLAYING)
|
|
break;
|
|
|
|
connection->paused = true;
|
|
netplay->remote_paused = true;
|
|
if (netplay->is_server)
|
|
{
|
|
snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), connection->nick);
|
|
netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE,
|
|
connection->nick, NETPLAY_NICK_LEN);
|
|
}
|
|
else
|
|
{
|
|
snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), nick);
|
|
}
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
break;
|
|
}
|
|
|
|
case NETPLAY_CMD_RESUME:
|
|
remote_unpaused(netplay, connection);
|
|
break;
|
|
|
|
case NETPLAY_CMD_STALL:
|
|
{
|
|
uint32_t frames;
|
|
|
|
if (cmd_size != sizeof(uint32_t))
|
|
{
|
|
RARCH_ERR("NETPLAY_CMD_STALL with incorrect payload size.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
RECV(&frames, sizeof(frames))
|
|
{
|
|
RARCH_ERR("Failed to receive NETPLAY_CMD_STALL payload.\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
frames = ntohl(frames);
|
|
if (frames > NETPLAY_MAX_REQ_STALL_TIME)
|
|
frames = NETPLAY_MAX_REQ_STALL_TIME;
|
|
|
|
if (netplay->is_server)
|
|
{
|
|
/* Only servers can request a stall! */
|
|
RARCH_ERR("Netplay client requested a stall?\n");
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
/* We can only stall for one reason at a time */
|
|
if (!netplay->stall)
|
|
{
|
|
connection->stall = netplay->stall = NETPLAY_STALL_SERVER_REQUESTED;
|
|
netplay->stall_time = 0;
|
|
connection->stall_frame = frames;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED));
|
|
return netplay_cmd_nak(netplay, connection);
|
|
}
|
|
|
|
netplay_recv_flush(&connection->recv_packet_buffer);
|
|
netplay->timeout_cnt = 0;
|
|
if (had_input)
|
|
*had_input = true;
|
|
return true;
|
|
|
|
shrt:
|
|
/* No more data, reset and try again */
|
|
netplay_recv_reset(&connection->recv_packet_buffer);
|
|
return true;
|
|
|
|
#undef RECV
|
|
}
|
|
|
|
/**
|
|
* netplay_poll_net_input
|
|
*
|
|
* Poll input from the network
|
|
*/
|
|
int netplay_poll_net_input(netplay_t *netplay, bool block)
|
|
{
|
|
bool had_input = false;
|
|
int max_fd = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *connection = &netplay->connections[i];
|
|
if (connection->active && connection->fd >= max_fd)
|
|
max_fd = connection->fd + 1;
|
|
}
|
|
|
|
if (max_fd == 0)
|
|
return 0;
|
|
|
|
netplay->timeout_cnt = 0;
|
|
|
|
do
|
|
{
|
|
had_input = false;
|
|
|
|
netplay->timeout_cnt++;
|
|
|
|
/* Read input from each connection */
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *connection = &netplay->connections[i];
|
|
if (connection->active && !netplay_get_cmd(netplay, connection, &had_input))
|
|
netplay_hangup(netplay, connection);
|
|
}
|
|
|
|
if (block)
|
|
{
|
|
netplay_update_unread_ptr(netplay);
|
|
|
|
/* If we were blocked for input, pass if we have this frame's input */
|
|
if (netplay->unread_frame_count > netplay->run_frame_count)
|
|
break;
|
|
|
|
/* If we're supposed to block but we didn't have enough input, wait for it */
|
|
if (!had_input)
|
|
{
|
|
fd_set fds;
|
|
struct timeval tv = {0};
|
|
tv.tv_usec = RETRY_MS * 1000;
|
|
|
|
FD_ZERO(&fds);
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *connection = &netplay->connections[i];
|
|
if (connection->active)
|
|
FD_SET(connection->fd, &fds);
|
|
}
|
|
|
|
if (socket_select(max_fd, &fds, NULL, NULL, &tv) < 0)
|
|
return -1;
|
|
|
|
RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n",
|
|
netplay->run_frame_count, netplay->timeout_cnt, MAX_RETRIES);
|
|
|
|
if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused)
|
|
return -1;
|
|
}
|
|
}
|
|
} while (had_input || block);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* netplay_handle_slaves
|
|
*
|
|
* Handle any slave connections
|
|
*/
|
|
void netplay_handle_slaves(netplay_t *netplay)
|
|
{
|
|
struct delta_frame *frame = &netplay->buffer[netplay->self_ptr];
|
|
size_t i;
|
|
for (i = 0; i < netplay->connections_size; i++)
|
|
{
|
|
struct netplay_connection *connection = &netplay->connections[i];
|
|
if (connection->active &&
|
|
connection->mode == NETPLAY_CONNECTION_SLAVE)
|
|
{
|
|
int player = connection->player;
|
|
|
|
/* This is a slave connection. First, should we do anything at all? If
|
|
* we've already "read" this data, then we can just ignore it */
|
|
if (netplay->read_frame_count[player] > netplay->self_frame_count)
|
|
continue;
|
|
|
|
/* Alright, we have to send something. Do we need to generate it first? */
|
|
if (!frame->have_real[player])
|
|
{
|
|
/* Copy the previous frame's data */
|
|
memcpy(frame->real_input_state[player],
|
|
netplay->buffer[PREV_PTR(netplay->self_ptr)].real_input_state[player],
|
|
WORDS_PER_INPUT*sizeof(uint32_t));
|
|
frame->have_real[player] = true;
|
|
}
|
|
|
|
/* Send it along */
|
|
send_input_frame(netplay, NULL, NULL, netplay->self_frame_count,
|
|
player, frame->real_input_state[player]);
|
|
|
|
/* And mark it as "read" */
|
|
netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr);
|
|
netplay->read_frame_count[player] = netplay->self_frame_count + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* netplay_flip_port
|
|
*
|
|
* Should we flip ports 0 and 1?
|
|
*/
|
|
bool netplay_flip_port(netplay_t *netplay)
|
|
{
|
|
size_t frame = netplay->self_frame_count;
|
|
|
|
if (netplay->flip_frame == 0)
|
|
return false;
|
|
|
|
if (netplay->is_replay)
|
|
frame = netplay->replay_frame_count;
|
|
|
|
return netplay->flip ^ (frame < netplay->flip_frame);
|
|
}
|
|
|
|
/**
|
|
* netplay_announce_nat_traversal
|
|
*
|
|
* Announce successful NAT traversal.
|
|
*/
|
|
void netplay_announce_nat_traversal(netplay_t *netplay)
|
|
{
|
|
#ifndef HAVE_SOCKET_LEGACY
|
|
char msg[512], host[PATH_MAX_LENGTH], port[6];
|
|
|
|
if (netplay->nat_traversal_state.have_inet4)
|
|
{
|
|
if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr,
|
|
sizeof(struct sockaddr_in),
|
|
host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0)
|
|
return;
|
|
|
|
}
|
|
#ifdef HAVE_INET6
|
|
else if (netplay->nat_traversal_state.have_inet6)
|
|
{
|
|
if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr,
|
|
sizeof(struct sockaddr_in6),
|
|
host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0)
|
|
return;
|
|
|
|
}
|
|
#endif
|
|
else
|
|
return;
|
|
|
|
snprintf(msg, sizeof(msg), "%s: %s:%s\n",
|
|
msg_hash_to_str(MSG_PUBLIC_ADDRESS),
|
|
host, port);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
RARCH_LOG("%s\n", msg);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* netplay_init_nat_traversal
|
|
*
|
|
* Initialize the NAT traversal library and try to open a port
|
|
*/
|
|
void netplay_init_nat_traversal(netplay_t *netplay)
|
|
{
|
|
memset(&netplay->nat_traversal_state, 0, sizeof(netplay->nat_traversal_state));
|
|
netplay->nat_traversal_task_oustanding = true;
|
|
task_push_netplay_nat_traversal(&netplay->nat_traversal_state, netplay->tcp_port);
|
|
}
|