RetroArch/network/netplay/netplay_common.c

389 lines
10 KiB
C
Raw Normal View History

2015-12-23 20:25:28 +00:00
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
2016-01-10 03:06:50 +00:00
* Copyright (C) 2011-2016 - Daniel De Matteis
* Copyright (C) 2016 - Gregor Richards
2015-12-23 20:25:28 +00: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-05 22:56:00 +00:00
#include <compat/strl.h>
2015-12-23 20:25:28 +00:00
#include "netplay_private.h"
2016-05-01 21:37:49 +00:00
#include <net/net_socket.h>
2016-09-21 10:31:40 +00:00
#include <encodings/crc32.h>
2016-09-03 05:45:51 +00:00
#include "../../movie.h"
2016-06-29 01:06:15 +00:00
#include "../../msg_hash.h"
#include "../../content.h"
2016-09-03 05:51:11 +00:00
#include "../../runloop.h"
2016-09-03 05:45:51 +00:00
#include "../../version.h"
2016-01-22 15:09:48 +00:00
2016-05-12 10:03:43 +00:00
bool netplay_get_nickname(netplay_t *netplay, int fd)
2015-12-23 20:25:28 +00:00
{
uint8_t nick_size;
if (!socket_receive_all_blocking(fd, &nick_size, sizeof(nick_size)))
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_SIZE_FROM_HOST));
2015-12-23 20:25:28 +00:00
return false;
}
if (nick_size >= sizeof(netplay->other_nick))
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_INVALID_NICKNAME_SIZE));
2015-12-23 20:25:28 +00:00
return false;
}
if (!socket_receive_all_blocking(fd, netplay->other_nick, nick_size))
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME));
2015-12-23 20:25:28 +00:00
return false;
}
return true;
}
2016-05-12 10:03:43 +00:00
bool netplay_send_nickname(netplay_t *netplay, int fd)
2015-12-23 20:25:28 +00:00
{
uint8_t nick_size = strlen(netplay->nick);
2016-05-01 21:21:30 +00:00
if (!socket_send_all_blocking(fd, &nick_size, sizeof(nick_size), false))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_SIZE));
2015-12-23 20:25:28 +00:00
return false;
}
2016-05-01 21:21:30 +00:00
if (!socket_send_all_blocking(fd, netplay->nick, nick_size, false))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME));
2015-12-23 20:25:28 +00:00
return false;
}
return true;
}
/**
2016-05-12 10:03:43 +00:00
* netplay_impl_magic:
2015-12-23 20:25:28 +00: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 10:03:43 +00:00
uint32_t netplay_impl_magic(void)
2015-12-23 20:25:28 +00:00
{
size_t i, len;
2016-01-28 03:26:17 +00:00
retro_ctx_api_info_t api_info;
unsigned api;
2015-12-23 20:25:28 +00:00
uint32_t res = 0;
rarch_system_info_t *info = NULL;
2015-12-23 20:25:28 +00:00
const char *lib = NULL;
const char *ver = PACKAGE_VERSION;
2016-01-28 03:26:17 +00:00
2016-05-07 23:33:57 +00:00
core_api_version(&api_info);
2016-01-28 03:26:17 +00:00
api = api_info.version;
runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
res |= api;
2015-12-23 20:25:28 +00:00
if (info)
{
2015-12-23 20:25:28 +00:00
lib = info->info.library_name;
len = strlen(lib);
for (i = 0; i < len; i++)
res ^= lib[i] << (i & 0xf);
2015-12-23 20:25:28 +00:00
lib = info->info.library_version;
len = strlen(lib);
2015-12-23 20:25:28 +00:00
for (i = 0; i < len; i++)
res ^= lib[i] << (i & 0xf);
}
2015-12-23 20:25:28 +00: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-12 02:01:47 +00:00
res ^= NETPLAY_PROTOCOL_VERSION << 24;
2015-12-23 20:25:28 +00:00
return res;
}
2016-05-12 10:03:43 +00:00
bool netplay_send_info(netplay_t *netplay)
2015-12-23 20:25:28 +00:00
{
unsigned sram_size, remote_sram_size;
2016-01-28 02:22:23 +00:00
retro_ctx_memory_info_t mem_info;
2016-01-22 15:09:48 +00:00
char msg[512] = {0};
uint32_t *content_crc_ptr = NULL;
void *sram = NULL;
uint32_t header[3] = {0};
size_t i;
2016-01-22 15:09:48 +00:00
2016-01-28 02:22:23 +00:00
mem_info.id = RETRO_MEMORY_SAVE_RAM;
2016-05-07 23:33:57 +00:00
core_get_memory(&mem_info);
2016-05-08 03:17:31 +00:00
content_get_crc(&content_crc_ptr);
2016-01-22 15:09:48 +00:00
header[0] = htonl(*content_crc_ptr);
2016-05-12 10:03:43 +00:00
header[1] = htonl(netplay_impl_magic());
2016-01-28 02:22:23 +00:00
header[2] = htonl(mem_info.size);
2015-12-23 20:25:28 +00:00
2016-05-01 21:21:30 +00:00
if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false))
2015-12-23 20:25:28 +00:00
return false;
if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header)))
{
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT));
return false;
}
if (*content_crc_ptr != ntohl(header[0]))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER));
return false;
}
if (netplay_impl_magic() != ntohl(header[1]))
{
RARCH_ERR("Implementations differ, make sure you're using exact same "
"libretro implementations and RetroArch version.\n");
return false;
}
/* Some cores only report the correct sram size late, so we can't actually
* error out if the sram size seems wrong. */
sram_size = mem_info.size;
remote_sram_size = ntohl(header[2]);
if (sram_size != 0 && remote_sram_size != 0 && sram_size != remote_sram_size)
{
RARCH_WARN("Content SRAM sizes do not correspond.\n");
}
2016-05-12 10:03:43 +00:00
if (!netplay_send_nickname(netplay, netplay->fd))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_HOST));
2015-12-23 20:25:28 +00:00
return false;
}
/* Get SRAM data from User 1. */
if (sram_size != 0 && sram_size == remote_sram_size)
{
sram = mem_info.data;
2015-12-23 20:25:28 +00:00
if (!socket_receive_all_blocking(netplay->fd, sram, 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)
2015-12-23 20:25:28 +00:00
{
/* We can't load this, but we still need to get rid of the data */
uint32_t quickbuf;
while (remote_sram_size > 0)
{
if (!socket_receive_all_blocking(netplay->fd, &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;
}
2015-12-23 20:25:28 +00:00
}
2016-05-12 10:03:43 +00:00
if (!netplay_get_nickname(netplay, netplay->fd))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST));
2015-12-23 20:25:28 +00:00
return false;
}
/* Reset our frame count so it's consistent with the server */
netplay->self_frame_count = netplay->read_frame_count = netplay->other_frame_count = 0;
for (i = 0; i < netplay->buffer_size; i++)
{
netplay->buffer[i].used = false;
if (i == netplay->self_ptr)
{
netplay_delta_frame_ready(netplay, &netplay->buffer[i], 0);
netplay->read_ptr = netplay->other_ptr = i;
}
else
{
netplay->buffer[i].used = false;
}
}
2016-06-29 01:06:15 +00:00
snprintf(msg, sizeof(msg), "%s: \"%s\"",
msg_hash_to_str(MSG_CONNECTED_TO),
netplay->other_nick);
2015-12-23 20:25:28 +00:00
RARCH_LOG("%s\n", msg);
runloop_msg_queue_push(msg, 1, 180, false);
return true;
}
2016-05-12 10:03:43 +00:00
bool netplay_get_info(netplay_t *netplay)
2015-12-23 20:25:28 +00:00
{
unsigned sram_size, remote_sram_size;
2015-12-23 20:25:28 +00:00
uint32_t header[3];
2016-01-28 02:22:23 +00:00
retro_ctx_memory_info_t mem_info;
2016-01-22 15:09:48 +00:00
uint32_t *content_crc_ptr = NULL;
const void *sram = NULL;
size_t i;
2015-12-23 20:25:28 +00:00
/* FIXME: There's a huge amount of duplication between send_info and
* get_info */
mem_info.id = RETRO_MEMORY_SAVE_RAM;
core_get_memory(&mem_info);
content_get_crc(&content_crc_ptr);
header[0] = htonl(*content_crc_ptr);
header[1] = htonl(netplay_impl_magic());
header[2] = htonl(mem_info.size);
if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false))
return false;
2015-12-23 20:25:28 +00:00
if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header)))
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT));
2015-12-23 20:25:28 +00:00
return false;
}
2016-01-22 15:09:48 +00:00
if (*content_crc_ptr != ntohl(header[0]))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n", msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER));
2015-12-23 20:25:28 +00:00
return false;
}
2016-05-12 10:03:43 +00:00
if (netplay_impl_magic() != ntohl(header[1]))
2015-12-23 20:25:28 +00:00
{
2016-02-03 16:05:32 +00:00
RARCH_ERR("Implementations differ, make sure you're using exact same "
"libretro implementations and RetroArch version.\n");
2015-12-23 20:25:28 +00:00
return false;
}
sram_size = mem_info.size;
remote_sram_size = ntohl(header[2]);
if (sram_size != 0 && remote_sram_size != 0 && sram_size != remote_sram_size)
2015-12-23 20:25:28 +00:00
{
RARCH_WARN("Content SRAM sizes do not correspond.\n");
2015-12-23 20:25:28 +00:00
}
2016-05-12 10:03:43 +00:00
if (!netplay_get_nickname(netplay, netplay->fd))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT));
2015-12-23 20:25:28 +00:00
return false;
}
/* Send SRAM data to our User 2. */
2016-01-28 02:22:23 +00:00
sram = mem_info.data;
2015-12-23 20:25:28 +00:00
2016-05-01 21:21:30 +00:00
if (!socket_send_all_blocking(netplay->fd, sram, sram_size, false))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_SEND_SRAM_DATA_TO_CLIENT));
2015-12-23 20:25:28 +00:00
return false;
}
2016-05-12 10:03:43 +00:00
if (!netplay_send_nickname(netplay, netplay->fd))
2015-12-23 20:25:28 +00:00
{
2016-06-29 01:06:15 +00:00
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT));
2015-12-23 20:25:28 +00:00
return false;
}
/* Reset our frame count so it's consistent with the client */
netplay->self_frame_count = netplay->read_frame_count = netplay->other_frame_count = 0;
for (i = 0; i < netplay->buffer_size; i++)
{
netplay->buffer[i].used = false;
if (i == netplay->self_ptr)
{
netplay_delta_frame_ready(netplay, &netplay->buffer[i], 0);
netplay->read_ptr = netplay->other_ptr = i;
}
else
{
netplay->buffer[i].used = false;
}
}
2015-12-23 20:25:28 +00:00
#ifndef HAVE_SOCKET_LEGACY
2016-05-12 10:03:43 +00:00
netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick);
2015-12-23 20:25:28 +00:00
#endif
return true;
}
bool netplay_is_server(netplay_t* netplay)
2015-12-23 20:25:28 +00:00
{
2016-04-27 09:30:50 +00:00
if (!netplay)
return false;
2015-12-23 20:25:28 +00:00
return netplay->is_server;
}
2016-05-12 10:03:43 +00:00
bool netplay_is_spectate(netplay_t* netplay)
2015-12-23 20:25:28 +00:00
{
2016-04-27 09:30:50 +00:00
if (!netplay)
return false;
2015-12-23 20:25:28 +00:00
return netplay->spectate.enabled;
2016-01-10 03:06:50 +00:00
}
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-12 02:01:47 +00:00
bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame)
{
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-12 02:01:47 +00:00
}
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
{
if (!netplay->state_size)
return 0;
2016-09-21 10:31:40 +00:00
return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size);
}