diff --git a/Makefile.common b/Makefile.common index 9071b8a29c..97c1113b76 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2099,10 +2099,9 @@ ifeq ($(HAVE_NETWORKING), 1) # Netplay DEFINES += -DHAVE_NETWORK_CMD - OBJ += network/netplay/netplay_handshake.o \ - network/netplay/netplay_io.o \ - network/netplay/netplay_discovery.o \ - network/netplay/netplay_room_parse.o + OBJ += \ + network/netplay/netplay_frontend.o \ + network/netplay/netplay_room_parse.o # RetroAchievements ifeq ($(HAVE_CHEEVOS), 1) diff --git a/griffin/griffin.c b/griffin/griffin.c index c39e328d63..6b4c86588a 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1290,9 +1290,7 @@ THREAD NETPLAY ============================================================ */ #ifdef HAVE_NETWORKING -#include "../network/netplay/netplay_handshake.c" -#include "../network/netplay/netplay_io.c" -#include "../network/netplay/netplay_discovery.c" +#include "../network/netplay/netplay_frontend.c" #include "../network/netplay/netplay_room_parse.c" #include "../libretro-common/net/net_compat.c" #include "../libretro-common/net/net_socket.c" diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 547ed16a5d..d88693b10b 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -96,4 +96,82 @@ int netplay_rooms_get_count(void); void netplay_rooms_free(void); +/** +* netplay_frontend_paused + * @netplay : pointer to netplay object + * @paused : true if frontend is paused + * + * Inform Netplay of the frontend's pause state (paused or otherwise) + */ +void netplay_frontend_paused(netplay_t *netplay, bool paused); + +/** + * netplay_toggle_play_spectate + * + * Toggle between play mode and spectate mode + */ +void netplay_toggle_play_spectate(netplay_t *netplay); + +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded, NULL means + * "load it yourself" + * @save : Whether to save the provided serial_info + * into the frame buffer + * + * Inform Netplay of a savestate load and send it to the other side + **/ +void netplay_load_savestate(netplay_t *netplay, + retro_ctx_serialize_info_t *serial_info, bool save); + +/** + * netplay_core_reset + * @netplay : pointer to netplay object + * + * Indicate that the core has been reset to netplay peers + **/ +void netplay_core_reset(netplay_t *netplay); + +int16_t netplay_input_state(netplay_t *netplay, + unsigned port, unsigned device, + unsigned idx, unsigned id); + +/** + * netplay_poll: + * @netplay : pointer to netplay object + * + * Polls network to see if we have anything new. If our + * network buffer is full, we simply have to block + * for new input data. + * + * Returns: true (1) if successful, otherwise false (0). + **/ +bool netplay_poll( + bool block_libretro_input, + void *settings_data, + netplay_t *netplay); + +/** + * netplay_is_alive: + * @netplay : pointer to netplay object + * + * Checks if input port/index is controlled by netplay or not. + * + * Returns: true (1) if alive, otherwise false (0). + **/ +bool netplay_is_alive(netplay_t *netplay); + +/** + * netplay_should_skip: + * @netplay : pointer to netplay object + * + * If we're fast-forward replaying to resync, check if we + * should actually show frame. + * + * Returns: bool (1) if we should skip this frame, otherwise + * false (0). + **/ +bool netplay_should_skip(netplay_t *netplay); + #endif diff --git a/network/netplay/netplay_discovery.c b/network/netplay/netplay_discovery.c deleted file mode 100644 index 091518604f..0000000000 --- a/network/netplay/netplay_discovery.c +++ /dev/null @@ -1,556 +0,0 @@ -/* RetroArch - A frontend for libretro. - * 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 . - */ - -/* - * 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) - */ - -#include -#include -#include -#include -#include -#include "../../file_path_special.h" -#include "../../paths.h" -#include "../../content.h" - -#include -#include - -#include "../../retroarch.h" -#include "../../version.h" -#include "../../verbosity.h" - -#include "netplay.h" -#include "netplay_discovery.h" -#include "netplay_private.h" - -#if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) && !defined(_3DS) -#define HAVE_INET6 1 -#endif - -/* TODO/FIXME - globals referenced outside */ -struct netplay_room *netplay_room_list = NULL; -int netplay_room_count = 0; - -#ifdef HAVE_NETPLAYDISCOVERY -struct ad_packet -{ - uint32_t header; - uint32_t protocol_version; - uint32_t port; - char address[NETPLAY_HOST_STR_LEN]; - char retroarch_version[NETPLAY_HOST_STR_LEN]; - char nick[NETPLAY_HOST_STR_LEN]; - char frontend[NETPLAY_HOST_STR_LEN]; - char core[NETPLAY_HOST_STR_LEN]; - char core_version[NETPLAY_HOST_STR_LEN]; - char content[NETPLAY_HOST_LONGSTR_LEN]; - char content_crc[NETPLAY_HOST_STR_LEN]; - char subsystem_name[NETPLAY_HOST_STR_LEN]; -}; - -/* TODO/FIXME - static globals */ - -/* LAN discovery sockets */ -static int lan_ad_server_fd = -1; -static int lan_ad_client_fd = -1; - -/* Packet buffer for advertisement and responses */ -static struct ad_packet ad_packet_buffer; - -/* List of discovered hosts */ -static struct netplay_host_list discovered_hosts; - -static size_t discovered_hosts_allocated; - -#ifdef HAVE_SOCKET_LEGACY - -#ifndef htons -/* The fact that I need to write this is deeply depressing */ -static int16_t htons_for_morons(int16_t value) -{ - union { - int32_t l; - int16_t s[2]; - } val; - val.l = htonl(value); - return val.s[1]; -} -#define htons htons_for_morons -#endif - -#endif - -static bool netplay_lan_ad_client(void) -{ - unsigned i; - fd_set fds; - socklen_t addr_size; - struct sockaddr their_addr; - struct timeval tmp_tv = {0}; - - if (lan_ad_client_fd < 0) - return false; - - their_addr.sa_family = 0; - for (i = 0; i < 14; i++) - their_addr.sa_data[i] = 0; - - /* Check for any ad queries */ - for (;;) - { - FD_ZERO(&fds); - FD_SET(lan_ad_client_fd, &fds); - if (socket_select(lan_ad_client_fd + 1, - &fds, NULL, NULL, &tmp_tv) <= 0) - break; - - if (!FD_ISSET(lan_ad_client_fd, &fds)) - break; - - /* Somebody queried, so check that it's valid */ - addr_size = sizeof(their_addr); - - if (recvfrom(lan_ad_client_fd, (char*)&ad_packet_buffer, - sizeof(struct ad_packet), 0, &their_addr, &addr_size) >= - (ssize_t) sizeof(struct ad_packet)) - { - struct netplay_host *host = NULL; - - /* Make sure it's a valid response */ - if (memcmp((void *) &ad_packet_buffer, "RANS", 4)) - continue; - - /* For this version */ - if (ntohl(ad_packet_buffer.protocol_version) - != NETPLAY_PROTOCOL_VERSION) - continue; - - /* And that we know how to handle it */ - if (their_addr.sa_family == AF_INET) - { - struct sockaddr_in *sin = NULL; - - RARCH_WARN ("[Discovery] Using IPv4 for discovery\n"); - sin = (struct sockaddr_in *) &their_addr; - sin->sin_port = htons(ntohl(ad_packet_buffer.port)); - - } -#ifdef HAVE_INET6 - else if (their_addr.sa_family == AF_INET6) - { - struct sockaddr_in6 *sin6 = NULL; - RARCH_WARN ("[Discovery] Using IPv6 for discovery\n"); - sin6 = (struct sockaddr_in6 *) &their_addr; - sin6->sin6_port = htons(ad_packet_buffer.port); - - } -#endif - else - continue; - - /* Allocate space for it */ - if (discovered_hosts.size >= discovered_hosts_allocated) - { - size_t allocated = discovered_hosts_allocated; - struct netplay_host *new_hosts = NULL; - - if (allocated == 0) - allocated = 2; - else - allocated *= 2; - - if (discovered_hosts.hosts) - new_hosts = (struct netplay_host *) - realloc(discovered_hosts.hosts, allocated * sizeof(struct - netplay_host)); - else - /* Should be equivalent to realloc, - * but I don't trust screwy libcs */ - new_hosts = (struct netplay_host *) - malloc(allocated * sizeof(struct netplay_host)); - - if (!new_hosts) - return false; - - discovered_hosts.hosts = new_hosts; - discovered_hosts_allocated = allocated; - } - - /* Get our host structure */ - host = &discovered_hosts.hosts[discovered_hosts.size++]; - - /* Copy in the response */ - memset(host, 0, sizeof(struct netplay_host)); - host->addr = their_addr; - host->addrlen = addr_size; - - host->port = ntohl(ad_packet_buffer.port); - - strlcpy(host->address, ad_packet_buffer.address, NETPLAY_HOST_STR_LEN); - strlcpy(host->nick, ad_packet_buffer.nick, NETPLAY_HOST_STR_LEN); - strlcpy(host->core, ad_packet_buffer.core, NETPLAY_HOST_STR_LEN); - strlcpy(host->retroarch_version, ad_packet_buffer.retroarch_version, - NETPLAY_HOST_STR_LEN); - strlcpy(host->core_version, ad_packet_buffer.core_version, - NETPLAY_HOST_STR_LEN); - strlcpy(host->content, ad_packet_buffer.content, - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(host->subsystem_name, ad_packet_buffer.subsystem_name, - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(host->frontend, ad_packet_buffer.frontend, - NETPLAY_HOST_STR_LEN); - - host->content_crc = - atoi(ad_packet_buffer.content_crc); - host->nick[NETPLAY_HOST_STR_LEN-1] = - host->core[NETPLAY_HOST_STR_LEN-1] = - host->core_version[NETPLAY_HOST_STR_LEN-1] = - host->content[NETPLAY_HOST_LONGSTR_LEN-1] = '\0'; - } - } - - return true; -} - -/** Initialize Netplay discovery (client) */ -bool init_netplay_discovery(void) -{ - struct addrinfo *addr = NULL; - int fd = socket_init((void **)&addr, 0, NULL, SOCKET_TYPE_DATAGRAM); - - if (fd < 0) - goto error; - - if (!socket_bind(fd, (void*)addr)) - { - socket_close(fd); - goto error; - } - - lan_ad_client_fd = fd; - freeaddrinfo_retro(addr); - return true; - -error: - if (addr) - freeaddrinfo_retro(addr); - RARCH_ERR("[Discovery] Failed to initialize netplay advertisement client socket.\n"); - return false; -} - -/** Deinitialize and free Netplay discovery */ -/* TODO/FIXME - this is apparently never called? */ -void deinit_netplay_discovery(void) -{ - if (lan_ad_client_fd >= 0) - { - socket_close(lan_ad_client_fd); - lan_ad_client_fd = -1; - } -} - -/** Discovery control */ -/* TODO/FIXME: implement net_ifinfo and ntohs for consoles */ -bool netplay_discovery_driver_ctl( - enum rarch_netplay_discovery_ctl_state state, void *data) -{ - int ret; - char port_str[6]; - unsigned k = 0; - - if (lan_ad_client_fd < 0) - return false; - - switch (state) - { - case RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY: - { - net_ifinfo_t interfaces; - struct addrinfo hints = {0}, *addr; - - if (!net_ifinfo_new(&interfaces)) - return false; - - /* Get the broadcast address (IPv4 only for now) */ - snprintf(port_str, 6, "%hu", (unsigned short) RARCH_DEFAULT_PORT); - if (getaddrinfo_retro("255.255.255.255", port_str, &hints, &addr) < 0) - return false; - - /* Make it broadcastable */ -#if defined(SOL_SOCKET) && defined(SO_BROADCAST) - { - int can_broadcast = 1; - if (setsockopt(lan_ad_client_fd, SOL_SOCKET, SO_BROADCAST, - (const char *)&can_broadcast, sizeof(can_broadcast)) < 0) - RARCH_WARN("[Discovery] Failed to set netplay discovery port to broadcast\n"); - } -#endif - - /* Put together the request */ - memcpy((void *) &ad_packet_buffer, "RANQ", 4); - ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION); - - for (k = 0; k < (unsigned)interfaces.size; k++) - { - strlcpy(ad_packet_buffer.address, interfaces.entries[k].host, - NETPLAY_HOST_STR_LEN); - - /* And send it off */ - ret = (int)sendto(lan_ad_client_fd, (const char *) &ad_packet_buffer, - sizeof(struct ad_packet), 0, addr->ai_addr, addr->ai_addrlen); - if (ret < (ssize_t) (2*sizeof(uint32_t))) - RARCH_WARN("[Discovery] Failed to send netplay discovery query (error: %d)\n", errno); - } - - freeaddrinfo_retro(addr); - net_ifinfo_free(&interfaces); - - break; - } - - case RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES: - if (!netplay_lan_ad_client()) - return false; - *((struct netplay_host_list **) data) = &discovered_hosts; - break; - - case RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES: - discovered_hosts.size = 0; - break; - - default: - return false; - } - return true; -} - -static bool init_lan_ad_server_socket(netplay_t *netplay, uint16_t port) -{ - struct addrinfo *addr = NULL; - int fd = socket_init((void **) &addr, port, NULL, SOCKET_TYPE_DATAGRAM); - - if (fd < 0) - goto error; - - if (!socket_bind(fd, (void*)addr)) - { - socket_close(fd); - goto error; - } - - lan_ad_server_fd = fd; - freeaddrinfo_retro(addr); - - return true; - -error: - if (addr) - freeaddrinfo_retro(addr); - return false; -} - -/** - * netplay_lan_ad_server - * - * Respond to any LAN ad queries that the netplay server has received. - */ -bool netplay_lan_ad_server(netplay_t *netplay) -{ - /* TODO/FIXME: implement net_ifinfo and ntohs for consoles */ - fd_set fds; - int ret; - unsigned i; - char buf[4096]; - net_ifinfo_t interfaces; - socklen_t addr_size; - char reply_addr[NETPLAY_HOST_STR_LEN], port_str[6]; - struct sockaddr their_addr; - struct timeval tmp_tv = {0}; - unsigned k = 0; - struct addrinfo *our_addr, hints = {0}; - struct string_list *subsystem = path_get_subsystem_list(); - - interfaces.entries = NULL; - interfaces.size = 0; - - their_addr.sa_family = 0; - for (i = 0; i < 14; i++) - their_addr.sa_data[i] = 0; - - if (!net_ifinfo_new(&interfaces)) - return false; - - if ( (lan_ad_server_fd < 0) - && !init_lan_ad_server_socket(netplay, RARCH_DEFAULT_PORT)) - { - RARCH_ERR("[Discovery] Failed to initialize netplay advertisement socket\n"); - return false; - } - - /* Check for any ad queries */ - for (;;) - { - FD_ZERO(&fds); - FD_SET(lan_ad_server_fd, &fds); - if (socket_select(lan_ad_server_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - break; - if (!FD_ISSET(lan_ad_server_fd, &fds)) - break; - - /* Somebody queried, so check that it's valid */ - addr_size = sizeof(their_addr); - ret = (int)recvfrom(lan_ad_server_fd, (char*)&ad_packet_buffer, - sizeof(struct ad_packet), 0, &their_addr, &addr_size); - if (ret >= (ssize_t) (2 * sizeof(uint32_t))) - { - char s[NETPLAY_HOST_STR_LEN]; - uint32_t content_crc = 0; - - /* Make sure it's a valid query */ - if (memcmp((void *) &ad_packet_buffer, "RANQ", 4)) - { - RARCH_LOG("[Discovery] Invalid query\n"); - continue; - } - - /* For this version */ - if (ntohl(ad_packet_buffer.protocol_version) != - NETPLAY_PROTOCOL_VERSION) - { - RARCH_LOG("[Discovery] Invalid protocol version\n"); - continue; - } - - if (!string_is_empty(ad_packet_buffer.address)) - strlcpy(reply_addr, ad_packet_buffer.address, NETPLAY_HOST_STR_LEN); - - for (k = 0; k < interfaces.size; k++) - { - char *p; - char sub[NETPLAY_HOST_STR_LEN]; - char frontend_architecture_tmp[32]; - char frontend[256]; - const frontend_ctx_driver_t *frontend_drv = - (const frontend_ctx_driver_t*) - frontend_driver_get_cpu_architecture_str( - frontend_architecture_tmp, sizeof(frontend_architecture_tmp)); - snprintf(frontend, sizeof(frontend), "%s %s", - frontend_drv->ident, frontend_architecture_tmp); - - p=strrchr(reply_addr,'.'); - if (p) - { - strlcpy(sub, reply_addr, p - reply_addr + 1); - if (strstr(interfaces.entries[k].host, sub) && - !strstr(interfaces.entries[k].host, "127.0.0.1")) - { - struct retro_system_info *info = runloop_get_libretro_system_info(); - - RARCH_LOG ("[Discovery] Query received on common interface: %s/%s (theirs / ours) \n", - reply_addr, interfaces.entries[k].host); - - /* Now build our response */ - buf[0] = '\0'; - content_crc = content_get_crc(); - - memset(&ad_packet_buffer, 0, sizeof(struct ad_packet)); - memcpy(&ad_packet_buffer, "RANS", 4); - - if (subsystem) - { - unsigned i; - - for (i = 0; i < subsystem->size; i++) - { - strlcat(buf, path_basename(subsystem->elems[i].data), NETPLAY_HOST_LONGSTR_LEN); - if (i < subsystem->size - 1) - strlcat(buf, "|", NETPLAY_HOST_LONGSTR_LEN); - } - strlcpy(ad_packet_buffer.content, buf, - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(ad_packet_buffer.subsystem_name, path_get(RARCH_PATH_SUBSYSTEM), - NETPLAY_HOST_STR_LEN); - } - else - { - strlcpy(ad_packet_buffer.content, !string_is_empty( - path_basename(path_get(RARCH_PATH_BASENAME))) - ? path_basename(path_get(RARCH_PATH_BASENAME)) : "N/A", - NETPLAY_HOST_LONGSTR_LEN); - strlcpy(ad_packet_buffer.subsystem_name, "N/A", NETPLAY_HOST_STR_LEN); - } - - strlcpy(ad_packet_buffer.address, interfaces.entries[k].host, - NETPLAY_HOST_STR_LEN); - ad_packet_buffer.protocol_version = - htonl(NETPLAY_PROTOCOL_VERSION); - ad_packet_buffer.port = htonl(netplay->tcp_port); - strlcpy(ad_packet_buffer.retroarch_version, PACKAGE_VERSION, - NETPLAY_HOST_STR_LEN); - strlcpy(ad_packet_buffer.nick, netplay->nick, NETPLAY_HOST_STR_LEN); - strlcpy(ad_packet_buffer.frontend, frontend, NETPLAY_HOST_STR_LEN); - - if (info) - { - strlcpy(ad_packet_buffer.core, info->library_name, - NETPLAY_HOST_STR_LEN); - strlcpy(ad_packet_buffer.core_version, info->library_version, - NETPLAY_HOST_STR_LEN); - } - - snprintf(s, sizeof(s), "%d", content_crc); - strlcpy(ad_packet_buffer.content_crc, s, - NETPLAY_HOST_STR_LEN); - - /* Build up the destination address*/ - snprintf(port_str, 6, "%hu", ntohs(((struct sockaddr_in*)(&their_addr))->sin_port)); - if (getaddrinfo_retro(reply_addr, port_str, &hints, &our_addr) < 0) - continue; - - RARCH_LOG ("[Discovery] Sending reply to %s \n", reply_addr); - - /* And send it */ - sendto(lan_ad_server_fd, (const char*)&ad_packet_buffer, - sizeof(struct ad_packet), 0, our_addr->ai_addr, our_addr->ai_addrlen); - freeaddrinfo_retro(our_addr); - } - else - continue; - } - else - continue; - } - } - } - net_ifinfo_free(&interfaces); - return true; -} -#endif diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_frontend.c similarity index 64% rename from network/netplay/netplay_io.c rename to network/netplay/netplay_frontend.c index a819fe37ed..99b43afdc3 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_frontend.c @@ -22,42 +22,156 @@ #include #include #include +#include #include -#include -#include +#include #include #include +#include +#include +#include -#include "netplay_private.h" -#include "netplay_discovery.h" +#include +#include + +#include "../../file_path_special.h" +#include "../../paths.h" +#include "../../content.h" + +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif #include "../../autosave.h" #include "../../configuration.h" +#include "../../command.h" +#include "../../content.h" #include "../../driver.h" #include "../../retroarch.h" -#include "../../command.h" +#include "../../version.h" +#include "../../verbosity.h" + #include "../../tasks/tasks_internal.h" #include "../../input/input_driver.h" +#ifdef HAVE_MENU +#include "../../menu/menu_input.h" +#endif + +#ifdef HAVE_DISCORD +#include "../discord.h" +#endif + +#include "netplay.h" +#include "netplay_discovery.h" +#include "netplay_private.h" + #if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) && !defined(_3DS) #define HAVE_INET6 1 #endif -#ifdef HAVE_DISCORD -#include "../discord.h" +#define RECV(buf, sz) \ + recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \ + if (recvd >= 0 && recvd < (ssize_t) (sz)) \ + { \ + netplay_recv_reset(&connection->recv_packet_buffer); \ + return true; \ + } \ + else if (recvd < 0) -/* TODO/FIXME - global public variable */ -extern bool discord_is_inited; -#endif +#define NETPLAY_MAGIC 0x52414E50 /* RANP */ + + +/* + * 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) + */ struct vote_count { uint16_t votes[32]; }; +struct nick_buf_s +{ + uint32_t cmd[2]; + char nick[NETPLAY_NICK_LEN]; +}; + +struct password_buf_s +{ + uint32_t cmd[2]; + char password[NETPLAY_PASS_HASH_LEN]; +}; + +struct info_buf_s +{ + uint32_t cmd[2]; + uint32_t content_crc; + char core_name[NETPLAY_NICK_LEN]; + char core_version[NETPLAY_NICK_LEN]; +}; + +#ifdef HAVE_NETPLAYDISCOVERY +struct ad_packet +{ + uint32_t header; + uint32_t protocol_version; + uint32_t port; + char address[NETPLAY_HOST_STR_LEN]; + char retroarch_version[NETPLAY_HOST_STR_LEN]; + char nick[NETPLAY_HOST_STR_LEN]; + char frontend[NETPLAY_HOST_STR_LEN]; + char core[NETPLAY_HOST_STR_LEN]; + char core_version[NETPLAY_HOST_STR_LEN]; + char content[NETPLAY_HOST_LONGSTR_LEN]; + char content_crc[NETPLAY_HOST_STR_LEN]; + char subsystem_name[NETPLAY_HOST_STR_LEN]; +}; +#endif + +/* Forward declarations */ +#ifdef HAVE_DISCORD +extern bool discord_is_inited; +#endif + +/* TODO/FIXME - globals */ +struct netplay_room *netplay_room_list = NULL; +int netplay_room_count = 0; +static netplay_t *handshake_password_netplay = NULL; +static unsigned long simple_rand_next = 1; + + +#ifdef HAVE_NETPLAYDISCOVERY +/* LAN discovery sockets */ +static int lan_ad_server_fd = -1; +static int lan_ad_client_fd = -1; + +/* Packet buffer for advertisement and responses */ +static struct ad_packet ad_packet_buffer; + +/* List of discovered hosts */ +static struct netplay_host_list discovered_hosts; + +static size_t discovered_hosts_allocated; +#endif + /* The mapping of keys from netplay (network) to libretro (host) */ const uint16_t netplay_key_ntoh_mapping[] = { (uint16_t) RETROK_UNKNOWN, @@ -72,6 +186,1574 @@ const uint16_t netplay_key_ntoh_mapping[] = { /* TODO/FIXME - static global variables */ static uint16_t netplay_mapping[RETROK_LAST]; +#ifdef HAVE_NETPLAYDISCOVERY +#ifdef HAVE_SOCKET_LEGACY + +#ifndef htons +/* The fact that I need to write this is deeply depressing */ +static int16_t htons_for_morons(int16_t value) +{ + union { + int32_t l; + int16_t s[2]; + } val; + val.l = htonl(value); + return val.s[1]; +} +#define htons htons_for_morons +#endif + +#endif + +static bool netplay_lan_ad_client(void) +{ + unsigned i; + fd_set fds; + socklen_t addr_size; + struct sockaddr their_addr; + struct timeval tmp_tv = {0}; + + if (lan_ad_client_fd < 0) + return false; + + their_addr.sa_family = 0; + for (i = 0; i < 14; i++) + their_addr.sa_data[i] = 0; + + /* Check for any ad queries */ + for (;;) + { + FD_ZERO(&fds); + FD_SET(lan_ad_client_fd, &fds); + if (socket_select(lan_ad_client_fd + 1, + &fds, NULL, NULL, &tmp_tv) <= 0) + break; + + if (!FD_ISSET(lan_ad_client_fd, &fds)) + break; + + /* Somebody queried, so check that it's valid */ + addr_size = sizeof(their_addr); + + if (recvfrom(lan_ad_client_fd, (char*)&ad_packet_buffer, + sizeof(struct ad_packet), 0, &their_addr, &addr_size) >= + (ssize_t) sizeof(struct ad_packet)) + { + struct netplay_host *host = NULL; + + /* Make sure it's a valid response */ + if (memcmp((void *) &ad_packet_buffer, "RANS", 4)) + continue; + + /* For this version */ + if (ntohl(ad_packet_buffer.protocol_version) + != NETPLAY_PROTOCOL_VERSION) + continue; + + /* And that we know how to handle it */ + if (their_addr.sa_family == AF_INET) + { + struct sockaddr_in *sin = NULL; + + RARCH_WARN ("[Discovery] Using IPv4 for discovery\n"); + sin = (struct sockaddr_in *) &their_addr; + sin->sin_port = htons(ntohl(ad_packet_buffer.port)); + + } +#ifdef HAVE_INET6 + else if (their_addr.sa_family == AF_INET6) + { + struct sockaddr_in6 *sin6 = NULL; + RARCH_WARN ("[Discovery] Using IPv6 for discovery\n"); + sin6 = (struct sockaddr_in6 *) &their_addr; + sin6->sin6_port = htons(ad_packet_buffer.port); + + } +#endif + else + continue; + + /* Allocate space for it */ + if (discovered_hosts.size >= discovered_hosts_allocated) + { + size_t allocated = discovered_hosts_allocated; + struct netplay_host *new_hosts = NULL; + + if (allocated == 0) + allocated = 2; + else + allocated *= 2; + + if (discovered_hosts.hosts) + new_hosts = (struct netplay_host *) + realloc(discovered_hosts.hosts, allocated * sizeof(struct + netplay_host)); + else + /* Should be equivalent to realloc, + * but I don't trust screwy libcs */ + new_hosts = (struct netplay_host *) + malloc(allocated * sizeof(struct netplay_host)); + + if (!new_hosts) + return false; + + discovered_hosts.hosts = new_hosts; + discovered_hosts_allocated = allocated; + } + + /* Get our host structure */ + host = &discovered_hosts.hosts[discovered_hosts.size++]; + + /* Copy in the response */ + memset(host, 0, sizeof(struct netplay_host)); + host->addr = their_addr; + host->addrlen = addr_size; + + host->port = ntohl(ad_packet_buffer.port); + + strlcpy(host->address, ad_packet_buffer.address, NETPLAY_HOST_STR_LEN); + strlcpy(host->nick, ad_packet_buffer.nick, NETPLAY_HOST_STR_LEN); + strlcpy(host->core, ad_packet_buffer.core, NETPLAY_HOST_STR_LEN); + strlcpy(host->retroarch_version, ad_packet_buffer.retroarch_version, + NETPLAY_HOST_STR_LEN); + strlcpy(host->core_version, ad_packet_buffer.core_version, + NETPLAY_HOST_STR_LEN); + strlcpy(host->content, ad_packet_buffer.content, + NETPLAY_HOST_LONGSTR_LEN); + strlcpy(host->subsystem_name, ad_packet_buffer.subsystem_name, + NETPLAY_HOST_LONGSTR_LEN); + strlcpy(host->frontend, ad_packet_buffer.frontend, + NETPLAY_HOST_STR_LEN); + + host->content_crc = + atoi(ad_packet_buffer.content_crc); + host->nick[NETPLAY_HOST_STR_LEN-1] = + host->core[NETPLAY_HOST_STR_LEN-1] = + host->core_version[NETPLAY_HOST_STR_LEN-1] = + host->content[NETPLAY_HOST_LONGSTR_LEN-1] = '\0'; + } + } + + return true; +} + +/** Initialize Netplay discovery (client) */ +bool init_netplay_discovery(void) +{ + struct addrinfo *addr = NULL; + int fd = socket_init((void **)&addr, 0, NULL, SOCKET_TYPE_DATAGRAM); + + if (fd < 0) + goto error; + + if (!socket_bind(fd, (void*)addr)) + { + socket_close(fd); + goto error; + } + + lan_ad_client_fd = fd; + freeaddrinfo_retro(addr); + return true; + +error: + if (addr) + freeaddrinfo_retro(addr); + RARCH_ERR("[Discovery] Failed to initialize netplay advertisement client socket.\n"); + return false; +} + +/** Deinitialize and free Netplay discovery */ +/* TODO/FIXME - this is apparently never called? */ +void deinit_netplay_discovery(void) +{ + if (lan_ad_client_fd >= 0) + { + socket_close(lan_ad_client_fd); + lan_ad_client_fd = -1; + } +} + +/** Discovery control */ +/* TODO/FIXME: implement net_ifinfo and ntohs for consoles */ +bool netplay_discovery_driver_ctl( + enum rarch_netplay_discovery_ctl_state state, void *data) +{ + int ret; + char port_str[6]; + unsigned k = 0; + + if (lan_ad_client_fd < 0) + return false; + + switch (state) + { + case RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY: + { + net_ifinfo_t interfaces; + struct addrinfo hints = {0}, *addr; + + if (!net_ifinfo_new(&interfaces)) + return false; + + /* Get the broadcast address (IPv4 only for now) */ + snprintf(port_str, 6, "%hu", (unsigned short) RARCH_DEFAULT_PORT); + if (getaddrinfo_retro("255.255.255.255", port_str, &hints, &addr) < 0) + return false; + + /* Make it broadcastable */ +#if defined(SOL_SOCKET) && defined(SO_BROADCAST) + { + int can_broadcast = 1; + if (setsockopt(lan_ad_client_fd, SOL_SOCKET, SO_BROADCAST, + (const char *)&can_broadcast, sizeof(can_broadcast)) < 0) + RARCH_WARN("[Discovery] Failed to set netplay discovery port to broadcast\n"); + } +#endif + + /* Put together the request */ + memcpy((void *) &ad_packet_buffer, "RANQ", 4); + ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION); + + for (k = 0; k < (unsigned)interfaces.size; k++) + { + strlcpy(ad_packet_buffer.address, interfaces.entries[k].host, + NETPLAY_HOST_STR_LEN); + + /* And send it off */ + ret = (int)sendto(lan_ad_client_fd, (const char *) &ad_packet_buffer, + sizeof(struct ad_packet), 0, addr->ai_addr, addr->ai_addrlen); + if (ret < (ssize_t) (2*sizeof(uint32_t))) + RARCH_WARN("[Discovery] Failed to send netplay discovery query (error: %d)\n", errno); + } + + freeaddrinfo_retro(addr); + net_ifinfo_free(&interfaces); + + break; + } + + case RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES: + if (!netplay_lan_ad_client()) + return false; + *((struct netplay_host_list **) data) = &discovered_hosts; + break; + + case RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES: + discovered_hosts.size = 0; + break; + + default: + return false; + } + return true; +} + +static bool init_lan_ad_server_socket(netplay_t *netplay, uint16_t port) +{ + struct addrinfo *addr = NULL; + int fd = socket_init((void **) &addr, port, NULL, SOCKET_TYPE_DATAGRAM); + + if (fd < 0) + goto error; + + if (!socket_bind(fd, (void*)addr)) + { + socket_close(fd); + goto error; + } + + lan_ad_server_fd = fd; + freeaddrinfo_retro(addr); + + return true; + +error: + if (addr) + freeaddrinfo_retro(addr); + return false; +} + +/** + * netplay_lan_ad_server + * + * Respond to any LAN ad queries that the netplay server has received. + */ +bool netplay_lan_ad_server(netplay_t *netplay) +{ + /* TODO/FIXME: implement net_ifinfo and ntohs for consoles */ + fd_set fds; + int ret; + unsigned i; + char buf[4096]; + net_ifinfo_t interfaces; + socklen_t addr_size; + char reply_addr[NETPLAY_HOST_STR_LEN], port_str[6]; + struct sockaddr their_addr; + struct timeval tmp_tv = {0}; + unsigned k = 0; + struct addrinfo *our_addr, hints = {0}; + struct string_list *subsystem = path_get_subsystem_list(); + + interfaces.entries = NULL; + interfaces.size = 0; + + their_addr.sa_family = 0; + for (i = 0; i < 14; i++) + their_addr.sa_data[i] = 0; + + if (!net_ifinfo_new(&interfaces)) + return false; + + if ( (lan_ad_server_fd < 0) + && !init_lan_ad_server_socket(netplay, RARCH_DEFAULT_PORT)) + { + RARCH_ERR("[Discovery] Failed to initialize netplay advertisement socket\n"); + return false; + } + + /* Check for any ad queries */ + for (;;) + { + FD_ZERO(&fds); + FD_SET(lan_ad_server_fd, &fds); + if (socket_select(lan_ad_server_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + break; + if (!FD_ISSET(lan_ad_server_fd, &fds)) + break; + + /* Somebody queried, so check that it's valid */ + addr_size = sizeof(their_addr); + ret = (int)recvfrom(lan_ad_server_fd, (char*)&ad_packet_buffer, + sizeof(struct ad_packet), 0, &their_addr, &addr_size); + if (ret >= (ssize_t) (2 * sizeof(uint32_t))) + { + char s[NETPLAY_HOST_STR_LEN]; + uint32_t content_crc = 0; + + /* Make sure it's a valid query */ + if (memcmp((void *) &ad_packet_buffer, "RANQ", 4)) + { + RARCH_LOG("[Discovery] Invalid query\n"); + continue; + } + + /* For this version */ + if (ntohl(ad_packet_buffer.protocol_version) != + NETPLAY_PROTOCOL_VERSION) + { + RARCH_LOG("[Discovery] Invalid protocol version\n"); + continue; + } + + if (!string_is_empty(ad_packet_buffer.address)) + strlcpy(reply_addr, ad_packet_buffer.address, NETPLAY_HOST_STR_LEN); + + for (k = 0; k < interfaces.size; k++) + { + char *p; + char sub[NETPLAY_HOST_STR_LEN]; + char frontend_architecture_tmp[32]; + char frontend[256]; + const frontend_ctx_driver_t *frontend_drv = + (const frontend_ctx_driver_t*) + frontend_driver_get_cpu_architecture_str( + frontend_architecture_tmp, sizeof(frontend_architecture_tmp)); + snprintf(frontend, sizeof(frontend), "%s %s", + frontend_drv->ident, frontend_architecture_tmp); + + p=strrchr(reply_addr,'.'); + if (p) + { + strlcpy(sub, reply_addr, p - reply_addr + 1); + if (strstr(interfaces.entries[k].host, sub) && + !strstr(interfaces.entries[k].host, "127.0.0.1")) + { + struct retro_system_info *info = runloop_get_libretro_system_info(); + + RARCH_LOG ("[Discovery] Query received on common interface: %s/%s (theirs / ours) \n", + reply_addr, interfaces.entries[k].host); + + /* Now build our response */ + buf[0] = '\0'; + content_crc = content_get_crc(); + + memset(&ad_packet_buffer, 0, sizeof(struct ad_packet)); + memcpy(&ad_packet_buffer, "RANS", 4); + + if (subsystem) + { + unsigned i; + + for (i = 0; i < subsystem->size; i++) + { + strlcat(buf, path_basename(subsystem->elems[i].data), NETPLAY_HOST_LONGSTR_LEN); + if (i < subsystem->size - 1) + strlcat(buf, "|", NETPLAY_HOST_LONGSTR_LEN); + } + strlcpy(ad_packet_buffer.content, buf, + NETPLAY_HOST_LONGSTR_LEN); + strlcpy(ad_packet_buffer.subsystem_name, path_get(RARCH_PATH_SUBSYSTEM), + NETPLAY_HOST_STR_LEN); + } + else + { + strlcpy(ad_packet_buffer.content, !string_is_empty( + path_basename(path_get(RARCH_PATH_BASENAME))) + ? path_basename(path_get(RARCH_PATH_BASENAME)) : "N/A", + NETPLAY_HOST_LONGSTR_LEN); + strlcpy(ad_packet_buffer.subsystem_name, "N/A", NETPLAY_HOST_STR_LEN); + } + + strlcpy(ad_packet_buffer.address, interfaces.entries[k].host, + NETPLAY_HOST_STR_LEN); + ad_packet_buffer.protocol_version = + htonl(NETPLAY_PROTOCOL_VERSION); + ad_packet_buffer.port = htonl(netplay->tcp_port); + strlcpy(ad_packet_buffer.retroarch_version, PACKAGE_VERSION, + NETPLAY_HOST_STR_LEN); + strlcpy(ad_packet_buffer.nick, netplay->nick, NETPLAY_HOST_STR_LEN); + strlcpy(ad_packet_buffer.frontend, frontend, NETPLAY_HOST_STR_LEN); + + if (info) + { + strlcpy(ad_packet_buffer.core, info->library_name, + NETPLAY_HOST_STR_LEN); + strlcpy(ad_packet_buffer.core_version, info->library_version, + NETPLAY_HOST_STR_LEN); + } + + snprintf(s, sizeof(s), "%d", content_crc); + strlcpy(ad_packet_buffer.content_crc, s, + NETPLAY_HOST_STR_LEN); + + /* Build up the destination address*/ + snprintf(port_str, 6, "%hu", ntohs(((struct sockaddr_in*)(&their_addr))->sin_port)); + if (getaddrinfo_retro(reply_addr, port_str, &hints, &our_addr) < 0) + continue; + + RARCH_LOG ("[Discovery] Sending reply to %s \n", reply_addr); + + /* And send it */ + sendto(lan_ad_server_fd, (const char*)&ad_packet_buffer, + sizeof(struct ad_packet), 0, our_addr->ai_addr, our_addr->ai_addrlen); + freeaddrinfo_retro(our_addr); + } + else + continue; + } + else + continue; + } + } + } + net_ifinfo_free(&interfaces); + return true; +} +#endif + +/* TODO/FIXME - replace netplay_log_connection with calls + * to inet_ntop_compat and move runloop message queue pushing + * outside */ +#if !defined(HAVE_SOCKET_LEGACY) && !defined(WIIU) && !defined(_3DS) +/* Custom inet_ntop. Win32 doesn't seem to support this ... */ +static void netplay_log_connection( + const struct sockaddr_storage *their_addr, + unsigned slot, const char *nick, char *s, size_t len) +{ + union + { + const struct sockaddr_storage *storage; + const struct sockaddr_in *v4; + const struct sockaddr_in6 *v6; + } u; + const char *str = NULL; + char buf_v4[INET_ADDRSTRLEN] = {0}; + char buf_v6[INET6_ADDRSTRLEN] = {0}; + + u.storage = their_addr; + + switch (their_addr->ss_family) + { + case AF_INET: + { + struct sockaddr_in in; + + memset(&in, 0, sizeof(in)); + + str = buf_v4; + in.sin_family = AF_INET; + memcpy(&in.sin_addr, &u.v4->sin_addr, sizeof(struct in_addr)); + + getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), + buf_v4, sizeof(buf_v4), + NULL, 0, NI_NUMERICHOST); + } + break; + case AF_INET6: + { + struct sockaddr_in6 in; + memset(&in, 0, sizeof(in)); + + str = buf_v6; + in.sin6_family = AF_INET6; + memcpy(&in.sin6_addr, &u.v6->sin6_addr, sizeof(struct in6_addr)); + + getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6), + buf_v6, sizeof(buf_v6), NULL, 0, NI_NUMERICHOST); + } + break; + default: + break; + } + + if (str) + snprintf(s, len, msg_hash_to_str(MSG_GOT_CONNECTION_FROM_NAME), + nick, str); + else + snprintf(s, len, msg_hash_to_str(MSG_GOT_CONNECTION_FROM), + nick); +} +#else +static void netplay_log_connection( + const struct sockaddr_storage *their_addr, + unsigned slot, const char *nick, char *s, size_t len) +{ + /* Stub code - will need to be implemented */ + snprintf(s, len, msg_hash_to_str(MSG_GOT_CONNECTION_FROM), + nick); +} +#endif + +/** + * netplay_impl_magic: + * + * A pseudo-hash of the RetroArch and Netplay version, so only compatible + * versions play together. + */ +static uint32_t netplay_impl_magic(void) +{ + size_t i; + uint32_t res = 0; + const char *ver = PACKAGE_VERSION; + size_t len = strlen(ver); + + for (i = 0; i < len; i++) + res ^= ver[i] << (i & 0xf); + + res ^= NETPLAY_PROTOCOL_VERSION << (i & 0xf); + + 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); +} + +static int simple_rand(void) +{ + simple_rand_next = simple_rand_next * 1103515245 + 12345; + return((unsigned)(simple_rand_next / 65536) % 32768); +} + +static void simple_srand(unsigned int seed) +{ + simple_rand_next = seed; +} + +static uint32_t simple_rand_uint32(void) +{ + uint32_t parts[3]; + parts[0] = simple_rand(); + parts[1] = simple_rand(); + parts[2] = simple_rand(); + return ((parts[0] << 30) + + (parts[1] << 15) + + parts[2]); +} + +/** + * netplay_handshake_init_send + * + * Initialize our handshake and send the first part of the handshake protocol. + */ +bool netplay_handshake_init_send(netplay_t *netplay, + struct netplay_connection *connection) +{ + uint32_t header[6]; + unsigned conn_salt = 0; + settings_t *settings = config_get_ptr(); + + header[0] = htonl(NETPLAY_MAGIC); + header[1] = htonl(netplay_platform_magic()); + header[2] = htonl(NETPLAY_COMPRESSION_SUPPORTED); + header[3] = 0; + header[4] = htonl(NETPLAY_PROTOCOL_VERSION); + header[5] = htonl(netplay_impl_magic()); + + if (netplay->is_server && + (settings->paths.netplay_password[0] || + settings->paths.netplay_spectate_password[0])) + { + /* Demand a password */ + if (simple_rand_next == 1) + simple_srand((unsigned int) time(NULL)); + connection->salt = simple_rand_uint32(); + if (connection->salt == 0) + connection->salt = 1; + conn_salt = connection->salt; + } + + header[3] = htonl(conn_salt); + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, + sizeof(header)) || + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) + return false; + + return true; +} + +#ifdef HAVE_MENU +static void handshake_password(void *ignore, const char *line) +{ + struct password_buf_s password_buf; + char password[8+NETPLAY_PASS_LEN]; /* 8 for salt, 128 for password */ + char hash[NETPLAY_PASS_HASH_LEN+1]; /* + NULL terminator */ + netplay_t *netplay = handshake_password_netplay; + struct netplay_connection *connection = &netplay->connections[0]; + + snprintf(password, sizeof(password), "%08X", connection->salt); + if (!string_is_empty(line)) + 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(hash, (uint8_t *) password, strlen(password)); + memcpy(password_buf.password, hash, NETPLAY_PASS_HASH_LEN); + + /* We have no way to handle an error here, so we'll let the next function error out */ + if (netplay_send(&connection->send_packet_buffer, connection->fd, &password_buf, sizeof(password_buf))) + netplay_send_flush(&connection->send_packet_buffer, connection->fd, false); + +#ifdef HAVE_MENU + menu_input_dialog_end(); + retroarch_menu_running_finished(false); +#endif +} +#endif + +/** + * netplay_handshake_init + * + * Data receiver for the initial part of the handshake, i.e., waiting for the + * netplay header. + */ +bool netplay_handshake_init(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + ssize_t recvd; + struct nick_buf_s nick_buf; + uint32_t header[6]; + uint32_t local_pmagic = 0; + uint32_t remote_pmagic = 0; + uint32_t remote_version = 0; + uint32_t compression = 0; + struct compression_transcoder *ctrans = NULL; + const char *dmsg = NULL; + + memset(header, 0, sizeof(header)); + + RECV(header, sizeof(uint32_t)) + { + dmsg = msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT); + goto error; + } + + if (ntohl(header[0]) != NETPLAY_MAGIC) + { + dmsg = msg_hash_to_str(MSG_NETPLAY_NOT_RETROARCH); + goto error; + } + + RECV(header + 1, sizeof(header) - sizeof(uint32_t)) + { + dmsg = msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT); + goto error; + } + + remote_version = ntohl(header[4]); + if (remote_version < NETPLAY_PROTOCOL_VERSION) + { + dmsg = msg_hash_to_str(MSG_NETPLAY_OUT_OF_DATE); + goto error; + } + + if (ntohl(header[5]) != netplay_impl_magic()) + { + /* We allow the connection but warn that this could cause issues. */ + dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_VERSIONS); + RARCH_WARN("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + + /* We only care about platform magic if our core is quirky */ + local_pmagic = netplay_platform_magic(); + remote_pmagic = ntohl(header[1]); + + if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) && + netplay_endian_mismatch(local_pmagic, remote_pmagic)) + { + RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n"); + dmsg = msg_hash_to_str(MSG_NETPLAY_ENDIAN_DEPENDENT); + goto error; + } + if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) && + (local_pmagic != remote_pmagic)) + { + RARCH_ERR("Platform mismatch with a platform-sensitive core.\n"); + dmsg = msg_hash_to_str(MSG_NETPLAY_PLATFORM_DEPENDENT); + goto error; + } + + /* Check what compression is supported */ + compression = ntohl(header[2]); + compression &= NETPLAY_COMPRESSION_SUPPORTED; + + if (compression & NETPLAY_COMPRESSION_ZLIB) + { + ctrans = &netplay->compress_zlib; + if (!ctrans->compression_backend) + { + ctrans->compression_backend = + trans_stream_get_zlib_deflate_backend(); + if (!ctrans->compression_backend) + ctrans->compression_backend = trans_stream_get_pipe_backend(); + } + connection->compression_supported = NETPLAY_COMPRESSION_ZLIB; + } + else + { + ctrans = &netplay->compress_nil; + if (!ctrans->compression_backend) + { + ctrans->compression_backend = + trans_stream_get_pipe_backend(); + } + connection->compression_supported = 0; + } + + if (!ctrans->decompression_backend) + ctrans->decompression_backend = ctrans->compression_backend->reverse; + + /* Allocate our compression stream */ + if (!ctrans->compression_stream) + { + ctrans->compression_stream = ctrans->compression_backend->stream_new(); + ctrans->decompression_stream = ctrans->decompression_backend->stream_new(); + } + if (!ctrans->compression_stream || !ctrans->decompression_stream) + { + RARCH_ERR("Failed to allocate compression transcoder!\n"); + return false; + } + + /* If a password is demanded, ask for it */ + if (!netplay->is_server && (connection->salt = ntohl(header[3]))) + { +#ifdef HAVE_MENU + menu_input_ctx_line_t line; + retroarch_menu_running(); +#endif + + handshake_password_netplay = netplay; + +#ifdef HAVE_MENU + memset(&line, 0, sizeof(line)); + line.label = msg_hash_to_str(MSG_NETPLAY_ENTER_PASSWORD); + line.label_setting = "no_setting"; + line.cb = handshake_password; + if (!menu_input_dialog_start(&line)) + return false; +#endif + } + + /* 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)); + if (!netplay_send(&connection->send_packet_buffer, connection->fd, &nick_buf, + sizeof(nick_buf)) || + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) + return false; + + /* Move on to the next mode */ + connection->mode = NETPLAY_CONNECTION_PRE_NICK; + *had_input = true; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; + +error: + if (dmsg) + { + RARCH_ERR("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + return false; +} + +static void netplay_handshake_ready(netplay_t *netplay, + struct netplay_connection *connection) +{ + char msg[512]; + msg[0] = '\0'; + + if (netplay->is_server) + { + unsigned slot = (unsigned)(connection - netplay->connections); + + netplay_log_connection(&connection->addr, + slot, connection->nick, msg, sizeof(msg)); + + RARCH_LOG("%s %u\n", msg_hash_to_str(MSG_CONNECTION_SLOT), slot); + + /* Send them the savestate */ + if (!(netplay->quirks & + (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) + netplay->force_send_savestate = true; + } + else + { + netplay->is_connected = true; + snprintf(msg, sizeof(msg), "%s: \"%s\"", + msg_hash_to_str(MSG_CONNECTED_TO), + connection->nick); + } + + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + + /* Unstall if we were waiting for this */ + if (netplay->stall == NETPLAY_STALL_NO_CONNECTION) + netplay->stall = NETPLAY_STALL_NONE; +} + +/** + * netplay_handshake_info + * + * Send an INFO command. + */ +static bool netplay_handshake_info(netplay_t *netplay, + struct netplay_connection *connection) +{ + struct info_buf_s info_buf; + uint32_t content_crc = 0; + struct retro_system_info *system = runloop_get_libretro_system_info(); + + memset(&info_buf, 0, sizeof(info_buf)); + info_buf.cmd[0] = htonl(NETPLAY_CMD_INFO); + info_buf.cmd[1] = htonl(sizeof(info_buf) - 2*sizeof(uint32_t)); + + /* Get our core info */ + if (system) + { + strlcpy(info_buf.core_name, + system->library_name, sizeof(info_buf.core_name)); + strlcpy(info_buf.core_version, + system->library_version, sizeof(info_buf.core_version)); + } + else + { + strlcpy(info_buf.core_name, + "UNKNOWN", sizeof(info_buf.core_name)); + strlcpy(info_buf.core_version, + "UNKNOWN", sizeof(info_buf.core_version)); + } + + /* Get our content CRC */ + content_crc = content_get_crc(); + + if (content_crc != 0) + info_buf.content_crc = htonl(content_crc); + + /* Send it off and wait for info back */ + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + &info_buf, sizeof(info_buf)) || + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, + false)) + return false; + + connection->mode = NETPLAY_CONNECTION_PRE_INFO; + return true; +} + +/** + * netplay_handshake_sync + * + * Send a SYNC command. + */ +static bool netplay_handshake_sync(netplay_t *netplay, + struct netplay_connection *connection) +{ + /* If we're the server, now we send sync info */ + size_t i; + int matchct; + uint32_t cmd[4]; + retro_ctx_memory_info_t mem_info; + uint32_t client_num = 0; + uint32_t device = 0; + size_t nicklen, nickmangle = 0; + bool nick_matched = false; + +#ifdef HAVE_THREADS + autosave_lock(); +#endif + mem_info.id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&mem_info); +#ifdef HAVE_THREADS + autosave_unlock(); +#endif + + /* Send basic sync info */ + cmd[0] = htonl(NETPLAY_CMD_SYNC); + cmd[1] = htonl(2*sizeof(uint32_t) + /* Controller devices */ + + MAX_INPUT_DEVICES*sizeof(uint32_t) + + /* Share modes */ + + MAX_INPUT_DEVICES*sizeof(uint8_t) + + /* Device-client mapping */ + + MAX_INPUT_DEVICES*sizeof(uint32_t) + + /* Client nick */ + + NETPLAY_NICK_LEN + + /* And finally, sram */ + + mem_info.size); + cmd[2] = htonl(netplay->self_frame_count); + client_num = (uint32_t)(connection - netplay->connections + 1); + + if (netplay->local_paused || netplay->remote_paused) + client_num |= NETPLAY_CMD_SYNC_BIT_PAUSED; + + cmd[3] = htonl(client_num); + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, + sizeof(cmd))) + return false; + + /* Now send the device info */ + for (i = 0; i < MAX_INPUT_DEVICES; i++) + { + device = htonl(netplay->config_devices[i]); + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + &device, sizeof(device))) + return false; + } + + /* Then the share mode */ + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + netplay->device_share_modes, sizeof(netplay->device_share_modes))) + return false; + + /* Then the device-client mapping */ + for (i = 0; i < MAX_INPUT_DEVICES; i++) + { + device = htonl(netplay->device_clients[i]); + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + &device, sizeof(device))) + return false; + } + + /* Now see if we need to mangle their nick */ + nicklen = strlen(connection->nick); + if (nicklen > NETPLAY_NICK_LEN - 5) + nickmangle = NETPLAY_NICK_LEN - 5; + else + nickmangle = nicklen; + matchct = 1; + do + { + nick_matched = false; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *sc = &netplay->connections[i]; + if (sc == connection) + continue; + if (sc->active && + sc->mode >= NETPLAY_CONNECTION_CONNECTED && + !strncmp(connection->nick, sc->nick, NETPLAY_NICK_LEN)) + { + nick_matched = true; + break; + } + } + if (!strncmp(connection->nick, netplay->nick, NETPLAY_NICK_LEN)) + nick_matched = true; + + if (nick_matched) + { + /* Somebody has this nick, make a new one! */ + snprintf(connection->nick + nickmangle, + NETPLAY_NICK_LEN - nickmangle, " (%d)", ++matchct); + connection->nick[NETPLAY_NICK_LEN - 1] = '\0'; + } + } while (nick_matched); + + /* Send the nick */ + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + connection->nick, NETPLAY_NICK_LEN)) + return false; + + /* And finally, the SRAM */ +#ifdef HAVE_THREADS + autosave_lock(); +#endif + 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)) + { +#ifdef HAVE_THREADS + autosave_unlock(); +#endif + return false; + } +#ifdef HAVE_THREADS + autosave_unlock(); +#endif + + /* Now we're ready! */ + connection->mode = NETPLAY_CONNECTION_SPECTATING; + netplay_handshake_ready(netplay, connection); + + return true; +} + +/** + * netplay_handshake_pre_nick + * + * Data receiver for the second stage of handshake, receiving the other side's + * nickname. + */ +static bool netplay_handshake_pre_nick(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + 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, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + return false; + } + + strlcpy(connection->nick, nick_buf.nick, + (sizeof(connection->nick) < sizeof(nick_buf.nick)) ? + sizeof(connection->nick) : sizeof(nick_buf.nick)); + + if (netplay->is_server) + { + settings_t *settings = config_get_ptr(); + + /* There's a password, so just put them in PRE_PASSWORD mode */ + if ( settings->paths.netplay_password[0] || + settings->paths.netplay_spectate_password[0]) + connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD; + else + { + connection->can_play = true; + if (!netplay_handshake_info(netplay, connection)) + return false; + connection->mode = NETPLAY_CONNECTION_PRE_INFO; + } + } + /* Client needs to wait for INFO */ + else + connection->mode = NETPLAY_CONNECTION_PRE_INFO; + + *had_input = true; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; +} + +/** + * netplay_handshake_pre_password + * + * Data receiver for the third, optional stage of server handshake, receiving + * the password and sending core/content info. + */ +static bool netplay_handshake_pre_password(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + struct password_buf_s password_buf; + char password[8+NETPLAY_PASS_LEN]; /* 8 for salt */ + char hash[NETPLAY_PASS_HASH_LEN+1]; /* + NULL terminator */ + ssize_t recvd; + char msg[512]; + bool correct = false; + settings_t *settings = config_get_ptr(); + + msg[0] = '\0'; + + RECV(&password_buf, sizeof(password_buf)) {} + + /* Expecting only a password command */ + 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, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + return false; + } + + /* Calculate the correct password hash(es) and compare */ + correct = false; + snprintf(password, sizeof(password), "%08X", connection->salt); + + if (settings->paths.netplay_password[0]) + { + strlcpy(password + 8, + settings->paths.netplay_password, sizeof(password)-8); + + sha256_hash(hash, (uint8_t *) password, strlen(password)); + + if (!memcmp(password_buf.password, hash, NETPLAY_PASS_HASH_LEN)) + { + correct = true; + connection->can_play = true; + } + } + if (settings->paths.netplay_spectate_password[0]) + { + strlcpy(password + 8, + settings->paths.netplay_spectate_password, sizeof(password)-8); + + sha256_hash(hash, (uint8_t *) password, strlen(password)); + + if (!memcmp(password_buf.password, hash, NETPLAY_PASS_HASH_LEN)) + correct = true; + } + + /* Just disconnect if it was wrong */ + if (!correct) + return false; + + /* Otherwise, exchange info */ + if (!netplay_handshake_info(netplay, connection)) + return false; + + *had_input = true; + connection->mode = NETPLAY_CONNECTION_PRE_INFO; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; +} + +/** + * netplay_handshake_pre_info + * + * Data receiver for the third stage of server handshake, receiving + * the password. + */ +static bool netplay_handshake_pre_info(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + struct info_buf_s info_buf; + uint32_t cmd_size; + ssize_t recvd; + uint32_t content_crc = 0; + const char *dmsg = NULL; + struct retro_system_info *system = runloop_get_libretro_system_info(); + + RECV(&info_buf, sizeof(info_buf.cmd)) {} + + if (recvd < 0 || + ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO) + { + RARCH_ERR("Failed to receive netplay info.\n"); + return false; + } + + cmd_size = ntohl(info_buf.cmd[1]); + if (cmd_size != sizeof(info_buf) - 2*sizeof(uint32_t)) + { + /* Either the host doesn't have anything loaded, or this is just screwy */ + if (cmd_size != 0) + { + /* Huh? */ + RARCH_ERR("Invalid NETPLAY_CMD_INFO payload size.\n"); + return false; + } + + /* Send our info and hope they load it! */ + if (!netplay_handshake_info(netplay, connection)) + return false; + + *had_input = true; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; + } + + RECV(&info_buf.content_crc, cmd_size) + { + RARCH_ERR("Failed to receive netplay info payload.\n"); + return false; + } + + /* Check the core info */ + if (system) + { + if (strncmp(info_buf.core_name, + system->library_name, sizeof(info_buf.core_name))) + { + /* Wrong core! */ + dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_CORES); + RARCH_ERR("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + /* FIXME: Should still send INFO, so the other side knows what's what */ + return false; + } + if (strncmp(info_buf.core_version, + system->library_version, sizeof(info_buf.core_version))) + { + dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_CORE_VERSIONS); + RARCH_WARN("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + } + + /* Check the content CRC */ + content_crc = content_get_crc(); + + if (content_crc != 0) + { + if (ntohl(info_buf.content_crc) != content_crc) + { + dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); + RARCH_WARN("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + } + + /* Now switch to the right mode */ + if (netplay->is_server) + { + if (!netplay_handshake_sync(netplay, connection)) + return false; + } + else + { + if (!netplay_handshake_info(netplay, connection)) + return false; + connection->mode = NETPLAY_CONNECTION_PRE_SYNC; + } + + *had_input = true; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; +} + +/** + * netplay_handshake_pre_sync + * + * Data receiver for the client's third handshake stage, receiving the + * synchronization information. + */ +static bool netplay_handshake_pre_sync(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + uint32_t cmd[2]; + uint32_t new_frame_count, client_num; + uint32_t device; + uint32_t local_sram_size, remote_sram_size; + size_t i, j; + ssize_t recvd; + retro_ctx_controller_info_t pad; + char new_nick[NETPLAY_NICK_LEN]; + retro_ctx_memory_info_t mem_info; + + RECV(cmd, sizeof(cmd)) + { + const char *msg = msg_hash_to_str(MSG_NETPLAY_INCORRECT_PASSWORD); + RARCH_ERR("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + return false; + } + + /* Only expecting a sync command */ + if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || + ntohl(cmd[1]) < (2+2*MAX_INPUT_DEVICES)*sizeof(uint32_t) + (MAX_INPUT_DEVICES)*sizeof(uint8_t) + + NETPLAY_NICK_LEN) + { + RARCH_ERR("%s\n", + msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); + return false; + } + + /* Get the frame count */ + RECV(&new_frame_count, sizeof(new_frame_count)) + return false; + new_frame_count = ntohl(new_frame_count); + + /* Get our client number and pause mode */ + RECV(&client_num, sizeof(client_num)) + return false; + client_num = ntohl(client_num); + if (client_num & NETPLAY_CMD_SYNC_BIT_PAUSED) + { + netplay->remote_paused = true; + client_num ^= NETPLAY_CMD_SYNC_BIT_PAUSED; + } + netplay->self_client_num = client_num; + + /* Set our frame counters as requested */ + netplay->self_frame_count = netplay->run_frame_count = + netplay->other_frame_count = netplay->unread_frame_count = + netplay->server_frame_count = new_frame_count; + + /* And clear out the framebuffer */ + for (i = 0; i < netplay->buffer_size; i++) + { + struct delta_frame *ptr = &netplay->buffer[i]; + + ptr->used = false; + + if (i == netplay->self_ptr) + { + /* Clear out any current data but still use this frame */ + if (!netplay_delta_frame_ready(netplay, ptr, 0)) + return false; + + ptr->frame = new_frame_count; + ptr->have_local = true; + netplay->run_ptr = netplay->other_ptr = netplay->unread_ptr = + netplay->server_ptr = i; + + } + } + for (i = 0; i < MAX_CLIENTS; i++) + { + netplay->read_ptr[i] = netplay->self_ptr; + netplay->read_frame_count[i] = netplay->self_frame_count; + } + + /* Get and set each input device */ + for (i = 0; i < MAX_INPUT_DEVICES; i++) + { + RECV(&device, sizeof(device)) + return false; + + pad.port = (unsigned)i; + pad.device = ntohl(device); + netplay->config_devices[i] = pad.device; + if ((pad.device&RETRO_DEVICE_MASK) == RETRO_DEVICE_KEYBOARD) + { + netplay->have_updown_device = true; + netplay_key_hton_init(); + } + + core_set_controller_port_device(&pad); + } + + /* Get the share modes */ + RECV(netplay->device_share_modes, sizeof(netplay->device_share_modes)) + return false; + + /* Get the client-controller mapping */ + netplay->connected_players = + netplay->connected_slaves = + netplay->self_devices = 0; + for (i = 0; i < MAX_CLIENTS; i++) + netplay->client_devices[i] = 0; + for (i = 0; i < MAX_INPUT_DEVICES; i++) + { + RECV(&device, sizeof(device)) + return false; + device = ntohl(device); + + netplay->device_clients[i] = device; + netplay->connected_players |= device; + for (j = 0; j < MAX_CLIENTS; j++) + { + if (device & (1<client_devices[j] |= 1<nick, new_nick, NETPLAY_NICK_LEN)) + { + char msg[512]; + strlcpy(netplay->nick, new_nick, NETPLAY_NICK_LEN); + snprintf(msg, sizeof(msg), + msg_hash_to_str(MSG_NETPLAY_CHANGED_NICK), netplay->nick); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + + /* Now check the SRAM */ +#ifdef HAVE_THREADS + autosave_lock(); +#endif + mem_info.id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&mem_info); + + local_sram_size = (unsigned)mem_info.size; + remote_sram_size = ntohl(cmd[1]) - + (2+2*MAX_INPUT_DEVICES)*sizeof(uint32_t) - (MAX_INPUT_DEVICES)*sizeof(uint8_t) - NETPLAY_NICK_LEN; + + 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)); +#ifdef HAVE_THREADS + autosave_unlock(); +#endif + 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)); +#ifdef HAVE_THREADS + autosave_unlock(); +#endif + return false; + } + if (remote_sram_size > sizeof(uint32_t)) + remote_sram_size -= sizeof(uint32_t); + else + remote_sram_size = 0; + } + + } +#ifdef HAVE_THREADS + autosave_unlock(); +#endif + + /* We're ready! */ + *had_input = true; + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + connection->mode = NETPLAY_CONNECTION_PLAYING; + netplay_handshake_ready(netplay, connection); + netplay_recv_flush(&connection->recv_packet_buffer); + + /* Ask to switch to playing mode if we should */ + { + settings_t *settings = config_get_ptr(); + if (!settings->bools.netplay_start_as_spectator) + return netplay_cmd_mode(netplay, NETPLAY_CONNECTION_PLAYING); + } + + return true; +} + +/** + * netplay_handshake + * + * Data receiver for all handshake states. + */ +bool netplay_handshake(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + bool ret = false; + + switch (connection->mode) + { + case NETPLAY_CONNECTION_INIT: + ret = netplay_handshake_init(netplay, connection, had_input); + break; + case NETPLAY_CONNECTION_PRE_NICK: + ret = netplay_handshake_pre_nick(netplay, connection, had_input); + break; + case NETPLAY_CONNECTION_PRE_PASSWORD: + ret = netplay_handshake_pre_password(netplay, connection, had_input); + break; + case NETPLAY_CONNECTION_PRE_INFO: + ret = netplay_handshake_pre_info(netplay, connection, had_input); + break; + case NETPLAY_CONNECTION_PRE_SYNC: + ret = netplay_handshake_pre_sync(netplay, connection, had_input); + break; + case NETPLAY_CONNECTION_NONE: + default: + return false; + } + + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !netplay_send_cur_input(netplay, connection)) + return false; + + return ret; +} + /* The mapping of keys from libretro (host) to netplay (network) */ uint32_t netplay_key_hton(unsigned key) { @@ -4282,3 +5964,725 @@ void netplay_free(netplay_t *netplay) free(netplay); } + +/** + * netplay_send_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded + * @cx : compression type + * @z : compression backend to use + * + * Send a loaded savestate to those connected peers using the given compression + * scheme. + */ +static void netplay_send_savestate(netplay_t *netplay, + retro_ctx_serialize_info_t *serial_info, uint32_t cx, + struct compression_transcoder *z) +{ + uint32_t header[4]; + uint32_t rd, wn; + size_t i; + + /* Compress it */ + z->compression_backend->set_in(z->compression_stream, + (const uint8_t*)serial_info->data_const, (uint32_t)serial_info->size); + z->compression_backend->set_out(z->compression_stream, + netplay->zbuffer, (uint32_t)netplay->zbuffer_size); + if (!z->compression_backend->trans(z->compression_stream, true, &rd, + &wn, NULL)) + { + /* Catastrophe! */ + for (i = 0; i < netplay->connections_size; i++) + netplay_hangup(netplay, &netplay->connections[i]); + return; + } + + /* Send it to relevant peers */ + header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); + header[1] = htonl(wn + 2*sizeof(uint32_t)); + header[2] = htonl(netplay->run_frame_count); + header[3] = htonl(serial_info->size); + + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (!connection->active || + connection->mode < NETPLAY_CONNECTION_CONNECTED || + connection->compression_supported != cx) continue; + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, + sizeof(header)) || + !netplay_send(&connection->send_packet_buffer, connection->fd, + netplay->zbuffer, wn)) + netplay_hangup(netplay, connection); + } +} + +void netplay_frontend_paused(netplay_t *netplay, bool paused) +{ + size_t i; + uint32_t paused_ct = 0; + + netplay->local_paused = paused; + + /* Communicating this is a bit odd: If exactly one other connection is + * paused, then we must tell them that we're unpaused, as from their + * perspective we are. If more than one other connection is paused, then our + * status as proxy means we are NOT unpaused to either of them. */ + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->paused) + paused_ct++; + } + + if (paused_ct > 1) + return; + + /* Send our unpaused status. Must send manually because we must immediately + * flush the buffer: If we're paused, we won't be polled. */ + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if ( connection->active + && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + { + if (paused) + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_PAUSE, + netplay->nick, NETPLAY_NICK_LEN); + else + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_RESUME, + NULL, 0); + + /* We're not going to be polled, so we need to + * flush this command now */ + netplay_send_flush(&connection->send_packet_buffer, + connection->fd, true); + } + } +} + +/** + * netplay_force_future + * @netplay : pointer to netplay object + * + * Force netplay to ignore all past input, typically because we've just loaded + * a state or reset. + */ +static void netplay_force_future(netplay_t *netplay) +{ + /* Wherever we're inputting, that's where we consider our state to be loaded */ + netplay->run_ptr = netplay->self_ptr; + netplay->run_frame_count = netplay->self_frame_count; + + /* We need to ignore any intervening data from the other side, + * and never rewind past this */ + netplay_update_unread_ptr(netplay); + + if (netplay->unread_frame_count < netplay->run_frame_count) + { + uint32_t client; + for (client = 0; client < MAX_CLIENTS; client++) + { + if (!(netplay->connected_players & (1 << client))) + continue; + + if (netplay->read_frame_count[client] < netplay->run_frame_count) + { + netplay->read_ptr[client] = netplay->run_ptr; + netplay->read_frame_count[client] = netplay->run_frame_count; + } + } + if (netplay->server_frame_count < netplay->run_frame_count) + { + netplay->server_ptr = netplay->run_ptr; + netplay->server_frame_count = netplay->run_frame_count; + } + netplay_update_unread_ptr(netplay); + } + if (netplay->other_frame_count < netplay->run_frame_count) + { + netplay->other_ptr = netplay->run_ptr; + netplay->other_frame_count = netplay->run_frame_count; + } +} + + +void netplay_core_reset(netplay_t *netplay) +{ + size_t i; + uint32_t cmd[3]; + + /* Ignore past input */ + netplay_force_future(netplay); + + /* Request that our peers reset */ + cmd[0] = htonl(NETPLAY_CMD_RESET); + cmd[1] = htonl(sizeof(uint32_t)); + cmd[2] = htonl(netplay->self_frame_count); + + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (!connection->active || + connection->mode < NETPLAY_CONNECTION_CONNECTED) continue; + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, + sizeof(cmd))) + netplay_hangup(netplay, connection); + } +} + +void netplay_load_savestate(netplay_t *netplay, + retro_ctx_serialize_info_t *serial_info, bool save) +{ + retro_ctx_serialize_info_t tmp_serial_info; + + netplay_force_future(netplay); + + /* Record it in our own buffer */ + if (save || !serial_info) + { + /* TODO/FIXME: This is a critical failure! */ + if (!netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->run_ptr], netplay->run_frame_count)) + return; + + if (!serial_info) + { + tmp_serial_info.size = netplay->state_size; + tmp_serial_info.data = netplay->buffer[netplay->run_ptr].state; + if (!core_serialize(&tmp_serial_info)) + return; + tmp_serial_info.data_const = tmp_serial_info.data; + serial_info = &tmp_serial_info; + } + else + { + if (serial_info->size <= netplay->state_size) + memcpy(netplay->buffer[netplay->run_ptr].state, + serial_info->data_const, serial_info->size); + } + } + + /* Don't send it if we're expected to be desynced */ + if (netplay->desync) + return; + + /* If we can't send it to the peer, loading a state was a bad idea */ + if (netplay->quirks & ( + NETPLAY_QUIRK_NO_SAVESTATES + | NETPLAY_QUIRK_NO_TRANSMISSION)) + return; + + /* Send this to every peer */ + if (netplay->compress_nil.compression_backend) + netplay_send_savestate(netplay, serial_info, 0, &netplay->compress_nil); + if (netplay->compress_zlib.compression_backend) + netplay_send_savestate(netplay, serial_info, NETPLAY_COMPRESSION_ZLIB, + &netplay->compress_zlib); +} + +void netplay_toggle_play_spectate(netplay_t *netplay) +{ + switch (netplay->self_mode) + { + case NETPLAY_CONNECTION_PLAYING: + case NETPLAY_CONNECTION_SLAVE: + /* Switch to spectator mode immediately */ + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING); + break; + case NETPLAY_CONNECTION_SPECTATING: + /* Switch only after getting permission */ + netplay_cmd_mode(netplay, NETPLAY_CONNECTION_PLAYING); + break; + default: + break; + } +} + +int16_t netplay_input_state(netplay_t *netplay, + unsigned port, unsigned device, + unsigned idx, unsigned id) +{ + struct delta_frame *delta; + netplay_input_state_t istate; + const uint32_t *curr_input_state = NULL; + size_t ptr = + netplay->is_replay + ? netplay->replay_ptr + : netplay->run_ptr; + + if (port >= MAX_INPUT_DEVICES) + return 0; + + /* If the port doesn't seem to correspond to the device, "correct" it. This + * is common with devices that typically only have one instance, such as + * keyboards, mice and lightguns. */ + if (device != RETRO_DEVICE_JOYPAD && + (netplay->config_devices[port]&RETRO_DEVICE_MASK) != device) + { + for (port = 0; port < MAX_INPUT_DEVICES; port++) + { + if ((netplay->config_devices[port]&RETRO_DEVICE_MASK) == device) + break; + } + if (port == MAX_INPUT_DEVICES) + return 0; + } + + delta = &netplay->buffer[ptr]; + istate = delta->resolved_input[port]; + if (!istate || !istate->used || istate->size == 0) + return 0; + + curr_input_state = istate->data; + + switch (device) + { + case RETRO_DEVICE_JOYPAD: + if (id == RETRO_DEVICE_ID_JOYPAD_MASK) + return curr_input_state[0]; + return ((1 << id) & curr_input_state[0]) ? 1 : 0; + + case RETRO_DEVICE_ANALOG: + if (istate->size == 3) + { + uint32_t state = curr_input_state[1 + idx]; + return (int16_t)(uint16_t)(state >> (id * 16)); + } + break; + case RETRO_DEVICE_MOUSE: + case RETRO_DEVICE_LIGHTGUN: + if (istate->size == 2) + { + if (id <= RETRO_DEVICE_ID_MOUSE_Y) + return (int16_t)(uint16_t)(curr_input_state[1] >> (id * 16)); + return ((1 << id) & curr_input_state[0]) ? 1 : 0; + } + break; + case RETRO_DEVICE_KEYBOARD: + { + unsigned key = netplay_key_hton(id); + if (key != NETPLAY_KEY_UNKNOWN) + { + unsigned word = key / 32; + unsigned bit = key % 32; + if (word <= istate->size) + return ((UINT32_C(1) << bit) & curr_input_state[word]) ? 1 : 0; + } + } + break; + default: + break; + } + + return 0; +} + +/** + * get_self_input_state: + * @netplay : pointer to netplay object + * + * Grab our own input state and send this frame's input state (self and remote) + * over the network + * + * Returns: true (1) if successful, otherwise false (0). + */ +static bool get_self_input_state( + bool block_libretro_input, + netplay_t *netplay) +{ + unsigned i; + struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; + netplay_input_state_t istate = NULL; + uint32_t devices, used_devices = 0, devi, dev_type, local_device; + + if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) + return false; + + /* We've already read this frame! */ + if (ptr->have_local) + return true; + + devices = netplay->self_devices; + used_devices = 0; + + for (devi = 0; devi < MAX_INPUT_DEVICES; devi++) + { + if (!(devices & (1 << devi))) + continue; + + /* Find an appropriate local device */ + dev_type = netplay->config_devices[devi]&RETRO_DEVICE_MASK; + + for (local_device = 0; local_device < MAX_INPUT_DEVICES; local_device++) + { + if (used_devices & (1 << local_device)) + continue; + if ((netplay->config_devices[local_device]&RETRO_DEVICE_MASK) == dev_type) + break; + } + + if (local_device == MAX_INPUT_DEVICES) + local_device = 0; + used_devices |= (1 << local_device); + + istate = netplay_input_state_for(&ptr->real_input[devi], + /* If we're a slave, we write our own input to MAX_CLIENTS to keep it separate */ + (netplay->self_mode==NETPLAY_CONNECTION_SLAVE)?MAX_CLIENTS:netplay->self_client_num, + netplay_expected_input_size(netplay, 1 << devi), + true, false); + if (!istate) + continue; /* FIXME: More severe? */ + + /* First frame we always give zero input since relying on + * input from first frame screws up when we use -F 0. */ + if ( !block_libretro_input + && netplay->self_frame_count > 0) + { + uint32_t *state = istate->data; + retro_input_state_t cb = netplay->cbs.state_cb; + unsigned dtype = netplay->config_devices[devi]&RETRO_DEVICE_MASK; + + switch (dtype) + { + case RETRO_DEVICE_ANALOG: + for (i = 0; i < 2; i++) + { + int16_t tmp_x = cb(local_device, + RETRO_DEVICE_ANALOG, (unsigned)i, 0); + int16_t tmp_y = cb(local_device, + RETRO_DEVICE_ANALOG, (unsigned)i, 1); + state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); + } + /* no break */ + + case RETRO_DEVICE_JOYPAD: + for (i = 0; i <= RETRO_DEVICE_ID_JOYPAD_R3; i++) + { + int16_t tmp = cb(local_device, + RETRO_DEVICE_JOYPAD, 0, (unsigned)i); + state[0] |= tmp ? 1 << i : 0; + } + break; + + case RETRO_DEVICE_MOUSE: + case RETRO_DEVICE_LIGHTGUN: + { + int16_t tmp_x = cb(local_device, dtype, 0, 0); + int16_t tmp_y = cb(local_device, dtype, 0, 1); + state[1] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); + for (i = 2; + i <= (unsigned)((dtype == RETRO_DEVICE_MOUSE) ? + RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN : + RETRO_DEVICE_ID_LIGHTGUN_START); + i++) + { + int16_t tmp = cb(local_device, dtype, 0, + (unsigned) i); + state[0] |= tmp ? 1 << i : 0; + } + break; + } + + case RETRO_DEVICE_KEYBOARD: + { + unsigned key, word = 0, bit = 1; + for (key = 1; key < NETPLAY_KEY_LAST; key++) + { + state[word] |= + cb(local_device, RETRO_DEVICE_KEYBOARD, 0, + NETPLAY_KEY_NTOH(key)) ? + (UINT32_C(1) << bit) : 0; + bit++; + if (bit >= 32) + { + bit = 0; + word++; + if (word >= istate->size) + break; + } + } + break; + } + } + } + } + + ptr->have_local = true; + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + ptr->have_real[netplay->self_client_num] = true; + netplay->read_ptr[netplay->self_client_num] = NEXT_PTR(netplay->self_ptr); + netplay->read_frame_count[netplay->self_client_num] = netplay->self_frame_count + 1; + } + + /* And send this input to our peers */ + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + netplay_send_cur_input(netplay, &netplay->connections[i]); + } + + /* Handle any delayed state changes */ + if (netplay->is_server) + netplay_delayed_state_change(netplay); + + return true; +} + + +bool netplay_poll( + bool block_libretro_input, + void *settings_data, + netplay_t *netplay) +{ + size_t i; + int res; + uint32_t client; + settings_t *settings = (settings_t*)settings_data; + + if (!get_self_input_state(block_libretro_input, netplay)) + goto catastrophe; + + /* If we're not connected, we're done */ + if (netplay->self_mode == NETPLAY_CONNECTION_NONE) + return true; + + /* Read Netplay input, block if we're configured to stall for input every + * frame */ + netplay_update_unread_ptr(netplay); + if (netplay->stateless_mode && + (netplay->connected_players>1) && + netplay->unread_frame_count <= netplay->run_frame_count) + res = netplay_poll_net_input(netplay, true); + else + res = netplay_poll_net_input(netplay, false); + if (res == -1) + goto catastrophe; + + /* Resolve and/or simulate the input if we don't have real input */ + netplay_resolve_input(netplay, netplay->run_ptr, false); + + /* Handle any slaves */ + if (netplay->is_server && netplay->connected_slaves) + netplay_handle_slaves(netplay); + + netplay_update_unread_ptr(netplay); + + /* Figure out how many frames of input latency we should be using to hide + * network latency */ + if (netplay->frame_run_time_avg || netplay->stateless_mode) + { + /* FIXME: Using fixed 60fps for this calculation */ + unsigned frames_per_frame = netplay->frame_run_time_avg ? + (16666 / netplay->frame_run_time_avg) : + 0; + unsigned frames_ahead = (netplay->run_frame_count > netplay->unread_frame_count) ? + (netplay->run_frame_count - netplay->unread_frame_count) : + 0; + int input_latency_frames_min = settings->uints.netplay_input_latency_frames_min - + (settings->bools.run_ahead_enabled ? settings->uints.run_ahead_frames : 0); + int input_latency_frames_max = input_latency_frames_min + settings->uints.netplay_input_latency_frames_range; + + /* Assume we need a couple frames worth of time to actually run the + * current frame */ + if (frames_per_frame > 2) + frames_per_frame -= 2; + else + frames_per_frame = 0; + + /* Shall we adjust our latency? */ + if (netplay->stateless_mode) + { + /* In stateless mode, we adjust up if we're "close" and down if we + * have a lot of slack */ + if (netplay->input_latency_frames < input_latency_frames_min || + (netplay->unread_frame_count == netplay->run_frame_count + 1 && + netplay->input_latency_frames < input_latency_frames_max)) + netplay->input_latency_frames++; + else if (netplay->input_latency_frames > input_latency_frames_max || + (netplay->unread_frame_count > netplay->run_frame_count + 2 && + netplay->input_latency_frames > input_latency_frames_min)) + netplay->input_latency_frames--; + } + else if (netplay->input_latency_frames < input_latency_frames_min || + (frames_per_frame < frames_ahead && + netplay->input_latency_frames < input_latency_frames_max)) + { + /* We can't hide this much network latency with replay, so hide some + * with input latency */ + netplay->input_latency_frames++; + } + else if (netplay->input_latency_frames > input_latency_frames_max || + (frames_per_frame > frames_ahead + 2 && + netplay->input_latency_frames > input_latency_frames_min)) + { + /* We don't need this much latency (any more) */ + netplay->input_latency_frames--; + } + } + + /* If we're stalled, consider unstalling */ + switch (netplay->stall) + { + case NETPLAY_STALL_RUNNING_FAST: + if (netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - 2 + > netplay->self_frame_count) + { + netplay->stall = NETPLAY_STALL_NONE; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->stall) + connection->stall = NETPLAY_STALL_NONE; + } + } + break; + + case NETPLAY_STALL_SPECTATOR_WAIT: + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || netplay->unread_frame_count > netplay->self_frame_count) + netplay->stall = NETPLAY_STALL_NONE; + break; + + case NETPLAY_STALL_INPUT_LATENCY: + /* Just let it recalculate momentarily */ + netplay->stall = NETPLAY_STALL_NONE; + break; + + case NETPLAY_STALL_SERVER_REQUESTED: + /* See if the stall is done */ + if (netplay->connections[0].stall_frame == 0) + { + /* Stop stalling! */ + netplay->connections[0].stall = NETPLAY_STALL_NONE; + netplay->stall = NETPLAY_STALL_NONE; + } + else + netplay->connections[0].stall_frame--; + break; + case NETPLAY_STALL_NO_CONNECTION: + /* We certainly haven't fixed this */ + break; + default: /* not stalling */ + break; + } + + /* If we're not stalled, consider stalling */ + if (!netplay->stall) + { + /* Have we not read enough latency frames? */ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING && + netplay->connected_players && + netplay->run_frame_count + netplay->input_latency_frames > netplay->self_frame_count) + { + netplay->stall = NETPLAY_STALL_INPUT_LATENCY; + netplay->stall_time = 0; + } + + /* Are we too far ahead? */ + if (netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES + <= netplay->self_frame_count) + { + netplay->stall = NETPLAY_STALL_RUNNING_FAST; + netplay->stall_time = cpu_features_get_time_usec(); + + /* Figure out who to blame */ + if (netplay->is_server) + { + for (client = 1; client < MAX_CLIENTS; client++) + { + struct netplay_connection *connection; + if (!(netplay->connected_players & (1 << client))) + continue; + if (netplay->read_frame_count[client] > netplay->unread_frame_count) + continue; + connection = &netplay->connections[client-1]; + if (connection->active && + connection->mode == NETPLAY_CONNECTION_PLAYING) + { + connection->stall = NETPLAY_STALL_RUNNING_FAST; + connection->stall_time = netplay->stall_time; + } + } + } + + } + + /* If we're a spectator, are we ahead at all? */ + if (!netplay->is_server && + (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING || + netplay->self_mode == NETPLAY_CONNECTION_SLAVE) && + netplay->unread_frame_count <= netplay->self_frame_count) + { + netplay->stall = NETPLAY_STALL_SPECTATOR_WAIT; + netplay->stall_time = cpu_features_get_time_usec(); + } + } + + /* If we're stalling, consider disconnection */ + if (netplay->stall && netplay->stall_time) + { + retro_time_t now = cpu_features_get_time_usec(); + + /* Don't stall out while they're paused */ + if (netplay->remote_paused) + netplay->stall_time = now; + else if (now - netplay->stall_time >= + (netplay->is_server ? MAX_SERVER_STALL_TIME_USEC : + MAX_CLIENT_STALL_TIME_USEC)) + { + /* Stalled out! */ + if (netplay->is_server) + { + bool fixed = false; + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && + connection->mode == NETPLAY_CONNECTION_PLAYING && + connection->stall) + { + netplay_hangup(netplay, connection); + fixed = true; + } + } + + if (fixed) + { + /* Not stalled now :) */ + netplay->stall = NETPLAY_STALL_NONE; + return true; + } + } + else + goto catastrophe; + return false; + } + } + + return true; + +catastrophe: + for (i = 0; i < netplay->connections_size; i++) + netplay_hangup(netplay, &netplay->connections[i]); + return false; +} + +bool netplay_is_alive(netplay_t *netplay) +{ + return (netplay->is_server) || + (!netplay->is_server && + netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); +} + +bool netplay_should_skip(netplay_t *netplay) +{ + if (!netplay) + return false; + return netplay->is_replay + && (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); +} diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c deleted file mode 100644 index 312040c648..0000000000 --- a/network/netplay/netplay_handshake.c +++ /dev/null @@ -1,1176 +0,0 @@ -/* 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 . - */ - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "netplay_private.h" - -#ifdef HAVE_CONFIG_H -#include "../../config.h" -#endif - -#include "../../autosave.h" -#include "../../configuration.h" -#include "../../content.h" -#include "../../retroarch.h" -#include "../../version.h" -#include "../../menu/menu_input.h" - -struct nick_buf_s -{ - uint32_t cmd[2]; - char nick[NETPLAY_NICK_LEN]; -}; - -struct password_buf_s -{ - uint32_t cmd[2]; - char password[NETPLAY_PASS_HASH_LEN]; -}; - -struct info_buf_s -{ - uint32_t cmd[2]; - uint32_t content_crc; - char core_name[NETPLAY_NICK_LEN]; - char core_version[NETPLAY_NICK_LEN]; -}; - -#define RECV(buf, sz) \ - recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \ - if (recvd >= 0 && recvd < (ssize_t) (sz)) \ - { \ - netplay_recv_reset(&connection->recv_packet_buffer); \ - return true; \ - } \ - else if (recvd < 0) - -#define NETPLAY_MAGIC 0x52414E50 /* RANP */ - -/* TODO/FIXME - static global variables */ -static netplay_t *handshake_password_netplay = NULL; -static unsigned long simple_rand_next = 1; - -/* TODO/FIXME - replace netplay_log_connection with calls - * to inet_ntop_compat and move runloop message queue pushing - * outside */ -#if !defined(HAVE_SOCKET_LEGACY) && !defined(WIIU) && !defined(_3DS) -/* Custom inet_ntop. Win32 doesn't seem to support this ... */ -static void netplay_log_connection( - const struct sockaddr_storage *their_addr, - unsigned slot, const char *nick, char *s, size_t len) -{ - union - { - const struct sockaddr_storage *storage; - const struct sockaddr_in *v4; - const struct sockaddr_in6 *v6; - } u; - const char *str = NULL; - char buf_v4[INET_ADDRSTRLEN] = {0}; - char buf_v6[INET6_ADDRSTRLEN] = {0}; - - u.storage = their_addr; - - switch (their_addr->ss_family) - { - case AF_INET: - { - struct sockaddr_in in; - - memset(&in, 0, sizeof(in)); - - str = buf_v4; - in.sin_family = AF_INET; - memcpy(&in.sin_addr, &u.v4->sin_addr, sizeof(struct in_addr)); - - getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), - buf_v4, sizeof(buf_v4), - NULL, 0, NI_NUMERICHOST); - } - break; - case AF_INET6: - { - struct sockaddr_in6 in; - memset(&in, 0, sizeof(in)); - - str = buf_v6; - in.sin6_family = AF_INET6; - memcpy(&in.sin6_addr, &u.v6->sin6_addr, sizeof(struct in6_addr)); - - getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6), - buf_v6, sizeof(buf_v6), NULL, 0, NI_NUMERICHOST); - } - break; - default: - break; - } - - if (str) - snprintf(s, len, msg_hash_to_str(MSG_GOT_CONNECTION_FROM_NAME), - nick, str); - else - snprintf(s, len, msg_hash_to_str(MSG_GOT_CONNECTION_FROM), - nick); -} -#else -static void netplay_log_connection( - const struct sockaddr_storage *their_addr, - unsigned slot, const char *nick, char *s, size_t len) -{ - /* Stub code - will need to be implemented */ - snprintf(s, len, msg_hash_to_str(MSG_GOT_CONNECTION_FROM), - nick); -} -#endif - -/** - * netplay_impl_magic: - * - * A pseudo-hash of the RetroArch and Netplay version, so only compatible - * versions play together. - */ -static uint32_t netplay_impl_magic(void) -{ - size_t i; - uint32_t res = 0; - const char *ver = PACKAGE_VERSION; - size_t len = strlen(ver); - - for (i = 0; i < len; i++) - res ^= ver[i] << (i & 0xf); - - res ^= NETPLAY_PROTOCOL_VERSION << (i & 0xf); - - 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); -} - -static int simple_rand(void) -{ - simple_rand_next = simple_rand_next * 1103515245 + 12345; - return((unsigned)(simple_rand_next / 65536) % 32768); -} - -static void simple_srand(unsigned int seed) -{ - simple_rand_next = seed; -} - -static uint32_t simple_rand_uint32(void) -{ - uint32_t parts[3]; - parts[0] = simple_rand(); - parts[1] = simple_rand(); - parts[2] = simple_rand(); - return ((parts[0] << 30) + - (parts[1] << 15) + - parts[2]); -} - -/** - * netplay_handshake_init_send - * - * Initialize our handshake and send the first part of the handshake protocol. - */ -bool netplay_handshake_init_send(netplay_t *netplay, - struct netplay_connection *connection) -{ - uint32_t header[6]; - unsigned conn_salt = 0; - settings_t *settings = config_get_ptr(); - - header[0] = htonl(NETPLAY_MAGIC); - header[1] = htonl(netplay_platform_magic()); - header[2] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - header[3] = 0; - header[4] = htonl(NETPLAY_PROTOCOL_VERSION); - header[5] = htonl(netplay_impl_magic()); - - if (netplay->is_server && - (settings->paths.netplay_password[0] || - settings->paths.netplay_spectate_password[0])) - { - /* Demand a password */ - if (simple_rand_next == 1) - simple_srand((unsigned int) time(NULL)); - connection->salt = simple_rand_uint32(); - if (connection->salt == 0) - connection->salt = 1; - conn_salt = connection->salt; - } - - header[3] = htonl(conn_salt); - - if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, - sizeof(header)) || - !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) - return false; - - return true; -} - -#ifdef HAVE_MENU -static void handshake_password(void *ignore, const char *line) -{ - struct password_buf_s password_buf; - char password[8+NETPLAY_PASS_LEN]; /* 8 for salt, 128 for password */ - char hash[NETPLAY_PASS_HASH_LEN+1]; /* + NULL terminator */ - netplay_t *netplay = handshake_password_netplay; - struct netplay_connection *connection = &netplay->connections[0]; - - snprintf(password, sizeof(password), "%08X", connection->salt); - if (!string_is_empty(line)) - 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(hash, (uint8_t *) password, strlen(password)); - memcpy(password_buf.password, hash, NETPLAY_PASS_HASH_LEN); - - /* We have no way to handle an error here, so we'll let the next function error out */ - if (netplay_send(&connection->send_packet_buffer, connection->fd, &password_buf, sizeof(password_buf))) - netplay_send_flush(&connection->send_packet_buffer, connection->fd, false); - -#ifdef HAVE_MENU - menu_input_dialog_end(); - retroarch_menu_running_finished(false); -#endif -} -#endif - -/** - * netplay_handshake_init - * - * Data receiver for the initial part of the handshake, i.e., waiting for the - * netplay header. - */ -bool netplay_handshake_init(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - ssize_t recvd; - struct nick_buf_s nick_buf; - uint32_t header[6]; - uint32_t local_pmagic = 0; - uint32_t remote_pmagic = 0; - uint32_t remote_version = 0; - uint32_t compression = 0; - struct compression_transcoder *ctrans = NULL; - const char *dmsg = NULL; - - memset(header, 0, sizeof(header)); - - RECV(header, sizeof(uint32_t)) - { - dmsg = msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT); - goto error; - } - - if (ntohl(header[0]) != NETPLAY_MAGIC) - { - dmsg = msg_hash_to_str(MSG_NETPLAY_NOT_RETROARCH); - goto error; - } - - RECV(header + 1, sizeof(header) - sizeof(uint32_t)) - { - dmsg = msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT); - goto error; - } - - remote_version = ntohl(header[4]); - if (remote_version < NETPLAY_PROTOCOL_VERSION) - { - dmsg = msg_hash_to_str(MSG_NETPLAY_OUT_OF_DATE); - goto error; - } - - if (ntohl(header[5]) != netplay_impl_magic()) - { - /* We allow the connection but warn that this could cause issues. */ - dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_VERSIONS); - RARCH_WARN("%s\n", dmsg); - runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - - /* We only care about platform magic if our core is quirky */ - local_pmagic = netplay_platform_magic(); - remote_pmagic = ntohl(header[1]); - - if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) && - netplay_endian_mismatch(local_pmagic, remote_pmagic)) - { - RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n"); - dmsg = msg_hash_to_str(MSG_NETPLAY_ENDIAN_DEPENDENT); - goto error; - } - if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) && - (local_pmagic != remote_pmagic)) - { - RARCH_ERR("Platform mismatch with a platform-sensitive core.\n"); - dmsg = msg_hash_to_str(MSG_NETPLAY_PLATFORM_DEPENDENT); - goto error; - } - - /* Check what compression is supported */ - compression = ntohl(header[2]); - compression &= NETPLAY_COMPRESSION_SUPPORTED; - - if (compression & NETPLAY_COMPRESSION_ZLIB) - { - ctrans = &netplay->compress_zlib; - if (!ctrans->compression_backend) - { - ctrans->compression_backend = - trans_stream_get_zlib_deflate_backend(); - if (!ctrans->compression_backend) - ctrans->compression_backend = trans_stream_get_pipe_backend(); - } - connection->compression_supported = NETPLAY_COMPRESSION_ZLIB; - } - else - { - ctrans = &netplay->compress_nil; - if (!ctrans->compression_backend) - { - ctrans->compression_backend = - trans_stream_get_pipe_backend(); - } - connection->compression_supported = 0; - } - - if (!ctrans->decompression_backend) - ctrans->decompression_backend = ctrans->compression_backend->reverse; - - /* Allocate our compression stream */ - if (!ctrans->compression_stream) - { - ctrans->compression_stream = ctrans->compression_backend->stream_new(); - ctrans->decompression_stream = ctrans->decompression_backend->stream_new(); - } - if (!ctrans->compression_stream || !ctrans->decompression_stream) - { - RARCH_ERR("Failed to allocate compression transcoder!\n"); - return false; - } - - /* If a password is demanded, ask for it */ - if (!netplay->is_server && (connection->salt = ntohl(header[3]))) - { -#ifdef HAVE_MENU - menu_input_ctx_line_t line; - retroarch_menu_running(); -#endif - - handshake_password_netplay = netplay; - -#ifdef HAVE_MENU - memset(&line, 0, sizeof(line)); - line.label = msg_hash_to_str(MSG_NETPLAY_ENTER_PASSWORD); - line.label_setting = "no_setting"; - line.cb = handshake_password; - if (!menu_input_dialog_start(&line)) - return false; -#endif - } - - /* 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)); - if (!netplay_send(&connection->send_packet_buffer, connection->fd, &nick_buf, - sizeof(nick_buf)) || - !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) - return false; - - /* Move on to the next mode */ - connection->mode = NETPLAY_CONNECTION_PRE_NICK; - *had_input = true; - netplay_recv_flush(&connection->recv_packet_buffer); - return true; - -error: - if (dmsg) - { - RARCH_ERR("%s\n", dmsg); - runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - return false; -} - -static void netplay_handshake_ready(netplay_t *netplay, - struct netplay_connection *connection) -{ - char msg[512]; - msg[0] = '\0'; - - if (netplay->is_server) - { - unsigned slot = (unsigned)(connection - netplay->connections); - - netplay_log_connection(&connection->addr, - slot, connection->nick, msg, sizeof(msg)); - - RARCH_LOG("%s %u\n", msg_hash_to_str(MSG_CONNECTION_SLOT), slot); - - /* Send them the savestate */ - if (!(netplay->quirks & - (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) - netplay->force_send_savestate = true; - } - else - { - netplay->is_connected = true; - snprintf(msg, sizeof(msg), "%s: \"%s\"", - msg_hash_to_str(MSG_CONNECTED_TO), - connection->nick); - } - - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - - /* Unstall if we were waiting for this */ - if (netplay->stall == NETPLAY_STALL_NO_CONNECTION) - netplay->stall = NETPLAY_STALL_NONE; -} - -/** - * netplay_handshake_info - * - * Send an INFO command. - */ -static bool netplay_handshake_info(netplay_t *netplay, - struct netplay_connection *connection) -{ - struct info_buf_s info_buf; - uint32_t content_crc = 0; - struct retro_system_info *system = runloop_get_libretro_system_info(); - - memset(&info_buf, 0, sizeof(info_buf)); - info_buf.cmd[0] = htonl(NETPLAY_CMD_INFO); - info_buf.cmd[1] = htonl(sizeof(info_buf) - 2*sizeof(uint32_t)); - - /* Get our core info */ - if (system) - { - strlcpy(info_buf.core_name, - system->library_name, sizeof(info_buf.core_name)); - strlcpy(info_buf.core_version, - system->library_version, sizeof(info_buf.core_version)); - } - else - { - strlcpy(info_buf.core_name, - "UNKNOWN", sizeof(info_buf.core_name)); - strlcpy(info_buf.core_version, - "UNKNOWN", sizeof(info_buf.core_version)); - } - - /* Get our content CRC */ - content_crc = content_get_crc(); - - if (content_crc != 0) - info_buf.content_crc = htonl(content_crc); - - /* Send it off and wait for info back */ - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - &info_buf, sizeof(info_buf)) || - !netplay_send_flush(&connection->send_packet_buffer, connection->fd, - false)) - return false; - - connection->mode = NETPLAY_CONNECTION_PRE_INFO; - return true; -} - -/** - * netplay_handshake_sync - * - * Send a SYNC command. - */ -static bool netplay_handshake_sync(netplay_t *netplay, - struct netplay_connection *connection) -{ - /* If we're the server, now we send sync info */ - size_t i; - int matchct; - uint32_t cmd[4]; - retro_ctx_memory_info_t mem_info; - uint32_t client_num = 0; - uint32_t device = 0; - size_t nicklen, nickmangle = 0; - bool nick_matched = false; - -#ifdef HAVE_THREADS - autosave_lock(); -#endif - mem_info.id = RETRO_MEMORY_SAVE_RAM; - core_get_memory(&mem_info); -#ifdef HAVE_THREADS - autosave_unlock(); -#endif - - /* Send basic sync info */ - cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(2*sizeof(uint32_t) - /* Controller devices */ - + MAX_INPUT_DEVICES*sizeof(uint32_t) - - /* Share modes */ - + MAX_INPUT_DEVICES*sizeof(uint8_t) - - /* Device-client mapping */ - + MAX_INPUT_DEVICES*sizeof(uint32_t) - - /* Client nick */ - + NETPLAY_NICK_LEN - - /* And finally, sram */ - + mem_info.size); - cmd[2] = htonl(netplay->self_frame_count); - client_num = (uint32_t)(connection - netplay->connections + 1); - - if (netplay->local_paused || netplay->remote_paused) - client_num |= NETPLAY_CMD_SYNC_BIT_PAUSED; - - cmd[3] = htonl(client_num); - - if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, - sizeof(cmd))) - return false; - - /* Now send the device info */ - for (i = 0; i < MAX_INPUT_DEVICES; i++) - { - device = htonl(netplay->config_devices[i]); - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - &device, sizeof(device))) - return false; - } - - /* Then the share mode */ - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - netplay->device_share_modes, sizeof(netplay->device_share_modes))) - return false; - - /* Then the device-client mapping */ - for (i = 0; i < MAX_INPUT_DEVICES; i++) - { - device = htonl(netplay->device_clients[i]); - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - &device, sizeof(device))) - return false; - } - - /* Now see if we need to mangle their nick */ - nicklen = strlen(connection->nick); - if (nicklen > NETPLAY_NICK_LEN - 5) - nickmangle = NETPLAY_NICK_LEN - 5; - else - nickmangle = nicklen; - matchct = 1; - do - { - nick_matched = false; - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *sc = &netplay->connections[i]; - if (sc == connection) - continue; - if (sc->active && - sc->mode >= NETPLAY_CONNECTION_CONNECTED && - !strncmp(connection->nick, sc->nick, NETPLAY_NICK_LEN)) - { - nick_matched = true; - break; - } - } - if (!strncmp(connection->nick, netplay->nick, NETPLAY_NICK_LEN)) - nick_matched = true; - - if (nick_matched) - { - /* Somebody has this nick, make a new one! */ - snprintf(connection->nick + nickmangle, - NETPLAY_NICK_LEN - nickmangle, " (%d)", ++matchct); - connection->nick[NETPLAY_NICK_LEN - 1] = '\0'; - } - } while (nick_matched); - - /* Send the nick */ - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - connection->nick, NETPLAY_NICK_LEN)) - return false; - - /* And finally, the SRAM */ -#ifdef HAVE_THREADS - autosave_lock(); -#endif - 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)) - { -#ifdef HAVE_THREADS - autosave_unlock(); -#endif - return false; - } -#ifdef HAVE_THREADS - autosave_unlock(); -#endif - - /* Now we're ready! */ - connection->mode = NETPLAY_CONNECTION_SPECTATING; - netplay_handshake_ready(netplay, connection); - - return true; -} - -/** - * netplay_handshake_pre_nick - * - * Data receiver for the second stage of handshake, receiving the other side's - * nickname. - */ -static bool netplay_handshake_pre_nick(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - 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, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - return false; - } - - strlcpy(connection->nick, nick_buf.nick, - (sizeof(connection->nick) < sizeof(nick_buf.nick)) ? - sizeof(connection->nick) : sizeof(nick_buf.nick)); - - if (netplay->is_server) - { - settings_t *settings = config_get_ptr(); - - /* There's a password, so just put them in PRE_PASSWORD mode */ - if ( settings->paths.netplay_password[0] || - settings->paths.netplay_spectate_password[0]) - connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD; - else - { - connection->can_play = true; - if (!netplay_handshake_info(netplay, connection)) - return false; - connection->mode = NETPLAY_CONNECTION_PRE_INFO; - } - } - /* Client needs to wait for INFO */ - else - connection->mode = NETPLAY_CONNECTION_PRE_INFO; - - *had_input = true; - netplay_recv_flush(&connection->recv_packet_buffer); - return true; -} - -/** - * netplay_handshake_pre_password - * - * Data receiver for the third, optional stage of server handshake, receiving - * the password and sending core/content info. - */ -static bool netplay_handshake_pre_password(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - struct password_buf_s password_buf; - char password[8+NETPLAY_PASS_LEN]; /* 8 for salt */ - char hash[NETPLAY_PASS_HASH_LEN+1]; /* + NULL terminator */ - ssize_t recvd; - char msg[512]; - bool correct = false; - settings_t *settings = config_get_ptr(); - - msg[0] = '\0'; - - RECV(&password_buf, sizeof(password_buf)) {} - - /* Expecting only a password command */ - 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, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - return false; - } - - /* Calculate the correct password hash(es) and compare */ - correct = false; - snprintf(password, sizeof(password), "%08X", connection->salt); - - if (settings->paths.netplay_password[0]) - { - strlcpy(password + 8, - settings->paths.netplay_password, sizeof(password)-8); - - sha256_hash(hash, (uint8_t *) password, strlen(password)); - - if (!memcmp(password_buf.password, hash, NETPLAY_PASS_HASH_LEN)) - { - correct = true; - connection->can_play = true; - } - } - if (settings->paths.netplay_spectate_password[0]) - { - strlcpy(password + 8, - settings->paths.netplay_spectate_password, sizeof(password)-8); - - sha256_hash(hash, (uint8_t *) password, strlen(password)); - - if (!memcmp(password_buf.password, hash, NETPLAY_PASS_HASH_LEN)) - correct = true; - } - - /* Just disconnect if it was wrong */ - if (!correct) - return false; - - /* Otherwise, exchange info */ - if (!netplay_handshake_info(netplay, connection)) - return false; - - *had_input = true; - connection->mode = NETPLAY_CONNECTION_PRE_INFO; - netplay_recv_flush(&connection->recv_packet_buffer); - return true; -} - -/** - * netplay_handshake_pre_info - * - * Data receiver for the third stage of server handshake, receiving - * the password. - */ -static bool netplay_handshake_pre_info(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - struct info_buf_s info_buf; - uint32_t cmd_size; - ssize_t recvd; - uint32_t content_crc = 0; - const char *dmsg = NULL; - struct retro_system_info *system = runloop_get_libretro_system_info(); - - RECV(&info_buf, sizeof(info_buf.cmd)) {} - - if (recvd < 0 || - ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO) - { - RARCH_ERR("Failed to receive netplay info.\n"); - return false; - } - - cmd_size = ntohl(info_buf.cmd[1]); - if (cmd_size != sizeof(info_buf) - 2*sizeof(uint32_t)) - { - /* Either the host doesn't have anything loaded, or this is just screwy */ - if (cmd_size != 0) - { - /* Huh? */ - RARCH_ERR("Invalid NETPLAY_CMD_INFO payload size.\n"); - return false; - } - - /* Send our info and hope they load it! */ - if (!netplay_handshake_info(netplay, connection)) - return false; - - *had_input = true; - netplay_recv_flush(&connection->recv_packet_buffer); - return true; - } - - RECV(&info_buf.content_crc, cmd_size) - { - RARCH_ERR("Failed to receive netplay info payload.\n"); - return false; - } - - /* Check the core info */ - if (system) - { - if (strncmp(info_buf.core_name, - system->library_name, sizeof(info_buf.core_name))) - { - /* Wrong core! */ - dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_CORES); - RARCH_ERR("%s\n", dmsg); - runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - /* FIXME: Should still send INFO, so the other side knows what's what */ - return false; - } - if (strncmp(info_buf.core_version, - system->library_version, sizeof(info_buf.core_version))) - { - dmsg = msg_hash_to_str(MSG_NETPLAY_DIFFERENT_CORE_VERSIONS); - RARCH_WARN("%s\n", dmsg); - runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - } - - /* Check the content CRC */ - content_crc = content_get_crc(); - - if (content_crc != 0) - { - if (ntohl(info_buf.content_crc) != content_crc) - { - dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); - RARCH_WARN("%s\n", dmsg); - runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - } - - /* Now switch to the right mode */ - if (netplay->is_server) - { - if (!netplay_handshake_sync(netplay, connection)) - return false; - } - else - { - if (!netplay_handshake_info(netplay, connection)) - return false; - connection->mode = NETPLAY_CONNECTION_PRE_SYNC; - } - - *had_input = true; - netplay_recv_flush(&connection->recv_packet_buffer); - return true; -} - -/** - * netplay_handshake_pre_sync - * - * Data receiver for the client's third handshake stage, receiving the - * synchronization information. - */ -static bool netplay_handshake_pre_sync(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - uint32_t cmd[2]; - uint32_t new_frame_count, client_num; - uint32_t device; - uint32_t local_sram_size, remote_sram_size; - size_t i, j; - ssize_t recvd; - retro_ctx_controller_info_t pad; - char new_nick[NETPLAY_NICK_LEN]; - retro_ctx_memory_info_t mem_info; - - RECV(cmd, sizeof(cmd)) - { - const char *msg = msg_hash_to_str(MSG_NETPLAY_INCORRECT_PASSWORD); - RARCH_ERR("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - return false; - } - - /* Only expecting a sync command */ - if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || - ntohl(cmd[1]) < (2+2*MAX_INPUT_DEVICES)*sizeof(uint32_t) + (MAX_INPUT_DEVICES)*sizeof(uint8_t) + - NETPLAY_NICK_LEN) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); - return false; - } - - /* Get the frame count */ - RECV(&new_frame_count, sizeof(new_frame_count)) - return false; - new_frame_count = ntohl(new_frame_count); - - /* Get our client number and pause mode */ - RECV(&client_num, sizeof(client_num)) - return false; - client_num = ntohl(client_num); - if (client_num & NETPLAY_CMD_SYNC_BIT_PAUSED) - { - netplay->remote_paused = true; - client_num ^= NETPLAY_CMD_SYNC_BIT_PAUSED; - } - netplay->self_client_num = client_num; - - /* Set our frame counters as requested */ - netplay->self_frame_count = netplay->run_frame_count = - netplay->other_frame_count = netplay->unread_frame_count = - netplay->server_frame_count = new_frame_count; - - /* And clear out the framebuffer */ - for (i = 0; i < netplay->buffer_size; i++) - { - struct delta_frame *ptr = &netplay->buffer[i]; - - ptr->used = false; - - if (i == netplay->self_ptr) - { - /* Clear out any current data but still use this frame */ - if (!netplay_delta_frame_ready(netplay, ptr, 0)) - return false; - - ptr->frame = new_frame_count; - ptr->have_local = true; - netplay->run_ptr = netplay->other_ptr = netplay->unread_ptr = - netplay->server_ptr = i; - - } - } - for (i = 0; i < MAX_CLIENTS; i++) - { - netplay->read_ptr[i] = netplay->self_ptr; - netplay->read_frame_count[i] = netplay->self_frame_count; - } - - /* Get and set each input device */ - for (i = 0; i < MAX_INPUT_DEVICES; i++) - { - RECV(&device, sizeof(device)) - return false; - - pad.port = (unsigned)i; - pad.device = ntohl(device); - netplay->config_devices[i] = pad.device; - if ((pad.device&RETRO_DEVICE_MASK) == RETRO_DEVICE_KEYBOARD) - { - netplay->have_updown_device = true; - netplay_key_hton_init(); - } - - core_set_controller_port_device(&pad); - } - - /* Get the share modes */ - RECV(netplay->device_share_modes, sizeof(netplay->device_share_modes)) - return false; - - /* Get the client-controller mapping */ - netplay->connected_players = - netplay->connected_slaves = - netplay->self_devices = 0; - for (i = 0; i < MAX_CLIENTS; i++) - netplay->client_devices[i] = 0; - for (i = 0; i < MAX_INPUT_DEVICES; i++) - { - RECV(&device, sizeof(device)) - return false; - device = ntohl(device); - - netplay->device_clients[i] = device; - netplay->connected_players |= device; - for (j = 0; j < MAX_CLIENTS; j++) - { - if (device & (1<client_devices[j] |= 1<nick, new_nick, NETPLAY_NICK_LEN)) - { - char msg[512]; - strlcpy(netplay->nick, new_nick, NETPLAY_NICK_LEN); - snprintf(msg, sizeof(msg), - msg_hash_to_str(MSG_NETPLAY_CHANGED_NICK), netplay->nick); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } - - /* Now check the SRAM */ -#ifdef HAVE_THREADS - autosave_lock(); -#endif - mem_info.id = RETRO_MEMORY_SAVE_RAM; - core_get_memory(&mem_info); - - local_sram_size = (unsigned)mem_info.size; - remote_sram_size = ntohl(cmd[1]) - - (2+2*MAX_INPUT_DEVICES)*sizeof(uint32_t) - (MAX_INPUT_DEVICES)*sizeof(uint8_t) - NETPLAY_NICK_LEN; - - 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)); -#ifdef HAVE_THREADS - autosave_unlock(); -#endif - 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)); -#ifdef HAVE_THREADS - autosave_unlock(); -#endif - return false; - } - if (remote_sram_size > sizeof(uint32_t)) - remote_sram_size -= sizeof(uint32_t); - else - remote_sram_size = 0; - } - - } -#ifdef HAVE_THREADS - autosave_unlock(); -#endif - - /* We're ready! */ - *had_input = true; - netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - connection->mode = NETPLAY_CONNECTION_PLAYING; - netplay_handshake_ready(netplay, connection); - netplay_recv_flush(&connection->recv_packet_buffer); - - /* Ask to switch to playing mode if we should */ - { - settings_t *settings = config_get_ptr(); - if (!settings->bools.netplay_start_as_spectator) - return netplay_cmd_mode(netplay, NETPLAY_CONNECTION_PLAYING); - } - - return true; -} - -/** - * netplay_handshake - * - * Data receiver for all handshake states. - */ -bool netplay_handshake(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - bool ret = false; - - switch (connection->mode) - { - case NETPLAY_CONNECTION_INIT: - ret = netplay_handshake_init(netplay, connection, had_input); - break; - case NETPLAY_CONNECTION_PRE_NICK: - ret = netplay_handshake_pre_nick(netplay, connection, had_input); - break; - case NETPLAY_CONNECTION_PRE_PASSWORD: - ret = netplay_handshake_pre_password(netplay, connection, had_input); - break; - case NETPLAY_CONNECTION_PRE_INFO: - ret = netplay_handshake_pre_info(netplay, connection, had_input); - break; - case NETPLAY_CONNECTION_PRE_SYNC: - ret = netplay_handshake_pre_sync(netplay, connection, had_input); - break; - case NETPLAY_CONNECTION_NONE: - default: - return false; - } - - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - - return ret; -} diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 518994edf4..7cf59ebf53 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -674,19 +674,6 @@ bool netplay_lan_ad_server(netplay_t *netplay); * NETPLAY-FRONTEND.C **************************************************************/ -/** - * netplay_load_savestate - * @netplay : pointer to netplay object - * @serial_info : the savestate being loaded, NULL means - * "load it yourself" - * @save : Whether to save the provided serial_info - * into the frame buffer - * - * Inform Netplay of a savestate load and send it to the other side - **/ -void netplay_load_savestate(netplay_t *netplay, - retro_ctx_serialize_info_t *serial_info, bool save); - /** * input_poll_net * diff --git a/retroarch.c b/retroarch.c index 6573ab9fdd..040d3556d3 100644 --- a/retroarch.c +++ b/retroarch.c @@ -4641,192 +4641,6 @@ static void discord_init( #endif #ifdef HAVE_NETWORKING -/** - * netplay_is_alive: - * @netplay : pointer to netplay object - * - * Checks if input port/index is controlled by netplay or not. - * - * Returns: true (1) if alive, otherwise false (0). - **/ -static bool netplay_is_alive(netplay_t *netplay) -{ - return (netplay->is_server) || - (!netplay->is_server && - netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); -} - -/** - * netplay_should_skip: - * @netplay : pointer to netplay object - * - * If we're fast-forward replaying to resync, check if we - * should actually show frame. - * - * Returns: bool (1) if we should skip this frame, otherwise - * false (0). - **/ -static bool netplay_should_skip(netplay_t *netplay) -{ - if (!netplay) - return false; - return netplay->is_replay - && (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); -} - -/** - * get_self_input_state: - * @netplay : pointer to netplay object - * - * Grab our own input state and send this frame's input state (self and remote) - * over the network - * - * Returns: true (1) if successful, otherwise false (0). - */ -static bool get_self_input_state( - bool block_libretro_input, - netplay_t *netplay) -{ - unsigned i; - struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; - netplay_input_state_t istate = NULL; - uint32_t devices, used_devices = 0, devi, dev_type, local_device; - - if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) - return false; - - /* We've already read this frame! */ - if (ptr->have_local) - return true; - - devices = netplay->self_devices; - used_devices = 0; - - for (devi = 0; devi < MAX_INPUT_DEVICES; devi++) - { - if (!(devices & (1 << devi))) - continue; - - /* Find an appropriate local device */ - dev_type = netplay->config_devices[devi]&RETRO_DEVICE_MASK; - - for (local_device = 0; local_device < MAX_INPUT_DEVICES; local_device++) - { - if (used_devices & (1 << local_device)) - continue; - if ((netplay->config_devices[local_device]&RETRO_DEVICE_MASK) == dev_type) - break; - } - - if (local_device == MAX_INPUT_DEVICES) - local_device = 0; - used_devices |= (1 << local_device); - - istate = netplay_input_state_for(&ptr->real_input[devi], - /* If we're a slave, we write our own input to MAX_CLIENTS to keep it separate */ - (netplay->self_mode==NETPLAY_CONNECTION_SLAVE)?MAX_CLIENTS:netplay->self_client_num, - netplay_expected_input_size(netplay, 1 << devi), - true, false); - if (!istate) - continue; /* FIXME: More severe? */ - - /* First frame we always give zero input since relying on - * input from first frame screws up when we use -F 0. */ - if ( !block_libretro_input - && netplay->self_frame_count > 0) - { - uint32_t *state = istate->data; - retro_input_state_t cb = netplay->cbs.state_cb; - unsigned dtype = netplay->config_devices[devi]&RETRO_DEVICE_MASK; - - switch (dtype) - { - case RETRO_DEVICE_ANALOG: - for (i = 0; i < 2; i++) - { - int16_t tmp_x = cb(local_device, - RETRO_DEVICE_ANALOG, (unsigned)i, 0); - int16_t tmp_y = cb(local_device, - RETRO_DEVICE_ANALOG, (unsigned)i, 1); - state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); - } - /* no break */ - - case RETRO_DEVICE_JOYPAD: - for (i = 0; i <= RETRO_DEVICE_ID_JOYPAD_R3; i++) - { - int16_t tmp = cb(local_device, - RETRO_DEVICE_JOYPAD, 0, (unsigned)i); - state[0] |= tmp ? 1 << i : 0; - } - break; - - case RETRO_DEVICE_MOUSE: - case RETRO_DEVICE_LIGHTGUN: - { - int16_t tmp_x = cb(local_device, dtype, 0, 0); - int16_t tmp_y = cb(local_device, dtype, 0, 1); - state[1] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); - for (i = 2; - i <= (unsigned)((dtype == RETRO_DEVICE_MOUSE) ? - RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN : - RETRO_DEVICE_ID_LIGHTGUN_START); - i++) - { - int16_t tmp = cb(local_device, dtype, 0, - (unsigned) i); - state[0] |= tmp ? 1 << i : 0; - } - break; - } - - case RETRO_DEVICE_KEYBOARD: - { - unsigned key, word = 0, bit = 1; - for (key = 1; key < NETPLAY_KEY_LAST; key++) - { - state[word] |= - cb(local_device, RETRO_DEVICE_KEYBOARD, 0, - NETPLAY_KEY_NTOH(key)) ? - (UINT32_C(1) << bit) : 0; - bit++; - if (bit >= 32) - { - bit = 0; - word++; - if (word >= istate->size) - break; - } - } - break; - } - } - } - } - - ptr->have_local = true; - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - { - ptr->have_real[netplay->self_client_num] = true; - netplay->read_ptr[netplay->self_client_num] = NEXT_PTR(netplay->self_ptr); - netplay->read_frame_count[netplay->self_client_num] = netplay->self_frame_count + 1; - } - - /* And send this input to our peers */ - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) - netplay_send_cur_input(netplay, &netplay->connections[i]); - } - - /* Handle any delayed state changes */ - if (netplay->is_server) - netplay_delayed_state_change(netplay); - - return true; -} - static bool init_netplay_deferred( struct rarch_state *p_rarch, const char* server, unsigned port) @@ -4844,252 +4658,6 @@ static bool init_netplay_deferred( return p_rarch->netplay_client_deferred; } -/** - * netplay_poll: - * @netplay : pointer to netplay object - * - * Polls network to see if we have anything new. If our - * network buffer is full, we simply have to block - * for new input data. - * - * Returns: true (1) if successful, otherwise false (0). - **/ -static bool netplay_poll( - bool block_libretro_input, - settings_t *settings, - netplay_t *netplay) -{ - int res; - uint32_t client; - size_t i; - - if (!get_self_input_state(block_libretro_input, netplay)) - goto catastrophe; - - /* If we're not connected, we're done */ - if (netplay->self_mode == NETPLAY_CONNECTION_NONE) - return true; - - /* Read Netplay input, block if we're configured to stall for input every - * frame */ - netplay_update_unread_ptr(netplay); - if (netplay->stateless_mode && - (netplay->connected_players>1) && - netplay->unread_frame_count <= netplay->run_frame_count) - res = netplay_poll_net_input(netplay, true); - else - res = netplay_poll_net_input(netplay, false); - if (res == -1) - goto catastrophe; - - /* Resolve and/or simulate the input if we don't have real input */ - netplay_resolve_input(netplay, netplay->run_ptr, false); - - /* Handle any slaves */ - if (netplay->is_server && netplay->connected_slaves) - netplay_handle_slaves(netplay); - - netplay_update_unread_ptr(netplay); - - /* Figure out how many frames of input latency we should be using to hide - * network latency */ - if (netplay->frame_run_time_avg || netplay->stateless_mode) - { - /* FIXME: Using fixed 60fps for this calculation */ - unsigned frames_per_frame = netplay->frame_run_time_avg ? - (16666 / netplay->frame_run_time_avg) : - 0; - unsigned frames_ahead = (netplay->run_frame_count > netplay->unread_frame_count) ? - (netplay->run_frame_count - netplay->unread_frame_count) : - 0; - int input_latency_frames_min = settings->uints.netplay_input_latency_frames_min - - (settings->bools.run_ahead_enabled ? settings->uints.run_ahead_frames : 0); - int input_latency_frames_max = input_latency_frames_min + settings->uints.netplay_input_latency_frames_range; - - /* Assume we need a couple frames worth of time to actually run the - * current frame */ - if (frames_per_frame > 2) - frames_per_frame -= 2; - else - frames_per_frame = 0; - - /* Shall we adjust our latency? */ - if (netplay->stateless_mode) - { - /* In stateless mode, we adjust up if we're "close" and down if we - * have a lot of slack */ - if (netplay->input_latency_frames < input_latency_frames_min || - (netplay->unread_frame_count == netplay->run_frame_count + 1 && - netplay->input_latency_frames < input_latency_frames_max)) - netplay->input_latency_frames++; - else if (netplay->input_latency_frames > input_latency_frames_max || - (netplay->unread_frame_count > netplay->run_frame_count + 2 && - netplay->input_latency_frames > input_latency_frames_min)) - netplay->input_latency_frames--; - } - else if (netplay->input_latency_frames < input_latency_frames_min || - (frames_per_frame < frames_ahead && - netplay->input_latency_frames < input_latency_frames_max)) - { - /* We can't hide this much network latency with replay, so hide some - * with input latency */ - netplay->input_latency_frames++; - } - else if (netplay->input_latency_frames > input_latency_frames_max || - (frames_per_frame > frames_ahead + 2 && - netplay->input_latency_frames > input_latency_frames_min)) - { - /* We don't need this much latency (any more) */ - netplay->input_latency_frames--; - } - } - - /* If we're stalled, consider unstalling */ - switch (netplay->stall) - { - case NETPLAY_STALL_RUNNING_FAST: - if (netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - 2 - > netplay->self_frame_count) - { - netplay->stall = NETPLAY_STALL_NONE; - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection->active && connection->stall) - connection->stall = NETPLAY_STALL_NONE; - } - } - break; - - case NETPLAY_STALL_SPECTATOR_WAIT: - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || netplay->unread_frame_count > netplay->self_frame_count) - netplay->stall = NETPLAY_STALL_NONE; - break; - - case NETPLAY_STALL_INPUT_LATENCY: - /* Just let it recalculate momentarily */ - netplay->stall = NETPLAY_STALL_NONE; - break; - - case NETPLAY_STALL_SERVER_REQUESTED: - /* See if the stall is done */ - if (netplay->connections[0].stall_frame == 0) - { - /* Stop stalling! */ - netplay->connections[0].stall = NETPLAY_STALL_NONE; - netplay->stall = NETPLAY_STALL_NONE; - } - else - netplay->connections[0].stall_frame--; - break; - case NETPLAY_STALL_NO_CONNECTION: - /* We certainly haven't fixed this */ - break; - default: /* not stalling */ - break; - } - - /* If we're not stalled, consider stalling */ - if (!netplay->stall) - { - /* Have we not read enough latency frames? */ - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING && - netplay->connected_players && - netplay->run_frame_count + netplay->input_latency_frames > netplay->self_frame_count) - { - netplay->stall = NETPLAY_STALL_INPUT_LATENCY; - netplay->stall_time = 0; - } - - /* Are we too far ahead? */ - if (netplay->unread_frame_count + NETPLAY_MAX_STALL_FRAMES - <= netplay->self_frame_count) - { - netplay->stall = NETPLAY_STALL_RUNNING_FAST; - netplay->stall_time = cpu_features_get_time_usec(); - - /* Figure out who to blame */ - if (netplay->is_server) - { - for (client = 1; client < MAX_CLIENTS; client++) - { - struct netplay_connection *connection; - if (!(netplay->connected_players & (1 << client))) - continue; - if (netplay->read_frame_count[client] > netplay->unread_frame_count) - continue; - connection = &netplay->connections[client-1]; - if (connection->active && - connection->mode == NETPLAY_CONNECTION_PLAYING) - { - connection->stall = NETPLAY_STALL_RUNNING_FAST; - connection->stall_time = netplay->stall_time; - } - } - } - - } - - /* If we're a spectator, are we ahead at all? */ - if (!netplay->is_server && - (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING || - netplay->self_mode == NETPLAY_CONNECTION_SLAVE) && - netplay->unread_frame_count <= netplay->self_frame_count) - { - netplay->stall = NETPLAY_STALL_SPECTATOR_WAIT; - netplay->stall_time = cpu_features_get_time_usec(); - } - } - - /* If we're stalling, consider disconnection */ - if (netplay->stall && netplay->stall_time) - { - retro_time_t now = cpu_features_get_time_usec(); - - /* Don't stall out while they're paused */ - if (netplay->remote_paused) - netplay->stall_time = now; - else if (now - netplay->stall_time >= - (netplay->is_server ? MAX_SERVER_STALL_TIME_USEC : - MAX_CLIENT_STALL_TIME_USEC)) - { - /* Stalled out! */ - if (netplay->is_server) - { - bool fixed = false; - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection->active && - connection->mode == NETPLAY_CONNECTION_PLAYING && - connection->stall) - { - netplay_hangup(netplay, connection); - fixed = true; - } - } - - if (fixed) - { - /* Not stalled now :) */ - netplay->stall = NETPLAY_STALL_NONE; - return true; - } - } - else - goto catastrophe; - return false; - } - } - - return true; - -catastrophe: - for (i = 0; i < netplay->connections_size; i++) - netplay_hangup(netplay, &netplay->connections[i]); - return false; -} - /** * input_poll_net * @@ -5136,85 +4704,6 @@ static size_t audio_sample_batch_net(const int16_t *data, size_t frames) return frames; } -static int16_t netplay_input_state(netplay_t *netplay, - unsigned port, unsigned device, - unsigned idx, unsigned id) -{ - struct delta_frame *delta; - netplay_input_state_t istate; - const uint32_t *curr_input_state = NULL; - size_t ptr = - netplay->is_replay - ? netplay->replay_ptr - : netplay->run_ptr; - - if (port >= MAX_INPUT_DEVICES) - return 0; - - /* If the port doesn't seem to correspond to the device, "correct" it. This - * is common with devices that typically only have one instance, such as - * keyboards, mice and lightguns. */ - if (device != RETRO_DEVICE_JOYPAD && - (netplay->config_devices[port]&RETRO_DEVICE_MASK) != device) - { - for (port = 0; port < MAX_INPUT_DEVICES; port++) - { - if ((netplay->config_devices[port]&RETRO_DEVICE_MASK) == device) - break; - } - if (port == MAX_INPUT_DEVICES) - return 0; - } - - delta = &netplay->buffer[ptr]; - istate = delta->resolved_input[port]; - if (!istate || !istate->used || istate->size == 0) - return 0; - - curr_input_state = istate->data; - - switch (device) - { - case RETRO_DEVICE_JOYPAD: - if (id == RETRO_DEVICE_ID_JOYPAD_MASK) - return curr_input_state[0]; - return ((1 << id) & curr_input_state[0]) ? 1 : 0; - - case RETRO_DEVICE_ANALOG: - if (istate->size == 3) - { - uint32_t state = curr_input_state[1 + idx]; - return (int16_t)(uint16_t)(state >> (id * 16)); - } - break; - case RETRO_DEVICE_MOUSE: - case RETRO_DEVICE_LIGHTGUN: - if (istate->size == 2) - { - if (id <= RETRO_DEVICE_ID_MOUSE_Y) - return (int16_t)(uint16_t)(curr_input_state[1] >> (id * 16)); - return ((1 << id) & curr_input_state[0]) ? 1 : 0; - } - break; - case RETRO_DEVICE_KEYBOARD: - { - unsigned key = netplay_key_hton(id); - if (key != NETPLAY_KEY_UNKNOWN) - { - unsigned word = key / 32; - unsigned bit = key % 32; - if (word <= istate->size) - return ((UINT32_C(1) << bit) & curr_input_state[word]) ? 1 : 0; - } - } - break; - default: - break; - } - - return 0; -} - static void netplay_announce_cb(retro_task_t *task, void *task_data, void *user_data, const char *error) { @@ -5486,57 +4975,6 @@ static int16_t input_state_net(unsigned port, unsigned device, /* ^^^ Netplay polling callbacks */ -/** -* netplay_frontend_paused - * @netplay : pointer to netplay object - * @paused : true if frontend is paused - * - * Inform Netplay of the frontend's pause state (paused or otherwise) - */ -static void netplay_frontend_paused(netplay_t *netplay, bool paused) -{ - size_t i; - uint32_t paused_ct = 0; - - netplay->local_paused = paused; - - /* Communicating this is a bit odd: If exactly one other connection is - * paused, then we must tell them that we're unpaused, as from their - * perspective we are. If more than one other connection is paused, then our - * status as proxy means we are NOT unpaused to either of them. */ - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection->active && connection->paused) - paused_ct++; - } - - if (paused_ct > 1) - return; - - /* Send our unpaused status. Must send manually because we must immediately - * flush the buffer: If we're paused, we won't be polled. */ - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if ( connection->active - && connection->mode >= NETPLAY_CONNECTION_CONNECTED) - { - if (paused) - netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_PAUSE, - netplay->nick, NETPLAY_NICK_LEN); - else - netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_RESUME, - NULL, 0); - - /* We're not going to be polled, so we need to - * flush this command now */ - netplay_send_flush(&connection->send_packet_buffer, - connection->fd, true); - } - } -} - /** * netplay_disconnect * @netplay : pointer to netplay object @@ -5685,219 +5123,6 @@ static void netplay_post_frame( netplay_disconnect(p_rarch, netplay); } -/** - * netplay_force_future - * @netplay : pointer to netplay object - * - * Force netplay to ignore all past input, typically because we've just loaded - * a state or reset. - */ -static void netplay_force_future(netplay_t *netplay) -{ - /* Wherever we're inputting, that's where we consider our state to be loaded */ - netplay->run_ptr = netplay->self_ptr; - netplay->run_frame_count = netplay->self_frame_count; - - /* We need to ignore any intervening data from the other side, - * and never rewind past this */ - netplay_update_unread_ptr(netplay); - - if (netplay->unread_frame_count < netplay->run_frame_count) - { - uint32_t client; - for (client = 0; client < MAX_CLIENTS; client++) - { - if (!(netplay->connected_players & (1 << client))) - continue; - - if (netplay->read_frame_count[client] < netplay->run_frame_count) - { - netplay->read_ptr[client] = netplay->run_ptr; - netplay->read_frame_count[client] = netplay->run_frame_count; - } - } - if (netplay->server_frame_count < netplay->run_frame_count) - { - netplay->server_ptr = netplay->run_ptr; - netplay->server_frame_count = netplay->run_frame_count; - } - netplay_update_unread_ptr(netplay); - } - if (netplay->other_frame_count < netplay->run_frame_count) - { - netplay->other_ptr = netplay->run_ptr; - netplay->other_frame_count = netplay->run_frame_count; - } -} - -/** - * netplay_send_savestate - * @netplay : pointer to netplay object - * @serial_info : the savestate being loaded - * @cx : compression type - * @z : compression backend to use - * - * Send a loaded savestate to those connected peers using the given compression - * scheme. - */ -static void netplay_send_savestate(netplay_t *netplay, - retro_ctx_serialize_info_t *serial_info, uint32_t cx, - struct compression_transcoder *z) -{ - uint32_t header[4]; - uint32_t rd, wn; - size_t i; - - /* Compress it */ - z->compression_backend->set_in(z->compression_stream, - (const uint8_t*)serial_info->data_const, (uint32_t)serial_info->size); - z->compression_backend->set_out(z->compression_stream, - netplay->zbuffer, (uint32_t)netplay->zbuffer_size); - if (!z->compression_backend->trans(z->compression_stream, true, &rd, - &wn, NULL)) - { - /* Catastrophe! */ - for (i = 0; i < netplay->connections_size; i++) - netplay_hangup(netplay, &netplay->connections[i]); - return; - } - - /* Send it to relevant peers */ - header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); - header[1] = htonl(wn + 2*sizeof(uint32_t)); - header[2] = htonl(netplay->run_frame_count); - header[3] = htonl(serial_info->size); - - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (!connection->active || - connection->mode < NETPLAY_CONNECTION_CONNECTED || - connection->compression_supported != cx) continue; - - if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, - sizeof(header)) || - !netplay_send(&connection->send_packet_buffer, connection->fd, - netplay->zbuffer, wn)) - netplay_hangup(netplay, connection); - } -} - -/** - * netplay_load_savestate - * @netplay : pointer to netplay object - * @serial_info : the savestate being loaded, NULL means - * "load it yourself" - * @save : Whether to save the provided serial_info - * into the frame buffer - * - * Inform Netplay of a savestate load and send it to the other side - **/ -void netplay_load_savestate(netplay_t *netplay, - retro_ctx_serialize_info_t *serial_info, bool save) -{ - retro_ctx_serialize_info_t tmp_serial_info; - - netplay_force_future(netplay); - - /* Record it in our own buffer */ - if (save || !serial_info) - { - /* TODO/FIXME: This is a critical failure! */ - if (!netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->run_ptr], netplay->run_frame_count)) - return; - - if (!serial_info) - { - tmp_serial_info.size = netplay->state_size; - tmp_serial_info.data = netplay->buffer[netplay->run_ptr].state; - if (!core_serialize(&tmp_serial_info)) - return; - tmp_serial_info.data_const = tmp_serial_info.data; - serial_info = &tmp_serial_info; - } - else - { - if (serial_info->size <= netplay->state_size) - memcpy(netplay->buffer[netplay->run_ptr].state, - serial_info->data_const, serial_info->size); - } - } - - /* Don't send it if we're expected to be desynced */ - if (netplay->desync) - return; - - /* If we can't send it to the peer, loading a state was a bad idea */ - if (netplay->quirks & ( - NETPLAY_QUIRK_NO_SAVESTATES - | NETPLAY_QUIRK_NO_TRANSMISSION)) - return; - - /* Send this to every peer */ - if (netplay->compress_nil.compression_backend) - netplay_send_savestate(netplay, serial_info, 0, &netplay->compress_nil); - if (netplay->compress_zlib.compression_backend) - netplay_send_savestate(netplay, serial_info, NETPLAY_COMPRESSION_ZLIB, - &netplay->compress_zlib); -} - -/** - * netplay_core_reset - * @netplay : pointer to netplay object - * - * Indicate that the core has been reset to netplay peers - **/ -static void netplay_core_reset(netplay_t *netplay) -{ - size_t i; - uint32_t cmd[3]; - - /* Ignore past input */ - netplay_force_future(netplay); - - /* Request that our peers reset */ - cmd[0] = htonl(NETPLAY_CMD_RESET); - cmd[1] = htonl(sizeof(uint32_t)); - cmd[2] = htonl(netplay->self_frame_count); - - for (i = 0; i < netplay->connections_size; i++) - { - struct netplay_connection *connection = &netplay->connections[i]; - if (!connection->active || - connection->mode < NETPLAY_CONNECTION_CONNECTED) continue; - - if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, - sizeof(cmd))) - netplay_hangup(netplay, connection); - } -} - -/** - * netplay_toggle_play_spectate - * - * Toggle between play mode and spectate mode - */ -static void netplay_toggle_play_spectate(netplay_t *netplay) -{ - switch (netplay->self_mode) - { - case NETPLAY_CONNECTION_PLAYING: - case NETPLAY_CONNECTION_SLAVE: - /* Switch to spectator mode immediately */ - netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - netplay_cmd_mode(netplay, NETPLAY_CONNECTION_SPECTATING); - break; - case NETPLAY_CONNECTION_SPECTATING: - /* Switch only after getting permission */ - netplay_cmd_mode(netplay, NETPLAY_CONNECTION_PLAYING); - break; - default: - break; - } -} - static void deinit_netplay(struct rarch_state *p_rarch) { if (p_rarch->netplay_data)