2015-12-23 13:25:28 -07:00
|
|
|
/* RetroArch - A frontend for libretro.
|
|
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
2016-01-10 04:06:50 +01:00
|
|
|
* Copyright (C) 2011-2016 - Daniel De Matteis
|
2016-09-13 17:06:23 -04:00
|
|
|
* Copyright (C) 2016 - Gregor Richards
|
2015-12-23 13:25:28 -07:00
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
2016-09-06 00:56:00 +02:00
|
|
|
#include <compat/strl.h>
|
|
|
|
|
2015-12-23 13:25:28 -07:00
|
|
|
#include "netplay_private.h"
|
2016-05-01 23:37:49 +02:00
|
|
|
#include <net/net_socket.h>
|
2016-12-13 17:05:07 -05:00
|
|
|
#include <rhash.h>
|
2016-12-03 09:58:16 -05:00
|
|
|
#include <compat/strl.h>
|
2016-05-19 11:46:54 +02:00
|
|
|
|
2016-09-21 12:31:40 +02:00
|
|
|
#include <encodings/crc32.h>
|
2016-09-14 23:19:47 -04:00
|
|
|
|
2016-09-03 07:45:51 +02:00
|
|
|
#include "../../movie.h"
|
2016-06-29 03:06:15 +02:00
|
|
|
#include "../../msg_hash.h"
|
2016-12-12 23:07:46 -05:00
|
|
|
#include "../../configuration.h"
|
2016-05-19 11:46:54 +02:00
|
|
|
#include "../../content.h"
|
2016-12-13 15:26:20 -05:00
|
|
|
#include "../../retroarch.h"
|
2016-09-03 07:51:11 +02:00
|
|
|
#include "../../runloop.h"
|
2016-09-03 07:45:51 +02:00
|
|
|
#include "../../version.h"
|
2016-12-13 15:26:20 -05:00
|
|
|
#include "../../menu/widgets/menu_input_dialog.h"
|
2016-01-22 16:09:48 +01:00
|
|
|
|
2015-12-23 13:25:28 -07:00
|
|
|
/**
|
2016-05-12 12:03:43 +02:00
|
|
|
* netplay_impl_magic:
|
2015-12-23 13:25:28 -07:00
|
|
|
*
|
|
|
|
* Not really a hash, but should be enough to differentiate
|
|
|
|
* implementations from each other.
|
|
|
|
*
|
|
|
|
* Subtle differences in the implementation will not be possible to spot.
|
|
|
|
* The alternative would have been checking serialization sizes, but it
|
|
|
|
* was troublesome for cross platform compat.
|
|
|
|
**/
|
2016-05-12 12:03:43 +02:00
|
|
|
uint32_t netplay_impl_magic(void)
|
2015-12-23 13:25:28 -07:00
|
|
|
{
|
|
|
|
size_t i, len;
|
2016-01-28 04:26:17 +01:00
|
|
|
retro_ctx_api_info_t api_info;
|
|
|
|
unsigned api;
|
2015-12-23 13:25:28 -07:00
|
|
|
uint32_t res = 0;
|
2016-09-06 08:38:26 +02:00
|
|
|
rarch_system_info_t *info = NULL;
|
2015-12-23 13:25:28 -07:00
|
|
|
const char *lib = NULL;
|
|
|
|
const char *ver = PACKAGE_VERSION;
|
2016-01-28 04:26:17 +01:00
|
|
|
|
2016-05-08 01:33:57 +02:00
|
|
|
core_api_version(&api_info);
|
2016-01-28 04:26:17 +01:00
|
|
|
|
|
|
|
api = api_info.version;
|
2016-09-13 17:05:28 -04:00
|
|
|
|
2016-09-06 08:38:26 +02:00
|
|
|
runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
|
2016-09-13 17:05:28 -04:00
|
|
|
|
2016-09-24 18:48:55 -04:00
|
|
|
res |= api;
|
|
|
|
|
2015-12-23 13:25:28 -07:00
|
|
|
if (info)
|
2016-09-24 18:48:55 -04:00
|
|
|
{
|
2015-12-23 13:25:28 -07:00
|
|
|
lib = info->info.library_name;
|
|
|
|
|
2016-09-24 18:48:55 -04:00
|
|
|
len = strlen(lib);
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
res ^= lib[i] << (i & 0xf);
|
2015-12-23 13:25:28 -07:00
|
|
|
|
2016-09-24 18:48:55 -04:00
|
|
|
lib = info->info.library_version;
|
|
|
|
len = strlen(lib);
|
2015-12-23 13:25:28 -07:00
|
|
|
|
2016-09-24 18:48:55 -04:00
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
res ^= lib[i] << (i & 0xf);
|
|
|
|
}
|
2015-12-23 13:25:28 -07:00
|
|
|
|
|
|
|
len = strlen(ver);
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
res ^= ver[i] << ((i & 0xf) + 16);
|
|
|
|
|
Multitudinous fixes and updates to Netplay. Had to be one commit since
they're mostly related:
(1) Renamed frame_count to self_frame_count to be consistent with all
other names.
(2) Previously, it was possible to overwrite data in the ring buffer
that hadn't yet been used. Now that's not possible, but that just
changes one breakage for another: It's now possible to miss the NEW
data. The final resolution for this will probably be requesting stalls.
This is accomplished simply by storing frame numbers in the ring buffer
and checking them against the 'other' head.
(3) In TCP packets, separated cmd_size from cmd. It was beyond pointless
for these to be combined, and restricted cmd_size to 16 bits, which
will probably fail when/if state loading is supported.
(4) Readahead is now allowed. In the past, if the peer got ahead of us,
we would simply ignore their data. Thus, if they got too far ahead of
us, we'd stop reading their data altogether. Fabulous. Now, we're happy
to read future input.
(5) If the peer gets too far ahead of us (currently an unconfigurable 10
frames), fast forward to catch up. This should prevent desync due to
clock drift or stutter.
(6) Used frame_count in a few places where ptr was used. Doing a
comparison of pointers on a ring buffer is a far more dangerous way to
assure we're done with a task than simply using the count, since the
ring buffer is... well, a ring.
(7) Renamed tmp_{ptr,frame_count} to replay_{ptr,frame_count} for
clarity.
(8) Slightly changed the protocol version hash, just to assure that
other clients wouldn't think they were compatible with this one.
(9) There was an off-by-one error which, under some circumstances, could
allow the replay engine to run a complete round through the ring buffer,
replaying stale data. Fixed.
2016-09-11 22:01:47 -04:00
|
|
|
res ^= NETPLAY_PROTOCOL_VERSION << 24;
|
|
|
|
|
2015-12-23 13:25:28 -07:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2016-10-05 21:12:42 -04:00
|
|
|
/**
|
|
|
|
* netplay_platform_magic
|
|
|
|
*
|
|
|
|
* Just enough info to tell us if our platforms mismatch: Endianness and a
|
|
|
|
* couple of type sizes.
|
|
|
|
*
|
|
|
|
* Format:
|
|
|
|
* bit 31: Reserved
|
|
|
|
* bit 30: 1 for big endian
|
|
|
|
* bits 29-15: sizeof(size_t)
|
|
|
|
* bits 14-0: sizeof(long)
|
|
|
|
*/
|
|
|
|
static uint32_t netplay_platform_magic(void)
|
|
|
|
{
|
|
|
|
uint32_t ret =
|
|
|
|
((1 == htonl(1)) << 30)
|
|
|
|
|(sizeof(size_t) << 15)
|
|
|
|
|(sizeof(long));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* netplay_endian_mismatch
|
|
|
|
*
|
|
|
|
* Do the platform magics mismatch on endianness?
|
|
|
|
*/
|
|
|
|
static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb)
|
|
|
|
{
|
|
|
|
uint32_t ebit = (1<<30);
|
|
|
|
return (pma & ebit) != (pmb & ebit);
|
|
|
|
}
|
|
|
|
|
2016-12-05 00:04:01 -05:00
|
|
|
bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection)
|
2016-12-03 15:17:38 -05:00
|
|
|
{
|
|
|
|
uint32_t *content_crc_ptr = NULL;
|
2016-12-13 15:26:20 -05:00
|
|
|
uint32_t header[5] = {0};
|
2016-12-03 15:17:38 -05:00
|
|
|
|
|
|
|
content_get_crc(&content_crc_ptr);
|
|
|
|
|
|
|
|
header[0] = htonl(netplay_impl_magic());
|
|
|
|
header[1] = htonl(*content_crc_ptr);
|
|
|
|
header[2] = htonl(netplay_platform_magic());
|
|
|
|
header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED);
|
2016-12-13 17:05:07 -05:00
|
|
|
if (netplay->is_server && netplay->password[0])
|
|
|
|
{
|
|
|
|
/* Demand a password */
|
|
|
|
/* FIXME: Better randomness, or at least seed it */
|
|
|
|
connection->salt = rand();
|
|
|
|
if (connection->salt == 0) connection->salt = 1;
|
|
|
|
header[4] = htonl(connection->salt);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
header[4] = htonl(0);
|
|
|
|
}
|
2016-12-03 15:17:38 -05:00
|
|
|
|
2016-12-09 14:14:54 -05:00
|
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd, header,
|
2016-12-03 15:17:38 -05:00
|
|
|
sizeof(header)) ||
|
2016-12-09 14:14:54 -05:00
|
|
|
!netplay_send_flush(&connection->send_packet_buffer, connection->fd, false))
|
2016-12-03 15:17:38 -05:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct nick_buf_s
|
|
|
|
{
|
|
|
|
uint32_t cmd[2];
|
|
|
|
char nick[32];
|
|
|
|
};
|
|
|
|
|
2016-12-13 15:26:20 -05:00
|
|
|
struct password_buf_s
|
|
|
|
{
|
|
|
|
uint32_t cmd[2];
|
2016-12-13 17:05:07 -05:00
|
|
|
char password[64];
|
2016-12-13 15:26:20 -05:00
|
|
|
};
|
|
|
|
|
2016-12-03 15:17:38 -05:00
|
|
|
#define RECV(buf, sz) \
|
2016-12-09 14:14:54 -05:00
|
|
|
recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \
|
2016-12-03 15:17:38 -05:00
|
|
|
if (recvd >= 0 && recvd < (sz)) \
|
|
|
|
{ \
|
2016-12-09 14:14:54 -05:00
|
|
|
netplay_recv_reset(&connection->recv_packet_buffer); \
|
2016-12-03 15:17:38 -05:00
|
|
|
return true; \
|
|
|
|
} \
|
|
|
|
else if (recvd < 0)
|
|
|
|
|
2016-12-13 15:26:20 -05:00
|
|
|
static netplay_t *handshake_password_netplay;
|
|
|
|
|
|
|
|
static void handshake_password(void *ignore, const char *line)
|
|
|
|
{
|
2016-12-13 17:05:07 -05:00
|
|
|
struct password_buf_s password_buf;
|
|
|
|
char password[8+128]; /* 8 for salt, 128 for password */
|
2016-12-13 15:26:20 -05:00
|
|
|
uint32_t cmd[2];
|
|
|
|
netplay_t *netplay = handshake_password_netplay;
|
|
|
|
struct netplay_connection *connection = &netplay->connections[0];
|
|
|
|
|
2016-12-13 17:05:07 -05:00
|
|
|
snprintf(password, sizeof(password), "%08X", connection->salt);
|
|
|
|
strlcpy(password + 8, line, sizeof(password)-8);
|
|
|
|
|
|
|
|
password_buf.cmd[0] = htonl(NETPLAY_CMD_PASSWORD);
|
|
|
|
password_buf.cmd[1] = htonl(sizeof(password_buf.password));
|
|
|
|
sha256_hash(password_buf.password, (uint8_t *) password, strlen(password));
|
2016-12-13 15:26:20 -05:00
|
|
|
|
2016-12-13 17:05:07 -05:00
|
|
|
netplay_send(&connection->send_packet_buffer, connection->fd, &password_buf, sizeof(password_buf)) &&
|
2016-12-13 15:26:20 -05:00
|
|
|
netplay_send_flush(&connection->send_packet_buffer, connection->fd, false);
|
|
|
|
|
|
|
|
menu_input_dialog_end();
|
|
|
|
rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL);
|
|
|
|
}
|
|
|
|
|
2016-12-05 00:04:01 -05:00
|
|
|
bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input)
|
2016-12-03 15:17:38 -05:00
|
|
|
{
|
2016-12-13 15:26:20 -05:00
|
|
|
uint32_t header[5] = {0};
|
2016-12-03 15:17:38 -05:00
|
|
|
ssize_t recvd;
|
|
|
|
char msg[512];
|
|
|
|
struct nick_buf_s nick_buf;
|
|
|
|
uint32_t *content_crc_ptr = NULL;
|
|
|
|
uint32_t local_pmagic, remote_pmagic;
|
|
|
|
uint32_t compression;
|
|
|
|
|
|
|
|
msg[0] = '\0';
|
|
|
|
|
|
|
|
RECV(header, sizeof(header))
|
|
|
|
{
|
|
|
|
strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT), sizeof(msg));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (netplay_impl_magic() != ntohl(header[0]))
|
|
|
|
{
|
|
|
|
strlcpy(msg, "Implementations differ. Make sure you're using exact same "
|
|
|
|
"libretro implementations and RetroArch version.", sizeof(msg));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
content_get_crc(&content_crc_ptr);
|
|
|
|
if (*content_crc_ptr != ntohl(header[1]))
|
|
|
|
{
|
|
|
|
strlcpy(msg, msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER), sizeof(msg));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We only care about platform magic if our core is quirky */
|
|
|
|
local_pmagic = netplay_platform_magic();
|
|
|
|
remote_pmagic = ntohl(header[2]);
|
|
|
|
if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) &&
|
|
|
|
netplay_endian_mismatch(local_pmagic, remote_pmagic))
|
|
|
|
{
|
|
|
|
RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n");
|
|
|
|
strlcpy(msg, "This core does not support inter-architecture netplay "
|
|
|
|
"between these systems.", sizeof(msg));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) &&
|
|
|
|
(local_pmagic != remote_pmagic))
|
|
|
|
{
|
|
|
|
RARCH_ERR("Platform mismatch with a platform-sensitive core.\n");
|
|
|
|
strlcpy(msg, "This core does not support inter-architecture netplay.",
|
|
|
|
sizeof(msg));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear any existing compression */
|
|
|
|
if (netplay->compression_stream)
|
|
|
|
netplay->compression_backend->stream_free(netplay->compression_stream);
|
|
|
|
if (netplay->decompression_stream)
|
|
|
|
netplay->decompression_backend->stream_free(netplay->decompression_stream);
|
|
|
|
|
|
|
|
/* Check what compression is supported */
|
|
|
|
compression = ntohl(header[3]);
|
|
|
|
compression &= NETPLAY_COMPRESSION_SUPPORTED;
|
|
|
|
if (compression & NETPLAY_COMPRESSION_ZLIB)
|
|
|
|
{
|
|
|
|
netplay->compression_backend = trans_stream_get_zlib_deflate_backend();
|
|
|
|
if (!netplay->compression_backend)
|
|
|
|
netplay->compression_backend = trans_stream_get_pipe_backend();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
netplay->compression_backend = trans_stream_get_pipe_backend();
|
|
|
|
}
|
|
|
|
netplay->decompression_backend = netplay->compression_backend->reverse;
|
|
|
|
|
|
|
|
/* Allocate our compression stream */
|
|
|
|
netplay->compression_stream = netplay->compression_backend->stream_new();
|
|
|
|
netplay->decompression_stream = netplay->decompression_backend->stream_new();
|
|
|
|
if (!netplay->compression_stream || !netplay->decompression_stream)
|
|
|
|
{
|
|
|
|
RARCH_ERR("Failed to allocate compression transcoder!\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-13 15:26:20 -05:00
|
|
|
/* If a password is demanded, ask for it */
|
2016-12-13 17:05:07 -05:00
|
|
|
if (!netplay->is_server && (connection->salt = ntohl(header[4])))
|
2016-12-13 15:26:20 -05:00
|
|
|
{
|
|
|
|
menu_input_ctx_line_t line;
|
|
|
|
rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL);
|
|
|
|
memset(&line, 0, sizeof(line));
|
|
|
|
handshake_password_netplay = netplay;
|
|
|
|
line.label = "Enter Netplay server password:";
|
|
|
|
line.label_setting = "no_setting";
|
|
|
|
line.cb = handshake_password;
|
|
|
|
menu_input_dialog_start(&line);
|
|
|
|
}
|
|
|
|
|
2016-12-03 15:17:38 -05:00
|
|
|
/* Send our nick */
|
|
|
|
nick_buf.cmd[0] = htonl(NETPLAY_CMD_NICK);
|
|
|
|
nick_buf.cmd[1] = htonl(sizeof(nick_buf.nick));
|
|
|
|
memset(nick_buf.nick, 0, sizeof(nick_buf.nick));
|
|
|
|
strlcpy(nick_buf.nick, netplay->nick, sizeof(nick_buf.nick));
|
2016-12-09 14:14:54 -05:00
|
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd, &nick_buf,
|
2016-12-03 15:17:38 -05:00
|
|
|
sizeof(nick_buf)) ||
|
2016-12-09 14:14:54 -05:00
|
|
|
!netplay_send_flush(&connection->send_packet_buffer, connection->fd, false))
|
2016-12-03 15:17:38 -05:00
|
|
|
return false;
|
|
|
|
|
|
|
|
/* Move on to the next mode */
|
2016-12-05 00:04:01 -05:00
|
|
|
connection->mode = NETPLAY_CONNECTION_PRE_NICK;
|
2016-12-03 15:17:38 -05:00
|
|
|
*had_input = true;
|
2016-12-09 14:14:54 -05:00
|
|
|
netplay_recv_flush(&connection->recv_packet_buffer);
|
2016-12-03 15:17:38 -05:00
|
|
|
return true;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (msg[0])
|
|
|
|
{
|
|
|
|
RARCH_ERR("%s\n", msg);
|
|
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-05 00:04:01 -05:00
|
|
|
static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connection *connection)
|
2016-12-03 15:17:38 -05:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
char msg[512];
|
|
|
|
|
|
|
|
if (netplay->is_server)
|
|
|
|
{
|
2016-12-12 21:57:14 -05:00
|
|
|
netplay_log_connection(&connection->addr, connection - netplay->connections, connection->nick);
|
2016-12-03 15:17:38 -05:00
|
|
|
|
|
|
|
/* Send them the savestate */
|
|
|
|
if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)))
|
|
|
|
{
|
2016-12-12 19:34:50 -05:00
|
|
|
netplay->force_send_savestate = true;
|
2016-12-03 15:17:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
snprintf(msg, sizeof(msg), "%s: \"%s\"",
|
|
|
|
msg_hash_to_str(MSG_CONNECTED_TO),
|
2016-12-09 22:02:31 -05:00
|
|
|
connection->nick);
|
2016-12-03 15:17:38 -05:00
|
|
|
RARCH_LOG("%s\n", msg);
|
|
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
}
|
|
|
|
|
2016-12-03 15:25:37 -05:00
|
|
|
/* Unstall if we were waiting for this */
|
2016-12-09 13:32:04 -05:00
|
|
|
if (netplay->stall == NETPLAY_STALL_NO_CONNECTION)
|
2016-12-03 15:25:37 -05:00
|
|
|
netplay->stall = 0;
|
2016-12-03 15:17:38 -05:00
|
|
|
}
|
|
|
|
|
2016-12-13 15:26:20 -05:00
|
|
|
bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *connection)
|
|
|
|
{
|
|
|
|
/* If we're the server, now we send sync info */
|
|
|
|
uint32_t cmd[5];
|
|
|
|
uint32_t connected_players;
|
|
|
|
settings_t *settings = config_get_ptr();
|
|
|
|
size_t i;
|
|
|
|
uint32_t device;
|
|
|
|
retro_ctx_memory_info_t mem_info;
|
|
|
|
|
|
|
|
mem_info.id = RETRO_MEMORY_SAVE_RAM;
|
|
|
|
core_get_memory(&mem_info);
|
|
|
|
|
|
|
|
/* Send basic sync info */
|
|
|
|
cmd[0] = htonl(NETPLAY_CMD_SYNC);
|
|
|
|
cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + mem_info.size);
|
|
|
|
cmd[2] = htonl(netplay->self_frame_count);
|
|
|
|
connected_players = netplay->connected_players;
|
|
|
|
if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING)
|
|
|
|
connected_players |= 1<<netplay->self_player;
|
|
|
|
cmd[3] = htonl(connected_players);
|
|
|
|
if (netplay->flip)
|
|
|
|
cmd[4] = htonl(netplay->flip_frame);
|
|
|
|
else
|
|
|
|
cmd[4] = htonl(0);
|
|
|
|
|
|
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd,
|
|
|
|
sizeof(cmd)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* Now send the device info */
|
|
|
|
for (i = 0; i < MAX_USERS; i++)
|
|
|
|
{
|
|
|
|
device = htonl(settings->input.libretro_device[i]);
|
|
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd,
|
|
|
|
&device, sizeof(device)))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* And finally, the SRAM */
|
|
|
|
if (!netplay_send(&connection->send_packet_buffer, connection->fd,
|
|
|
|
mem_info.data, mem_info.size) ||
|
|
|
|
!netplay_send_flush(&connection->send_packet_buffer, connection->fd,
|
|
|
|
false))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* Now we're ready! */
|
|
|
|
connection->mode = NETPLAY_CONNECTION_SPECTATING;
|
|
|
|
netplay_handshake_ready(netplay, connection);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-12-05 00:04:01 -05:00
|
|
|
bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input)
|
2016-12-03 15:17:38 -05:00
|
|
|
{
|
|
|
|
struct nick_buf_s nick_buf;
|
|
|
|
ssize_t recvd;
|
|
|
|
char msg[512];
|
|
|
|
|
|
|
|
msg[0] = '\0';
|
|
|
|
|
|
|
|
RECV(&nick_buf, sizeof(nick_buf));
|
|
|
|
|
|
|
|
/* Expecting only a nick command */
|
|
|
|
if (recvd < 0 ||
|
|
|
|
ntohl(nick_buf.cmd[0]) != NETPLAY_CMD_NICK ||
|
|
|
|
ntohl(nick_buf.cmd[1]) != sizeof(nick_buf.nick))
|
|
|
|
{
|
|
|
|
if (netplay->is_server)
|
|
|
|
strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT),
|
|
|
|
sizeof(msg));
|
|
|
|
else
|
|
|
|
strlcpy(msg,
|
|
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST),
|
|
|
|
sizeof(msg));
|
|
|
|
RARCH_ERR("%s\n", msg);
|
|
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-09 22:02:31 -05:00
|
|
|
strlcpy(connection->nick, nick_buf.nick,
|
|
|
|
(sizeof(connection->nick) < sizeof(nick_buf.nick)) ?
|
|
|
|
sizeof(connection->nick) : sizeof(nick_buf.nick));
|
2016-12-03 15:17:38 -05:00
|
|
|
|
|
|
|
if (netplay->is_server)
|
|
|
|
{
|
2016-12-13 15:26:20 -05:00
|
|
|
if (netplay->password[0])
|
|
|
|
{
|
|
|
|
/* There's a password, so just put them in PRE_PASSWORD mode */
|
|
|
|
connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD;
|
|
|
|
}
|
2016-12-11 21:34:17 -05:00
|
|
|
else
|
2016-12-12 23:07:46 -05:00
|
|
|
{
|
2016-12-13 15:26:20 -05:00
|
|
|
if (!netplay_handshake_sync(netplay, connection))
|
2016-12-12 23:07:46 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-03 15:17:38 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-12-03 18:53:57 -05:00
|
|
|
/* Client needs to wait for sync info */
|
2016-12-05 00:04:01 -05:00
|
|
|
connection->mode = NETPLAY_CONNECTION_PRE_SYNC;
|
2016-12-03 15:17:38 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*had_input = true;
|
2016-12-09 14:14:54 -05:00
|
|
|
netplay_recv_flush(&connection->recv_packet_buffer);
|
2016-12-03 15:17:38 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-12-13 15:26:20 -05:00
|
|
|
bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input)
|
|
|
|
{
|
2016-12-13 17:05:07 -05:00
|
|
|
struct password_buf_s password_buf, corr_password_buf;
|
|
|
|
char password[8+128]; /* 8 for salt, 128 for password */
|
2016-12-13 15:26:20 -05:00
|
|
|
ssize_t recvd;
|
|
|
|
char msg[512];
|
|
|
|
|
|
|
|
msg[0] = '\0';
|
|
|
|
|
|
|
|
RECV(&password_buf, sizeof(password_buf));
|
|
|
|
|
2016-12-13 17:05:07 -05:00
|
|
|
/* Expecting only a password command */
|
2016-12-13 15:26:20 -05:00
|
|
|
if (recvd < 0 ||
|
|
|
|
ntohl(password_buf.cmd[0]) != NETPLAY_CMD_PASSWORD ||
|
|
|
|
ntohl(password_buf.cmd[1]) != sizeof(password_buf.password))
|
|
|
|
{
|
|
|
|
if (netplay->is_server)
|
|
|
|
strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT),
|
|
|
|
sizeof(msg));
|
|
|
|
else
|
|
|
|
strlcpy(msg,
|
|
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST),
|
|
|
|
sizeof(msg));
|
|
|
|
RARCH_ERR("%s\n", msg);
|
|
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-13 17:05:07 -05:00
|
|
|
/* Calculate the correct password */
|
|
|
|
snprintf(password, sizeof(password), "%08X", connection->salt);
|
|
|
|
strlcpy(password + 8, netplay->password, sizeof(password)-8);
|
|
|
|
sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password));
|
|
|
|
|
|
|
|
/* Compare them */
|
|
|
|
if (memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password)))
|
2016-12-13 15:26:20 -05:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!netplay_handshake_sync(netplay, connection))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*had_input = true;
|
|
|
|
netplay_recv_flush(&connection->recv_packet_buffer);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-12-05 00:04:01 -05:00
|
|
|
bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input)
|
2016-12-03 15:17:38 -05:00
|
|
|
{
|
|
|
|
uint32_t cmd[2];
|
2016-12-11 21:34:17 -05:00
|
|
|
uint32_t new_frame_count, connected_players, flip_frame;
|
2016-12-12 23:07:46 -05:00
|
|
|
uint32_t device;
|
2016-12-03 15:17:38 -05:00
|
|
|
uint32_t local_sram_size, remote_sram_size;
|
2016-12-03 18:53:57 -05:00
|
|
|
size_t i;
|
2016-12-03 15:17:38 -05:00
|
|
|
ssize_t recvd;
|
2016-12-12 23:07:46 -05:00
|
|
|
settings_t *settings = config_get_ptr();
|
|
|
|
retro_ctx_controller_info_t pad;
|
2016-12-03 15:17:38 -05:00
|
|
|
retro_ctx_memory_info_t mem_info;
|
|
|
|
|
|
|
|
RECV(cmd, sizeof(cmd))
|
|
|
|
return false;
|
|
|
|
|
2016-12-03 18:53:57 -05:00
|
|
|
/* Only expecting a sync command */
|
|
|
|
if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC ||
|
2016-12-12 23:07:46 -05:00
|
|
|
ntohl(cmd[1]) < 3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t))
|
2016-12-03 15:17:38 -05:00
|
|
|
{
|
|
|
|
RARCH_ERR("%s\n",
|
|
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-03 18:53:57 -05:00
|
|
|
/* Get the frame count */
|
|
|
|
RECV(&new_frame_count, sizeof(new_frame_count))
|
|
|
|
return false;
|
|
|
|
new_frame_count = ntohl(new_frame_count);
|
|
|
|
|
2016-12-11 21:34:17 -05:00
|
|
|
/* Get the connected players */
|
|
|
|
RECV(&connected_players, sizeof(connected_players))
|
|
|
|
return false;
|
|
|
|
connected_players = ntohl(connected_players);
|
|
|
|
netplay->connected_players = connected_players;
|
|
|
|
|
|
|
|
/* And the flip state */
|
|
|
|
RECV(&flip_frame, sizeof(flip_frame))
|
|
|
|
return false;
|
|
|
|
flip_frame = ntohl(flip_frame);
|
|
|
|
netplay->flip = !!flip_frame;
|
|
|
|
netplay->flip_frame = flip_frame;
|
|
|
|
|
|
|
|
/* Set our frame counters as requested */
|
2016-12-03 18:53:57 -05:00
|
|
|
netplay->self_frame_count = netplay->other_frame_count =
|
2016-12-10 20:36:57 -05:00
|
|
|
netplay->unread_frame_count = netplay->server_frame_count =
|
2016-12-11 21:34:17 -05:00
|
|
|
new_frame_count;
|
2016-12-03 18:53:57 -05:00
|
|
|
for (i = 0; i < netplay->buffer_size; i++)
|
|
|
|
{
|
2016-12-03 20:20:16 -05:00
|
|
|
struct delta_frame *ptr = &netplay->buffer[i];
|
|
|
|
ptr->used = false;
|
|
|
|
|
2016-12-03 18:53:57 -05:00
|
|
|
if (i == netplay->self_ptr)
|
|
|
|
{
|
2016-12-03 20:20:16 -05:00
|
|
|
/* Clear out any current data but still use this frame */
|
|
|
|
netplay_delta_frame_ready(netplay, ptr, 0);
|
2016-12-03 18:53:57 -05:00
|
|
|
ptr->frame = new_frame_count;
|
2016-12-03 20:20:16 -05:00
|
|
|
ptr->have_local = true;
|
2016-12-11 21:34:17 -05:00
|
|
|
netplay->other_ptr = netplay->unread_ptr = netplay->server_ptr = i;
|
2016-12-03 20:20:16 -05:00
|
|
|
|
2016-12-03 18:53:57 -05:00
|
|
|
}
|
|
|
|
}
|
2016-12-11 21:34:17 -05:00
|
|
|
for (i = 0; i < MAX_USERS; i++)
|
|
|
|
{
|
|
|
|
if (connected_players & (1<<i))
|
|
|
|
{
|
|
|
|
netplay->read_ptr[i] = netplay->self_ptr;
|
|
|
|
netplay->read_frame_count[i] = netplay->self_frame_count;
|
|
|
|
}
|
|
|
|
}
|
2016-12-03 18:53:57 -05:00
|
|
|
|
2016-12-12 23:07:46 -05:00
|
|
|
/* Get and set each pad */
|
|
|
|
for (i = 0; i < MAX_USERS; i++)
|
|
|
|
{
|
|
|
|
RECV(&device, sizeof(device))
|
|
|
|
return false;
|
|
|
|
pad.port = i;
|
|
|
|
pad.device = ntohl(device);
|
|
|
|
core_set_controller_port_device(&pad);
|
|
|
|
}
|
|
|
|
|
2016-12-03 18:53:57 -05:00
|
|
|
/* Now check the SRAM */
|
2016-12-03 15:17:38 -05:00
|
|
|
mem_info.id = RETRO_MEMORY_SAVE_RAM;
|
|
|
|
core_get_memory(&mem_info);
|
|
|
|
|
|
|
|
local_sram_size = mem_info.size;
|
2016-12-12 23:07:46 -05:00
|
|
|
remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t) - MAX_USERS*sizeof(uint32_t);
|
2016-12-03 15:17:38 -05:00
|
|
|
|
|
|
|
if (local_sram_size != 0 && local_sram_size == remote_sram_size)
|
|
|
|
{
|
|
|
|
RECV(mem_info.data, local_sram_size)
|
|
|
|
{
|
|
|
|
RARCH_ERR("%s\n",
|
|
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (remote_sram_size != 0)
|
|
|
|
{
|
|
|
|
/* We can't load this, but we still need to get rid of the data */
|
|
|
|
uint32_t quickbuf;
|
|
|
|
while (remote_sram_size > 0)
|
|
|
|
{
|
|
|
|
RECV(&quickbuf, (remote_sram_size > sizeof(uint32_t)) ? sizeof(uint32_t) : remote_sram_size)
|
|
|
|
{
|
|
|
|
RARCH_ERR("%s\n",
|
|
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (remote_sram_size > sizeof(uint32_t))
|
|
|
|
remote_sram_size -= sizeof(uint32_t);
|
|
|
|
else
|
|
|
|
remote_sram_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We're ready! */
|
2016-12-10 20:36:57 -05:00
|
|
|
netplay->self_mode = NETPLAY_CONNECTION_SPECTATING;
|
|
|
|
connection->mode = NETPLAY_CONNECTION_PLAYING;
|
2016-12-05 00:04:01 -05:00
|
|
|
netplay_handshake_ready(netplay, connection);
|
2016-12-03 15:17:38 -05:00
|
|
|
*had_input = true;
|
2016-12-09 14:14:54 -05:00
|
|
|
netplay_recv_flush(&connection->recv_packet_buffer);
|
2016-12-10 20:36:57 -05:00
|
|
|
|
|
|
|
/* Ask to go to player mode */
|
|
|
|
return netplay_cmd_mode(netplay, connection, NETPLAY_CONNECTION_PLAYING);
|
2016-12-03 15:17:38 -05:00
|
|
|
}
|
|
|
|
|
2016-05-12 10:20:14 +02:00
|
|
|
bool netplay_is_server(netplay_t* netplay)
|
2015-12-23 13:25:28 -07:00
|
|
|
{
|
2016-04-27 11:30:50 +02:00
|
|
|
if (!netplay)
|
|
|
|
return false;
|
2015-12-23 13:25:28 -07:00
|
|
|
return netplay->is_server;
|
|
|
|
}
|
|
|
|
|
Multitudinous fixes and updates to Netplay. Had to be one commit since
they're mostly related:
(1) Renamed frame_count to self_frame_count to be consistent with all
other names.
(2) Previously, it was possible to overwrite data in the ring buffer
that hadn't yet been used. Now that's not possible, but that just
changes one breakage for another: It's now possible to miss the NEW
data. The final resolution for this will probably be requesting stalls.
This is accomplished simply by storing frame numbers in the ring buffer
and checking them against the 'other' head.
(3) In TCP packets, separated cmd_size from cmd. It was beyond pointless
for these to be combined, and restricted cmd_size to 16 bits, which
will probably fail when/if state loading is supported.
(4) Readahead is now allowed. In the past, if the peer got ahead of us,
we would simply ignore their data. Thus, if they got too far ahead of
us, we'd stop reading their data altogether. Fabulous. Now, we're happy
to read future input.
(5) If the peer gets too far ahead of us (currently an unconfigurable 10
frames), fast forward to catch up. This should prevent desync due to
clock drift or stutter.
(6) Used frame_count in a few places where ptr was used. Doing a
comparison of pointers on a ring buffer is a far more dangerous way to
assure we're done with a task than simply using the count, since the
ring buffer is... well, a ring.
(7) Renamed tmp_{ptr,frame_count} to replay_{ptr,frame_count} for
clarity.
(8) Slightly changed the protocol version hash, just to assure that
other clients wouldn't think they were compatible with this one.
(9) There was an off-by-one error which, under some circumstances, could
allow the replay engine to run a complete round through the ring buffer,
replaying stale data. Fixed.
2016-09-11 22:01:47 -04:00
|
|
|
bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame)
|
|
|
|
{
|
2016-09-13 17:05:28 -04:00
|
|
|
void *remember_state;
|
|
|
|
if (delta->used)
|
|
|
|
{
|
|
|
|
if (delta->frame == frame) return true;
|
|
|
|
if (netplay->other_frame_count <= delta->frame)
|
|
|
|
{
|
|
|
|
/* We haven't even replayed this frame yet, so we can't overwrite it! */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
remember_state = delta->state;
|
|
|
|
memset(delta, 0, sizeof(struct delta_frame));
|
|
|
|
delta->used = true;
|
|
|
|
delta->frame = frame;
|
|
|
|
delta->state = remember_state;
|
|
|
|
return true;
|
Multitudinous fixes and updates to Netplay. Had to be one commit since
they're mostly related:
(1) Renamed frame_count to self_frame_count to be consistent with all
other names.
(2) Previously, it was possible to overwrite data in the ring buffer
that hadn't yet been used. Now that's not possible, but that just
changes one breakage for another: It's now possible to miss the NEW
data. The final resolution for this will probably be requesting stalls.
This is accomplished simply by storing frame numbers in the ring buffer
and checking them against the 'other' head.
(3) In TCP packets, separated cmd_size from cmd. It was beyond pointless
for these to be combined, and restricted cmd_size to 16 bits, which
will probably fail when/if state loading is supported.
(4) Readahead is now allowed. In the past, if the peer got ahead of us,
we would simply ignore their data. Thus, if they got too far ahead of
us, we'd stop reading their data altogether. Fabulous. Now, we're happy
to read future input.
(5) If the peer gets too far ahead of us (currently an unconfigurable 10
frames), fast forward to catch up. This should prevent desync due to
clock drift or stutter.
(6) Used frame_count in a few places where ptr was used. Doing a
comparison of pointers on a ring buffer is a far more dangerous way to
assure we're done with a task than simply using the count, since the
ring buffer is... well, a ring.
(7) Renamed tmp_{ptr,frame_count} to replay_{ptr,frame_count} for
clarity.
(8) Slightly changed the protocol version hash, just to assure that
other clients wouldn't think they were compatible with this one.
(9) There was an off-by-one error which, under some circumstances, could
allow the replay engine to run a complete round through the ring buffer,
replaying stale data. Fixed.
2016-09-11 22:01:47 -04:00
|
|
|
}
|
2016-09-14 23:19:47 -04:00
|
|
|
|
|
|
|
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
|
|
|
|
{
|
|
|
|
if (!netplay->state_size)
|
|
|
|
return 0;
|
2016-09-21 12:31:40 +02:00
|
|
|
return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size);
|
2016-09-14 23:19:47 -04:00
|
|
|
}
|