mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-29 11:10:27 +00:00
523 lines
14 KiB
C
523 lines
14 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2016 - Daniel De Matteis
|
|
* Copyright (C) 2016 - 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 <compat/strl.h>
|
|
|
|
#include "netplay_private.h"
|
|
#include <net/net_socket.h>
|
|
|
|
#include <encodings/crc32.h>
|
|
|
|
#include "../../movie.h"
|
|
#include "../../msg_hash.h"
|
|
#include "../../content.h"
|
|
#include "../../runloop.h"
|
|
#include "../../version.h"
|
|
|
|
bool netplay_get_nickname(netplay_t *netplay, int fd)
|
|
{
|
|
uint8_t nick_size;
|
|
|
|
if (!socket_receive_all_blocking(fd, &nick_size, sizeof(nick_size)))
|
|
{
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_SIZE_FROM_HOST));
|
|
return false;
|
|
}
|
|
|
|
if (nick_size >= sizeof(netplay->other_nick))
|
|
{
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_INVALID_NICKNAME_SIZE));
|
|
return false;
|
|
}
|
|
|
|
if (!socket_receive_all_blocking(fd, netplay->other_nick, nick_size))
|
|
{
|
|
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool netplay_send_nickname(netplay_t *netplay, int fd)
|
|
{
|
|
uint8_t nick_size = strlen(netplay->nick);
|
|
|
|
if (!socket_send_all_blocking(fd, &nick_size, sizeof(nick_size), false))
|
|
{
|
|
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_SIZE));
|
|
return false;
|
|
}
|
|
|
|
if (!socket_send_all_blocking(fd, netplay->nick, nick_size, false))
|
|
{
|
|
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* netplay_impl_magic:
|
|
*
|
|
* 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.
|
|
**/
|
|
uint32_t netplay_impl_magic(void)
|
|
{
|
|
size_t i, len;
|
|
retro_ctx_api_info_t api_info;
|
|
unsigned api;
|
|
uint32_t res = 0;
|
|
rarch_system_info_t *info = NULL;
|
|
const char *lib = NULL;
|
|
const char *ver = PACKAGE_VERSION;
|
|
|
|
core_api_version(&api_info);
|
|
|
|
api = api_info.version;
|
|
|
|
runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
|
|
|
|
res |= api;
|
|
|
|
if (info)
|
|
{
|
|
lib = info->info.library_name;
|
|
|
|
len = strlen(lib);
|
|
for (i = 0; i < len; i++)
|
|
res ^= lib[i] << (i & 0xf);
|
|
|
|
lib = info->info.library_version;
|
|
len = strlen(lib);
|
|
|
|
for (i = 0; i < len; i++)
|
|
res ^= lib[i] << (i & 0xf);
|
|
}
|
|
|
|
len = strlen(ver);
|
|
for (i = 0; i < len; i++)
|
|
res ^= ver[i] << ((i & 0xf) + 16);
|
|
|
|
res ^= NETPLAY_PROTOCOL_VERSION << 24;
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
bool netplay_handshake(netplay_t *netplay)
|
|
{
|
|
size_t i;
|
|
uint32_t local_pmagic, remote_pmagic;
|
|
unsigned sram_size, remote_sram_size;
|
|
retro_ctx_memory_info_t mem_info;
|
|
char msg[512];
|
|
uint32_t *content_crc_ptr = NULL;
|
|
void *sram = NULL;
|
|
uint32_t header[5] = {0};
|
|
bool is_server = netplay->is_server;
|
|
int compression = 0;
|
|
|
|
msg[0] = '\0';
|
|
|
|
mem_info.id = RETRO_MEMORY_SAVE_RAM;
|
|
|
|
core_get_memory(&mem_info);
|
|
content_get_crc(&content_crc_ptr);
|
|
|
|
local_pmagic = netplay_platform_magic();
|
|
|
|
header[0] = htonl(*content_crc_ptr);
|
|
header[1] = htonl(netplay_impl_magic());
|
|
header[2] = htonl(mem_info.size);
|
|
header[3] = htonl(local_pmagic);
|
|
header[4] = htonl(NETPLAY_COMPRESSION_SUPPORTED);
|
|
|
|
if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false))
|
|
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");
|
|
}
|
|
|
|
/* We only care about platform magic if our core is quirky */
|
|
remote_pmagic = ntohl(header[3]);
|
|
if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) &&
|
|
netplay_endian_mismatch(local_pmagic, remote_pmagic))
|
|
{
|
|
RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n");
|
|
return false;
|
|
}
|
|
if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) &&
|
|
(local_pmagic != remote_pmagic))
|
|
{
|
|
RARCH_ERR("Platform mismatch with a platform-sensitive core.\n");
|
|
return false;
|
|
}
|
|
|
|
/* 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[4]);
|
|
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;
|
|
}
|
|
|
|
/* Client sends nickname first, server replies with nickname */
|
|
if (!is_server)
|
|
{
|
|
if (!netplay_send_nickname(netplay, netplay->fd))
|
|
{
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_HOST));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!netplay_get_nickname(netplay, netplay->fd))
|
|
{
|
|
if (is_server)
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT));
|
|
else
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST));
|
|
return false;
|
|
}
|
|
|
|
if (is_server)
|
|
{
|
|
if (!netplay_send_nickname(netplay, netplay->fd))
|
|
{
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Server sends SRAM, client receives */
|
|
if (is_server)
|
|
{
|
|
/* Send SRAM data to the client */
|
|
sram = mem_info.data;
|
|
|
|
if (!socket_send_all_blocking(netplay->fd, sram, sram_size, false))
|
|
{
|
|
RARCH_ERR("%s\n",
|
|
msg_hash_to_str(MSG_FAILED_TO_SEND_SRAM_DATA_TO_CLIENT));
|
|
return false;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
/* Get SRAM data from User 1. */
|
|
if (sram_size != 0 && sram_size == remote_sram_size)
|
|
{
|
|
sram = mem_info.data;
|
|
|
|
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)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Reset our frame count so it's consistent with the server */
|
|
netplay->self_frame_count = netplay->other_frame_count = 0;
|
|
netplay->read_frame_count = 1;
|
|
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->buffer[i].have_remote = true;
|
|
netplay->other_ptr = i;
|
|
netplay->read_ptr = NEXT_PTR(i);
|
|
}
|
|
else
|
|
{
|
|
netplay->buffer[i].used = false;
|
|
}
|
|
}
|
|
|
|
if (is_server)
|
|
{
|
|
netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick);
|
|
}
|
|
else
|
|
{
|
|
snprintf(msg, sizeof(msg), "%s: \"%s\"",
|
|
msg_hash_to_str(MSG_CONNECTED_TO),
|
|
netplay->other_nick);
|
|
RARCH_LOG("%s\n", msg);
|
|
runloop_msg_queue_push(msg, 1, 180, false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool netplay_is_server(netplay_t* netplay)
|
|
{
|
|
if (!netplay)
|
|
return false;
|
|
return netplay->is_server;
|
|
}
|
|
|
|
bool netplay_is_spectate(netplay_t* netplay)
|
|
{
|
|
if (!netplay)
|
|
return false;
|
|
return netplay->spectate.enabled;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
|
|
{
|
|
if (!netplay->state_size)
|
|
return 0;
|
|
return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size);
|
|
}
|
|
|
|
/*
|
|
* AD PACKET FORMAT:
|
|
*
|
|
* Request:
|
|
* 1 word: RANQ (RetroArch Netplay Query)
|
|
* 1 word: Netplay protocol version
|
|
*
|
|
* Reply:
|
|
* 1 word : RANS (RetroArch Netplay Server)
|
|
* 1 word : Netplay protocol version
|
|
* 1 word : Port
|
|
* 8 words: RetroArch version
|
|
* 8 words: Nick
|
|
* 8 words: Core name
|
|
* 8 words: Core version
|
|
* 8 words: Content name (currently always blank)
|
|
*/
|
|
|
|
#define AD_PACKET_MAX_SIZE 512
|
|
#define AD_PACKET_STRING_SIZE 32
|
|
#define AD_PACKET_STRING_WORDS (AD_PACKET_STRING_SIZE/sizeof(uint32_t))
|
|
static uint32_t *ad_packet_buffer = NULL;
|
|
|
|
bool netplay_ad_server(netplay_t *netplay, int ad_fd)
|
|
{
|
|
fd_set fds;
|
|
struct timeval tmp_tv = {0};
|
|
struct sockaddr their_addr;
|
|
socklen_t addr_size;
|
|
rarch_system_info_t *info = NULL;
|
|
size_t bufloc;
|
|
|
|
if (!ad_packet_buffer)
|
|
{
|
|
ad_packet_buffer = (uint32_t *) malloc(AD_PACKET_MAX_SIZE);
|
|
if (!ad_packet_buffer)
|
|
return false;
|
|
}
|
|
|
|
/* Check for any ad queries */
|
|
while (1)
|
|
{
|
|
FD_ZERO(&fds);
|
|
FD_SET(ad_fd, &fds);
|
|
if (socket_select(ad_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
|
|
break;
|
|
if (!FD_ISSET(ad_fd, &fds))
|
|
break;
|
|
|
|
/* Somebody queried, so check that it's valid */
|
|
if (recvfrom(ad_fd, (char*)ad_packet_buffer, AD_PACKET_MAX_SIZE, 0,
|
|
&their_addr, &addr_size) >= (ssize_t) (2*sizeof(uint32_t)))
|
|
{
|
|
/* Make sure it's a valid query */
|
|
if (memcmp(ad_packet_buffer, "RANQ", 4))
|
|
continue;
|
|
|
|
/* For this version */
|
|
if (ntohl(ad_packet_buffer[1]) != NETPLAY_PROTOCOL_VERSION)
|
|
continue;
|
|
|
|
runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
|
|
|
|
/* Now build our response */
|
|
memset(ad_packet_buffer, 0, AD_PACKET_MAX_SIZE);
|
|
memcpy(ad_packet_buffer, "RANS", 4);
|
|
ad_packet_buffer[1] = htonl(NETPLAY_PROTOCOL_VERSION);
|
|
ad_packet_buffer[2] = htonl(netplay->tcp_port);
|
|
bufloc = 3;
|
|
strncpy((char *) (ad_packet_buffer + bufloc),
|
|
PACKAGE_VERSION, AD_PACKET_STRING_SIZE);
|
|
bufloc += AD_PACKET_STRING_WORDS;
|
|
strncpy((char *) (ad_packet_buffer + bufloc),
|
|
netplay->nick, AD_PACKET_STRING_SIZE);
|
|
bufloc += AD_PACKET_STRING_WORDS;
|
|
if (info)
|
|
{
|
|
strncpy((char *) (ad_packet_buffer + bufloc),
|
|
info->info.library_name, AD_PACKET_STRING_SIZE);
|
|
bufloc += AD_PACKET_STRING_WORDS;
|
|
strncpy((char *) (ad_packet_buffer + bufloc),
|
|
info->info.library_version, AD_PACKET_STRING_SIZE);
|
|
bufloc += AD_PACKET_STRING_WORDS;
|
|
/* Blank content */
|
|
bufloc += AD_PACKET_STRING_WORDS;
|
|
}
|
|
else
|
|
{
|
|
bufloc += 3*AD_PACKET_STRING_WORDS;
|
|
}
|
|
|
|
/* And send it */
|
|
sendto(ad_fd, (const char*)ad_packet_buffer, bufloc*sizeof(uint32_t), 0,
|
|
&their_addr, addr_size);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|