/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2021 - 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 . */ #if defined(_MSC_VER) && !defined(_XBOX) #pragma comment(lib, "ws2_32") #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DISCORD #include "../discord.h" #endif #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 "../../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_private.h" #if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) && !defined(_3DS) #define HAVE_INET6 1 #endif #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 */ #define FULL_MAGIC 0x46554C4C /* FULL */ #define POKE_MAGIC 0x504F4B45 /* POKE */ #define MAX_CHAT_SIZE 256 #define CHAT_FRAME_TIME 600 /* * 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]; }; /* TODO/FIXME - globals */ 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; #endif /* The keys supported by netplay */ enum netplay_keys { NETPLAY_KEY_UNKNOWN = 0, #define K(k) NETPLAY_KEY_ ## k, #define KL(k,l) K(k) #include "netplay_keys.h" #undef KL #undef K NETPLAY_KEY_LAST }; /* The mapping of keys from netplay (network) to libretro (host) */ static const uint16_t netplay_key_ntoh_mapping[] = { (uint16_t) RETROK_UNKNOWN, #define K(k) (uint16_t) RETROK_ ## k, #define KL(k,l) (uint16_t) l, #include "netplay_keys.h" #undef KL #undef K 0 }; #define NETPLAY_KEY_NTOH(k) (netplay_key_ntoh_mapping[k]) static net_driver_state_t networking_driver_st = {0}; net_driver_state_t *networking_state_get_ptr(void) { return &networking_driver_st; } #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}; net_driver_state_t *net_st = &networking_driver_st; 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*)&net_st->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 *) &net_st->ad_packet_buffer, "RANS", 4)) continue; /* For this version */ if (ntohl(net_st->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(net_st->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(net_st->ad_packet_buffer.port); } #endif else continue; /* Allocate space for it */ if (net_st->discovered_hosts.size >= net_st->discovered_hosts_allocated) { size_t allocated = net_st->discovered_hosts_allocated; struct netplay_host *new_hosts = NULL; if (allocated == 0) allocated = 2; else allocated *= 2; if (net_st->discovered_hosts.hosts) new_hosts = (struct netplay_host *) realloc(net_st->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; net_st->discovered_hosts.hosts = new_hosts; net_st->discovered_hosts_allocated = allocated; } /* Get our host structure */ host = &net_st->discovered_hosts.hosts[net_st->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(net_st->ad_packet_buffer.port); strlcpy(host->address, net_st->ad_packet_buffer.address, NETPLAY_HOST_STR_LEN); strlcpy(host->nick, net_st->ad_packet_buffer.nick, NETPLAY_HOST_STR_LEN); strlcpy(host->core, net_st->ad_packet_buffer.core, NETPLAY_HOST_STR_LEN); strlcpy(host->retroarch_version, net_st->ad_packet_buffer.retroarch_version, NETPLAY_HOST_STR_LEN); strlcpy(host->core_version, net_st->ad_packet_buffer.core_version, NETPLAY_HOST_STR_LEN); strlcpy(host->content, net_st->ad_packet_buffer.content, NETPLAY_HOST_LONGSTR_LEN); strlcpy(host->subsystem_name, net_st->ad_packet_buffer.subsystem_name, NETPLAY_HOST_LONGSTR_LEN); strlcpy(host->frontend, net_st->ad_packet_buffer.frontend, NETPLAY_HOST_STR_LEN); host->content_crc = atoi(net_st->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; net_driver_state_t *net_st = &networking_driver_st; 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 *)&net_st->ad_packet_buffer, "RANQ", 4); net_st->ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION); for (k = 0; k < (unsigned)interfaces.size; k++) { strlcpy(net_st->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 *) &net_st->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) = &net_st->discovered_hosts; break; case RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES: net_st->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(); net_driver_state_t *net_st = &networking_driver_st; 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*) &net_st->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 *) &net_st->ad_packet_buffer, "RANQ", 4)) { RARCH_LOG("[Discovery] Invalid query\n"); continue; } /* For this version */ if (ntohl(net_st->ad_packet_buffer.protocol_version) != NETPLAY_PROTOCOL_VERSION) { RARCH_LOG("[Discovery] Invalid protocol version\n"); continue; } if (!string_is_empty(net_st->ad_packet_buffer.address)) strlcpy(reply_addr, net_st->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_state_get_ptr()->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(&net_st->ad_packet_buffer, 0, sizeof(struct ad_packet)); memcpy(&net_st->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(net_st->ad_packet_buffer.content, buf, NETPLAY_HOST_LONGSTR_LEN); strlcpy(net_st->ad_packet_buffer.subsystem_name, path_get(RARCH_PATH_SUBSYSTEM), NETPLAY_HOST_STR_LEN); } else { strlcpy(net_st->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(net_st->ad_packet_buffer.subsystem_name, "N/A", NETPLAY_HOST_STR_LEN); } strlcpy(net_st->ad_packet_buffer.address, interfaces.entries[k].host, NETPLAY_HOST_STR_LEN); net_st->ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION); net_st->ad_packet_buffer.port = htonl(netplay->tcp_port); strlcpy(net_st->ad_packet_buffer.retroarch_version, PACKAGE_VERSION, NETPLAY_HOST_STR_LEN); strlcpy(net_st->ad_packet_buffer.nick, netplay->nick, NETPLAY_HOST_STR_LEN); strlcpy(net_st->ad_packet_buffer.frontend, frontend, NETPLAY_HOST_STR_LEN); if (info) { strlcpy(net_st->ad_packet_buffer.core, info->library_name, NETPLAY_HOST_STR_LEN); strlcpy(net_st->ad_packet_buffer.core_version, info->library_version, NETPLAY_HOST_STR_LEN); } snprintf(s, sizeof(s), "%ld", (long)content_crc); strlcpy(net_st->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*)&net_st->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 uint32_t simple_rand_uint32(void) { uint32_t part0 = simple_rand(); uint32_t part1 = simple_rand(); uint32_t part2 = simple_rand(); return ((part0 << 30) + (part1 << 15) + part2); } /* * netplay_init_socket_buffer * * Initialize a new socket buffer. */ static bool netplay_init_socket_buffer( struct socket_buffer *sbuf, size_t size) { sbuf->data = (unsigned char*)malloc(size); if (!sbuf->data) return false; sbuf->bufsz = size; sbuf->start = sbuf->read = sbuf->end = 0; return true; } /** * netplay_deinit_socket_buffer * * Free a socket buffer. */ static void netplay_deinit_socket_buffer(struct socket_buffer *sbuf) { if (sbuf->data) free(sbuf->data); } /** * 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 && (!string_is_empty(settings->paths.netplay_password) || !string_is_empty(settings->paths.netplay_spectate_password))) { /* Demand a password */ if (simple_rand_next == 1) simple_rand_next = (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 */ struct netplay_connection *connection; net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay) return; connection = &netplay->connections[0]; snprintf(password, sizeof(password), "%08lX", (unsigned long)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 netplay_magic = 0; 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; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; memset(header, 0, sizeof(header)); RECV(header, sizeof(header[0])) { dmsg = msg_hash_to_str( netplay->is_server ? MSG_FAILED_TO_CONNECT_TO_CLIENT : MSG_FAILED_TO_CONNECT_TO_HOST); goto error; } netplay_magic = ntohl(header[0]); if (netplay->is_server) { /* Poking the server for information? Just disconnect */ if (netplay_magic == POKE_MAGIC) { socket_close(connection->fd); connection->active = false; netplay_deinit_socket_buffer(&connection->send_packet_buffer); netplay_deinit_socket_buffer(&connection->recv_packet_buffer); return true; } } else { if (netplay_magic == FULL_MAGIC) { dmsg = msg_hash_to_str(MSG_NETPLAY_HOST_FULL); goto error; } } if (netplay_magic != NETPLAY_MAGIC) { dmsg = msg_hash_to_str(MSG_NETPLAY_NOT_RETROARCH); goto error; } RECV(header + 1, sizeof(header) - sizeof(header[0])) { dmsg = msg_hash_to_str( netplay->is_server ? MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT : MSG_FAILED_TO_RECEIVE_HEADER_FROM_HOST); 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; } /* 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)) { dmsg = msg_hash_to_str(MSG_NETPLAY_ENDIAN_DEPENDENT); goto error; } if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) && (local_pmagic != remote_pmagic)) { dmsg = msg_hash_to_str(MSG_NETPLAY_PLATFORM_DEPENDENT); 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); if (extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); dmsg = NULL; } /* 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(); if (!ctrans->decompression_stream) ctrans->decompression_stream = ctrans->decompression_backend->stream_new(); if (!ctrans->compression_stream || !ctrans->decompression_stream) 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(); 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); /* These notifications are useful to the client in figuring out what caused its premature disconnection, but they are quite useless (annoying) to the server. Let them be optional if server. */ if (!netplay->is_server || extra_notifications) 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]; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; 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); /* Useful notification to the client in figuring out if a connection was successfully made before an error, but not as useful to the server. Let it be optional if server. */ if (!netplay->is_server || extra_notifications) 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_state_get_ptr()->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; const char *dmsg = NULL; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; 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)) { dmsg = msg_hash_to_str( netplay->is_server ? MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT : MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST); RARCH_ERR("%s\n", dmsg); /* Useful to the client in figuring out what caused its premature disconnection, but not as useful to the server. Let it be optional if server. */ if (!netplay->is_server || extra_notifications) runloop_msg_queue_push(dmsg, 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) { /* There's a password, so just put them in PRE_PASSWORD mode */ if ( !string_is_empty(settings->paths.netplay_password) || !string_is_empty(settings->paths.netplay_spectate_password)) 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; bool correct = false; settings_t *settings = config_get_ptr(); /* Only the server should call this */ if (!netplay->is_server) return false; 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)) { RARCH_ERR("Failed to receive netplay password.\n"); return false; } /* Calculate the correct password hash(es) and compare */ correct = false; snprintf(password, sizeof(password), "%08lX", (unsigned long)connection->salt); if (!string_is_empty(settings->paths.netplay_password)) { 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 (!correct && !string_is_empty(settings->paths.netplay_spectate_password)) { 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) { RARCH_WARN("A client tried to connect with the wrong password.\n"); 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_state_get_ptr()->system.info; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; RECV(&info_buf, sizeof(info_buf.cmd)) { if (!netplay->is_server) { dmsg = msg_hash_to_str(MSG_NETPLAY_INCORRECT_PASSWORD); RARCH_ERR("%s\n", dmsg); runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); return false; } } 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) - sizeof(info_buf.cmd)) { /* 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) 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); /* Useful to the client in figuring out what caused its premature disconnection, but not as useful to the server. Let it be optional if server. */ if (!netplay->is_server || extra_notifications) 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); if (extra_notifications) 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); if (extra_notifications) 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; /* Only the client should call this */ if (netplay->is_server) return false; RECV(cmd, sizeof(cmd)) {} /* Only expecting a sync command */ if (recvd < 0 || 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("Failed to receive netplay sync.\n"); 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) { net_driver_state_t *net_st = &networking_driver_st; if (key >= RETROK_LAST) return NETPLAY_KEY_UNKNOWN; return net_st->mapping[key]; } /* Because the hton keymapping has to be generated, call this before using * netplay_key_hton */ void netplay_key_hton_init(void) { static bool mapping_defined = false; net_driver_state_t *net_st = &networking_driver_st; if (!mapping_defined) { uint16_t i; for (i = 0; i < NETPLAY_KEY_LAST; i++) net_st->mapping[NETPLAY_KEY_NTOH(i)] = i; mapping_defined = true; } } static void clear_input(netplay_input_state_t istate) { while (istate) { istate->used = false; istate = istate->next; } } /** * netplay_delta_frame_ready * * Prepares, if possible, a delta frame for input, and reports whether it is * ready. * * Returns: True if the delta frame is ready for input at the given frame, * false otherwise. */ bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) { size_t i; if (delta->used) { if (delta->frame == frame) return true; /* We haven't even replayed this frame yet, * so we can't overwrite it! */ if (netplay->other_frame_count <= delta->frame) return false; } delta->used = true; delta->frame = frame; delta->crc = 0; for (i = 0; i < MAX_INPUT_DEVICES; i++) { clear_input(delta->resolved_input[i]); clear_input(delta->real_input[i]); clear_input(delta->simlated_input[i]); } delta->have_local = false; for (i = 0; i < MAX_CLIENTS; i++) delta->have_real[i] = false; return true; } /** * netplay_delta_frame_crc * * Get the CRC for the serialization of this frame. */ static uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) { return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size); } /* * Free an input state list */ static void free_input_state(netplay_input_state_t *list) { netplay_input_state_t cur, next; cur = *list; while (cur) { next = cur->next; free(cur); cur = next; } *list = NULL; } /** * netplay_delta_frame_free * * Free a delta frame's dependencies */ static void netplay_delta_frame_free(struct delta_frame *delta) { uint32_t i; if (delta->state) { free(delta->state); delta->state = NULL; } for (i = 0; i < MAX_INPUT_DEVICES; i++) { free_input_state(&delta->resolved_input[i]); free_input_state(&delta->real_input[i]); free_input_state(&delta->simlated_input[i]); } } /** * netplay_input_state_for * * Get an input state for a particular client */ netplay_input_state_t netplay_input_state_for( netplay_input_state_t *list, uint32_t client_num, size_t size, bool must_create, bool must_not_create) { netplay_input_state_t ret; while (*list) { ret = *list; if (!ret->used && !must_not_create && ret->size == size) { ret->client_num = client_num; ret->used = true; memset(ret->data, 0, size*sizeof(uint32_t)); return ret; } else if (ret->used && ret->client_num == client_num) { if (!must_create && ret->size == size) return ret; return NULL; } list = &(ret->next); } if (must_not_create) return NULL; /* Couldn't find a slot, allocate a fresh one */ if (size > 1) ret = (netplay_input_state_t)calloc(1, sizeof(struct netplay_input_state) + (size-1) * sizeof(uint32_t)); else ret = (netplay_input_state_t)calloc(1, sizeof(struct netplay_input_state)); if (!ret) return NULL; *list = ret; ret->client_num = client_num; ret->used = true; ret->size = (uint32_t)size; return ret; } /** * netplay_expected_input_size * * Size in words for a given set of devices. */ uint32_t netplay_expected_input_size(netplay_t *netplay, uint32_t devices) { uint32_t ret = 0, device; for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (!(devices & (1<config_devices[device]&RETRO_DEVICE_MASK) { /* These are all essentially magic numbers, but each device has a * fixed size, documented in network/netplay/README */ case RETRO_DEVICE_JOYPAD: ret += 1; break; case RETRO_DEVICE_MOUSE: ret += 2; break; case RETRO_DEVICE_KEYBOARD: ret += 5; break; case RETRO_DEVICE_LIGHTGUN: ret += 2; break; case RETRO_DEVICE_ANALOG: ret += 3; break; default: break; /* Unsupported */ } } return ret; } static size_t buf_used(struct socket_buffer *sbuf) { if (sbuf->end < sbuf->start) { size_t newend = sbuf->end; while (newend < sbuf->start) newend += sbuf->bufsz; return newend - sbuf->start; } return sbuf->end - sbuf->start; } static size_t buf_unread(struct socket_buffer *sbuf) { if (sbuf->end < sbuf->read) { size_t newend = sbuf->end; while (newend < sbuf->read) newend += sbuf->bufsz; return newend - sbuf->read; } return sbuf->end - sbuf->read; } static size_t buf_remaining(struct socket_buffer *sbuf) { return sbuf->bufsz - buf_used(sbuf) - 1; } /** * netplay_resize_socket_buffer * * Resize the given socket_buffer's buffer to the requested size. */ static bool netplay_resize_socket_buffer( struct socket_buffer *sbuf, size_t newsize) { unsigned char *newdata = (unsigned char*)malloc(newsize); if (!newdata) return false; /* Copy in the old data */ if (sbuf->end < sbuf->start) { memcpy(newdata, sbuf->data + sbuf->start, sbuf->bufsz - sbuf->start); memcpy(newdata + sbuf->bufsz - sbuf->start, sbuf->data, sbuf->end); } else if (sbuf->end > sbuf->start) memcpy(newdata, sbuf->data + sbuf->start, sbuf->end - sbuf->start); /* Adjust our read offset */ if (sbuf->read < sbuf->start) sbuf->read += sbuf->bufsz - sbuf->start; else sbuf->read -= sbuf->start; /* Adjust start and end */ sbuf->end = buf_used(sbuf); sbuf->start = 0; /* Free the old one and replace it with the new one */ free(sbuf->data); sbuf->data = newdata; sbuf->bufsz = newsize; return true; } /** * netplay_send * * Queue the given data for sending. */ bool netplay_send( struct socket_buffer *sbuf, int sockfd, const void *buf, size_t len) { if (buf_remaining(sbuf) < len) { /* Need to force a blocking send */ if (!netplay_send_flush(sbuf, sockfd, true)) return false; } if (buf_remaining(sbuf) < len) { /* Can only be that this is simply too big * for our buffer, in which case we just * need to do a blocking send */ if (!socket_send_all_blocking(sockfd, buf, len, false)) return false; return true; } /* Copy it into our buffer */ if (sbuf->bufsz - sbuf->end < len) { /* Half at a time */ size_t chunka = sbuf->bufsz - sbuf->end, chunkb = len - chunka; memcpy(sbuf->data + sbuf->end, buf, chunka); memcpy(sbuf->data, (const unsigned char *)buf + chunka, chunkb); sbuf->end = chunkb; } else { /* Straight in */ memcpy(sbuf->data + sbuf->end, buf, len); sbuf->end += len; } return true; } /** * netplay_send_flush * * Flush unsent data in the given socket buffer, blocking to do so if * requested. * * Returns false only on socket failures, true otherwise. */ bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block) { ssize_t sent; if (buf_used(sbuf) == 0) return true; if (sbuf->end > sbuf->start) { /* Usual case: Everything's in order */ if (block) { if (!socket_send_all_blocking( sockfd, sbuf->data + sbuf->start, buf_used(sbuf), true)) return false; sbuf->start = sbuf->end = 0; } else { sent = socket_send_all_nonblocking( sockfd, sbuf->data + sbuf->start, buf_used(sbuf), true); if (sent < 0) return false; sbuf->start += sent; if (sbuf->start == sbuf->end) sbuf->start = sbuf->end = 0; } } else { /* Unusual case: Buffer overlaps break */ if (block) { if (!socket_send_all_blocking( sockfd, sbuf->data + sbuf->start, sbuf->bufsz - sbuf->start, true)) return false; sbuf->start = 0; return netplay_send_flush(sbuf, sockfd, true); } else { sent = socket_send_all_nonblocking( sockfd, sbuf->data + sbuf->start, sbuf->bufsz - sbuf->start, true); if (sent < 0) return false; sbuf->start += sent; if (sbuf->start >= sbuf->bufsz) { sbuf->start = 0; return netplay_send_flush(sbuf, sockfd, false); } } } return true; } /** * netplay_recv * * Receive buffered or fresh data. * * Returns number of bytes returned, which may be short or 0, or -1 on error. */ ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t len, bool block) { ssize_t recvd; bool error = false; /* Receive whatever we can into the buffer */ if (sbuf->end >= sbuf->start) { recvd = socket_receive_all_nonblocking(sockfd, &error, sbuf->data + sbuf->end, sbuf->bufsz - sbuf->end - ((sbuf->start == 0) ? 1 : 0)); if (recvd < 0 || error) return -1; sbuf->end += recvd; if (sbuf->end >= sbuf->bufsz) { sbuf->end = 0; error = false; recvd = socket_receive_all_nonblocking( sockfd, &error, sbuf->data, sbuf->start - 1); if (recvd < 0 || error) return -1; sbuf->end += recvd; } } else { recvd = socket_receive_all_nonblocking( sockfd, &error, sbuf->data + sbuf->end, sbuf->start - sbuf->end - 1); if (recvd < 0 || error) return -1; sbuf->end += recvd; } /* Now copy it into the reader */ if (sbuf->end >= sbuf->read || (sbuf->bufsz - sbuf->read) >= len) { size_t unread = buf_unread(sbuf); if (len <= unread) { memcpy(buf, sbuf->data + sbuf->read, len); sbuf->read += len; if (sbuf->read >= sbuf->bufsz) sbuf->read = 0; recvd = len; } else { memcpy(buf, sbuf->data + sbuf->read, unread); sbuf->read += unread; if (sbuf->read >= sbuf->bufsz) sbuf->read = 0; recvd = unread; } } else { /* Our read goes around the edge */ size_t chunka = sbuf->bufsz - sbuf->read, pchunklen = len - chunka, chunkb = (pchunklen >= sbuf->end) ? sbuf->end : pchunklen; memcpy(buf, sbuf->data + sbuf->read, chunka); memcpy((unsigned char *) buf + chunka, sbuf->data, chunkb); sbuf->read = chunkb; recvd = chunka + chunkb; } /* Perhaps block for more data */ if (block) { sbuf->start = sbuf->read; if (recvd < 0 || recvd < (ssize_t) len) { if (!socket_receive_all_blocking( sockfd, (unsigned char *)buf + recvd, len - recvd)) return -1; recvd = len; } } return recvd; } /** * netplay_recv_reset * * Reset our recv buffer so that future netplay_recvs * will read the same data again. */ void netplay_recv_reset(struct socket_buffer *sbuf) { sbuf->read = sbuf->start; } /** * netplay_recv_flush * * Flush our recv buffer, so a future netplay_recv_reset will reset to this * point. */ void netplay_recv_flush(struct socket_buffer *sbuf) { sbuf->start = sbuf->read; } static bool netplay_full(netplay_t *netplay, int sockfd) { size_t i; settings_t *settings = config_get_ptr(); unsigned max_connections = settings->uints.netplay_max_connections; unsigned total = 0; if (max_connections) { for (i = 0; i < netplay->connections_size; i++) if (netplay->connections[i].active) total++; if (total >= max_connections) { /* We send a header to let the client know we are full */ uint32_t header[6]; /* The only parameter that we need to set is netplay magic; we set the protocol version parameter too for backwards compatibility */ memset(header, 0, sizeof(header)); header[0] = htonl(FULL_MAGIC); header[4] = htonl(NETPLAY_PROTOCOL_VERSION); /* The kernel might close the socket before sending our data. This is fine; the header is just a warning for the client. */ socket_send_all_nonblocking(sockfd, header, sizeof(header), false); return true; } } return false; } /** * netplay_cmd_crc * * Send a CRC command to all active clients. */ static bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) { size_t i; uint32_t payload[2]; bool success = true; payload[0] = htonl(delta->frame); payload[1] = htonl(delta->crc); for (i = 0; i < netplay->connections_size; i++) { if (netplay->connections[i].active && netplay->connections[i].mode >= NETPLAY_CONNECTION_CONNECTED) success = netplay_send_raw_cmd(netplay, &netplay->connections[i], NETPLAY_CMD_CRC, payload, sizeof(payload)) && success; } return success; } /** * netplay_cmd_request_savestate * * Send a savestate request command. */ static bool netplay_cmd_request_savestate(netplay_t *netplay) { if (netplay->connections_size == 0 || !netplay->connections[0].active || netplay->connections[0].mode < NETPLAY_CONNECTION_CONNECTED) return false; if (netplay->savestate_request_outstanding) return true; netplay->savestate_request_outstanding = true; return netplay_send_raw_cmd(netplay, &netplay->connections[0], NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); } /** * netplay_cmd_stall * * Send a stall command. */ static bool netplay_cmd_stall(netplay_t *netplay, struct netplay_connection *connection, uint32_t frames) { frames = htonl(frames); return netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_STALL, &frames, sizeof(frames)); } static void handle_play_spectate(netplay_t *netplay, uint32_t client_num, struct netplay_connection *connection, uint32_t cmd, uint32_t cmd_size, uint32_t *payload); #if 0 #define DEBUG_NONDETERMINISTIC_CORES #endif /** * netplay_update_unread_ptr * * Update the global unread_ptr and unread_frame_count to correspond to the * earliest unread frame count of any connected player */ void netplay_update_unread_ptr(netplay_t *netplay) { if (netplay->is_server && netplay->connected_players<=1) { /* Nothing at all to read! */ netplay->unread_ptr = netplay->self_ptr; netplay->unread_frame_count = netplay->self_frame_count; } else { size_t new_unread_ptr = 0; uint32_t new_unread_frame_count = (uint32_t) -1; uint32_t client; for (client = 0; client < MAX_CLIENTS; client++) { if (!(netplay->connected_players & (1 << client))) continue; if ((netplay->connected_slaves & (1 << client))) continue; if (netplay->read_frame_count[client] < new_unread_frame_count) { new_unread_ptr = netplay->read_ptr[client]; new_unread_frame_count = netplay->read_frame_count[client]; } } if ( !netplay->is_server && netplay->server_frame_count < new_unread_frame_count) { new_unread_ptr = netplay->server_ptr; new_unread_frame_count = netplay->server_frame_count; } if (new_unread_frame_count != (uint32_t) -1) { netplay->unread_ptr = new_unread_ptr; netplay->unread_frame_count = new_unread_frame_count; } else { netplay->unread_ptr = netplay->self_ptr; netplay->unread_frame_count = netplay->self_frame_count; } } } /** * netplay_device_client_state * @netplay : pointer to netplay object * @simframe : frame in which merging is being performed * @device : device being merged * @client : client to find state for */ netplay_input_state_t netplay_device_client_state(netplay_t *netplay, struct delta_frame *simframe, uint32_t device, uint32_t client) { uint32_t dsize = netplay_expected_input_size(netplay, 1 << device); netplay_input_state_t simstate = netplay_input_state_for( &simframe->real_input[device], client, dsize, false, true); if (!simstate) { if (netplay->read_frame_count[client] > simframe->frame) return NULL; simstate = netplay_input_state_for(&simframe->simlated_input[device], client, dsize, false, true); } return simstate; } /** * netplay_merge_digital * @netplay : pointer to netplay object * @resstate : state being resolved * @simframe : frame in which merging is being performed * @device : device being merged * @clients : bitmap of clients being merged * @digital : bitmap of digital bits */ static void netplay_merge_digital(netplay_t *netplay, netplay_input_state_t resstate, struct delta_frame *simframe, uint32_t device, uint32_t clients, const uint32_t *digital) { netplay_input_state_t simstate; uint32_t word, bit, client; uint8_t share_mode = netplay->device_share_modes[device] & NETPLAY_SHARE_DIGITAL_BITS; /* Make sure all real clients are accounted for */ for (simstate = simframe->real_input[device]; simstate; simstate = simstate->next) { if (!simstate->used || simstate->size != resstate->size) continue; clients |= 1 << simstate->client_num; } if (share_mode == NETPLAY_SHARE_DIGITAL_VOTE) { unsigned i, j; /* This just assumes we have no more than * three words, will need to be adjusted for new devices */ struct vote_count votes[3]; /* Vote mode requires counting all the bits */ uint32_t client_count = 0; for (i = 0; i < 3; i++) for (j = 0; j < 32; j++) votes[i].votes[j] = 0; for (client = 0; client < MAX_CLIENTS; client++) { if (!(clients & (1 << client))) continue; simstate = netplay_device_client_state( netplay, simframe, device, client); if (!simstate) continue; client_count++; for (word = 0; word < resstate->size; word++) { if (!digital[word]) continue; for (bit = 0; bit < 32; bit++) { if (!(digital[word] & (1 << bit))) continue; if (simstate->data[word] & (1 << bit)) votes[word].votes[bit]++; } } } /* Now count all the bits */ client_count /= 2; for (word = 0; word < resstate->size; word++) { for (bit = 0; bit < 32; bit++) { if (votes[word].votes[bit] > client_count) resstate->data[word] |= (1 << bit); } } } else /* !VOTE */ { for (client = 0; client < MAX_CLIENTS; client++) { if (!(clients & (1 << client))) continue; simstate = netplay_device_client_state( netplay, simframe, device, client); if (!simstate) continue; for (word = 0; word < resstate->size; word++) { uint32_t part; if (!digital[word]) continue; part = simstate->data[word]; if (digital[word] == (uint32_t) -1) { /* Combine the whole word */ switch (share_mode) { case NETPLAY_SHARE_DIGITAL_XOR: resstate->data[word] ^= part; break; default: resstate->data[word] |= part; } } else /* !whole word */ { for (bit = 0; bit < 32; bit++) { if (!(digital[word] & (1 << bit))) continue; switch (share_mode) { case NETPLAY_SHARE_DIGITAL_XOR: resstate->data[word] ^= part & (1 << bit); break; default: resstate->data[word] |= part & (1 << bit); } } } } } } } /** * merge_analog_part * @netplay : pointer to netplay object * @resstate : state being resolved * @simframe : frame in which merging is being performed * @device : device being merged * @clients : bitmap of clients being merged * @word : word to merge * @bit : first bit to merge */ static void merge_analog_part(netplay_t *netplay, netplay_input_state_t resstate, struct delta_frame *simframe, uint32_t device, uint32_t clients, uint32_t word, uint8_t bit) { netplay_input_state_t simstate; uint32_t client, client_count = 0; uint8_t share_mode = netplay->device_share_modes[device] & NETPLAY_SHARE_ANALOG_BITS; int32_t value = 0, new_value; /* Make sure all real clients are accounted for */ for (simstate = simframe->real_input[device]; simstate; simstate = simstate->next) { if (!simstate->used || simstate->size != resstate->size) continue; clients |= 1 << simstate->client_num; } for (client = 0; client < MAX_CLIENTS; client++) { if (!(clients & (1 << client))) continue; simstate = netplay_device_client_state( netplay, simframe, device, client); if (!simstate) continue; client_count++; new_value = (int16_t) ((simstate->data[word]>>bit) & 0xFFFF); switch (share_mode) { case NETPLAY_SHARE_ANALOG_AVERAGE: value += (int32_t) new_value; break; default: if (abs(new_value) > abs(value) || (abs(new_value) == abs(value) && new_value > value)) value = new_value; } } if (share_mode == NETPLAY_SHARE_ANALOG_AVERAGE) if (client_count > 0) /* Prevent potential divide by zero */ value /= client_count; resstate->data[word] |= ((uint32_t) (uint16_t) value) << bit; } /** * netplay_merge_analog * @netplay : pointer to netplay object * @resstate : state being resolved * @simframe : frame in which merging is being performed * @device : device being merged * @clients : bitmap of clients being merged * @dtype : device type */ static void netplay_merge_analog(netplay_t *netplay, netplay_input_state_t resstate, struct delta_frame *simframe, uint32_t device, uint32_t clients, unsigned dtype) { /* Devices with no analog parts */ if (dtype == RETRO_DEVICE_JOYPAD || dtype == RETRO_DEVICE_KEYBOARD) return; /* All other devices have at least one analog word */ merge_analog_part(netplay, resstate, simframe, device, clients, 1, 0); merge_analog_part(netplay, resstate, simframe, device, clients, 1, 16); /* And the ANALOG device has two (two sticks) */ if (dtype == RETRO_DEVICE_ANALOG) { merge_analog_part(netplay, resstate, simframe, device, clients, 2, 0); merge_analog_part(netplay, resstate, simframe, device, clients, 2, 16); } } /** * netplay_resolve_input * @netplay : pointer to netplay object * @sim_ptr : frame pointer for which to resolve input * @resim : are we resimulating, or simulating this frame for the * first time? * * "Simulate" input by assuming it hasn't changed since the last read input. * Returns true if the resolved input changed from the last time it was * resolved. */ bool netplay_resolve_input(netplay_t *netplay, size_t sim_ptr, bool resim) { size_t prev; uint32_t device; uint32_t clients, client, client_count; netplay_input_state_t simstate, client_state = NULL, resstate, oldresstate, pstate; bool ret = false; struct delta_frame *pframe = NULL; struct delta_frame *simframe = &netplay->buffer[sim_ptr]; for (device = 0; device < MAX_INPUT_DEVICES; device++) { unsigned dtype = netplay->config_devices[device]&RETRO_DEVICE_MASK; uint32_t dsize = netplay_expected_input_size(netplay, 1 << device); clients = netplay->device_clients[device]; client_count = 0; /* Make sure all real clients are accounted for */ for (simstate = simframe->real_input[device]; simstate; simstate = simstate->next) { if (!simstate->used || simstate->size != dsize) continue; clients |= 1 << simstate->client_num; } for (client = 0; client < MAX_CLIENTS; client++) { if (!(clients & (1 << client))) continue; /* Resolve this client-device */ simstate = netplay_input_state_for( &simframe->real_input[device], client, dsize, false, true); if (!simstate) { /* Don't already have this input, so must * simulate if we're supposed to have it at all */ if (netplay->read_frame_count[client] > simframe->frame) continue; simstate = netplay_input_state_for(&simframe->simlated_input[device], client, dsize, false, false); if (!simstate) continue; prev = PREV_PTR(netplay->read_ptr[client]); pframe = &netplay->buffer[prev]; pstate = netplay_input_state_for(&pframe->real_input[device], client, dsize, false, true); if (!pstate) continue; if (resim && (dtype == RETRO_DEVICE_JOYPAD || dtype == RETRO_DEVICE_ANALOG)) { /* In resimulation mode, we only copy the buttons. The reason for this * is nonobvious: * * If we resimulated nothing, then the /duration/ with which any input * was pressed would be approximately correct, since the original * simulation came in as the input came in, but the /number of times/ * the input was pressed would be wrong, as there would be an * advancing wavefront of real data overtaking the simulated data * (which is really just real data offset by some frames). * * That's acceptable for arrows in most situations, since the amount * you move is tied to the duration, but unacceptable for buttons, * which will seem to jerkily be pressed numerous times with those * wavefronts. */ const uint32_t keep = (UINT32_C(1) << RETRO_DEVICE_ID_JOYPAD_UP) | (UINT32_C(1) << RETRO_DEVICE_ID_JOYPAD_DOWN) | (UINT32_C(1) << RETRO_DEVICE_ID_JOYPAD_LEFT) | (UINT32_C(1) << RETRO_DEVICE_ID_JOYPAD_RIGHT); simstate->data[0] &= keep; simstate->data[0] |= pstate->data[0] & ~keep; } else memcpy(simstate->data, pstate->data, dsize * sizeof(uint32_t)); } client_state = simstate; client_count++; } /* The frontend always uses the first resolved input, * so make sure it's right */ while (simframe->resolved_input[device] && (simframe->resolved_input[device]->size != dsize || simframe->resolved_input[device]->client_num != 0)) { /* The default resolved input is of the wrong size! */ netplay_input_state_t nextistate = simframe->resolved_input[device]->next; free(simframe->resolved_input[device]); simframe->resolved_input[device] = nextistate; } /* Now we copy the state, whether real or simulated, * out into the resolved state */ resstate = netplay_input_state_for( &simframe->resolved_input[device], 0, dsize, false, false); if (!resstate) continue; if (client_count == 1) { /* Trivial in the common 1-client case */ if (memcmp(resstate->data, client_state->data, dsize * sizeof(uint32_t))) ret = true; memcpy(resstate->data, client_state->data, dsize * sizeof(uint32_t)); } else if (client_count == 0) { uint32_t word; for (word = 0; word < dsize; word++) { if (resstate->data[word]) ret = true; resstate->data[word] = 0; } } else { /* Merge them */ /* Most devices have all the digital parts in the first word. */ static const uint32_t digital_common[3] = {~0u, 0u, 0u}; static const uint32_t digital_keyboard[5] = {~0u, ~0u, ~0u, ~0u, ~0u}; const uint32_t *digital = digital_common; if (dtype == RETRO_DEVICE_KEYBOARD) digital = digital_keyboard; oldresstate = netplay_input_state_for( &simframe->resolved_input[device], 1, dsize, false, false); if (!oldresstate) continue; memcpy(oldresstate->data, resstate->data, dsize * sizeof(uint32_t)); memset(resstate->data, 0, dsize * sizeof(uint32_t)); netplay_merge_digital(netplay, resstate, simframe, device, clients, digital); netplay_merge_analog(netplay, resstate, simframe, device, clients, dtype); if (memcmp(resstate->data, oldresstate->data, dsize * sizeof(uint32_t))) ret = true; } } return ret; } static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta) { if (netplay->is_server) { if (netplay->check_frames && delta->frame % abs(netplay->check_frames) == 0) { if (netplay->state_size) delta->crc = netplay_delta_frame_crc(netplay, delta); else delta->crc = 0; netplay_cmd_crc(netplay, delta); } } else if (delta->crc && netplay->crcs_valid) { /* We have a remote CRC, so check it */ uint32_t local_crc = 0; if (netplay->state_size) local_crc = netplay_delta_frame_crc(netplay, delta); if (local_crc != delta->crc) { /* If the very first check frame is wrong, * they probably just don't work */ if (!netplay->crc_validity_checked) netplay->crcs_valid = false; else if (netplay->crcs_valid) { /* Fix this! */ if (netplay->check_frames < 0) { /* Just report */ RARCH_ERR("Netplay CRCs mismatch!\n"); } else netplay_cmd_request_savestate(netplay); } } else if (!netplay->crc_validity_checked) netplay->crc_validity_checked = true; } } /** * netplay_sync_pre_frame * @netplay : pointer to netplay object * * Pre-frame for Netplay synchronization. */ bool netplay_sync_pre_frame(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->run_ptr], netplay->run_frame_count)) { serial_info.data_const = NULL; serial_info.data = netplay->buffer[netplay->run_ptr].state; serial_info.size = netplay->state_size; memset(serial_info.data, 0, serial_info.size); if ((netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) || netplay->run_frame_count == 0) { /* Don't serialize until it's safe */ } else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info)) { if (netplay->force_send_savestate && !netplay->stall && !netplay->remote_paused) { /* Bring our running frame and input frames into * parity so we don't send old info. */ if (netplay->run_ptr != netplay->self_ptr) { memcpy(netplay->buffer[netplay->self_ptr].state, netplay->buffer[netplay->run_ptr].state, netplay->state_size); netplay->run_ptr = netplay->self_ptr; netplay->run_frame_count = netplay->self_frame_count; } /* Send this along to the other side */ serial_info.data_const = netplay->buffer[netplay->run_ptr].state; netplay_load_savestate(netplay, &serial_info, false); netplay->force_send_savestate = false; } } else { /* If the core can't serialize properly, we must stall for the * remote input on EVERY frame, because we can't recover */ netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES; netplay->stateless_mode = true; } /* If we can't transmit savestates, we must stall * until the client is ready. */ if (netplay->run_frame_count > 0 && (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)) && (netplay->connections_size == 0 || !netplay->connections[0].active || netplay->connections[0].mode < NETPLAY_CONNECTION_CONNECTED)) netplay->stall = NETPLAY_STALL_NO_CONNECTION; } if (netplay->is_server) { fd_set fds; struct timeval tmp_tv = {0}; int new_fd; struct sockaddr_storage their_addr; socklen_t addr_size; struct netplay_connection *connection; size_t connection_num; /* Check for a connection */ FD_ZERO(&fds); FD_SET(netplay->listen_fd, &fds); if (socket_select(netplay->listen_fd + 1, &fds, NULL, NULL, &tmp_tv) > 0 && FD_ISSET(netplay->listen_fd, &fds)) { addr_size = sizeof(their_addr); new_fd = accept(netplay->listen_fd, (struct sockaddr*)&their_addr, &addr_size); if (new_fd < 0) goto process; /* Set the socket nonblocking */ if (!socket_nonblock(new_fd)) { /* Catastrophe! */ socket_close(new_fd); goto process; } #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) { int on = 1; if (setsockopt(new_fd, IPPROTO_TCP, TCP_NODELAY, (const char*) &on, sizeof(on)) < 0) RARCH_WARN("Could not set netplay TCP socket to nodelay. Expect jitter.\n"); } #endif #if defined(F_SETFD) && defined(FD_CLOEXEC) /* Don't let any inherited processes keep open our port */ if (fcntl(new_fd, F_SETFD, FD_CLOEXEC) < 0) RARCH_WARN("Cannot set Netplay port to close-on-exec. It may fail to reopen if the client disconnects.\n"); #endif if (netplay_full(netplay, new_fd)) { /* Not accepting any more connections. */ socket_close(new_fd); goto process; } /* Allocate a connection */ for (connection_num = 0; connection_num < netplay->connections_size; connection_num++) if (!netplay->connections[connection_num].active && netplay->connections[connection_num].mode != NETPLAY_CONNECTION_DELAYED_DISCONNECT) break; if (connection_num == netplay->connections_size) { if (connection_num == 0) { netplay->connections = (struct netplay_connection*) malloc(sizeof(struct netplay_connection)); if (!netplay->connections) { socket_close(new_fd); goto process; } netplay->connections_size = 1; } else { size_t new_connections_size = netplay->connections_size * 2; struct netplay_connection *new_connections = (struct netplay_connection*) realloc(netplay->connections, new_connections_size*sizeof(struct netplay_connection)); if (!new_connections) { socket_close(new_fd); goto process; } memset(new_connections + netplay->connections_size, 0, netplay->connections_size * sizeof(struct netplay_connection)); netplay->connections = new_connections; netplay->connections_size = new_connections_size; } } connection = &netplay->connections[connection_num]; /* Set it up */ memset(connection, 0, sizeof(*connection)); connection->active = true; connection->fd = new_fd; connection->mode = NETPLAY_CONNECTION_INIT; if (!netplay_init_socket_buffer(&connection->send_packet_buffer, netplay->packet_buffer_size) || !netplay_init_socket_buffer(&connection->recv_packet_buffer, netplay->packet_buffer_size)) { if (connection->send_packet_buffer.data) netplay_deinit_socket_buffer(&connection->send_packet_buffer); connection->active = false; socket_close(new_fd); goto process; } netplay_handshake_init_send(netplay, connection); } } process: netplay->can_poll = true; input_poll_net(); return (netplay->stall != NETPLAY_STALL_NO_CONNECTION); } /** * netplay_sync_post_frame * @netplay : pointer to netplay object * * Post-frame for Netplay synchronization. * We check if we have new input and replay from recorded input. */ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) { uint32_t lo_frame_count, hi_frame_count; /* Unless we're stalling, we've just finished running a frame */ if (!stalled) { netplay->run_ptr = NEXT_PTR(netplay->run_ptr); netplay->run_frame_count++; } /* We've finished an input frame even if we're stalling */ if ((!stalled || netplay->stall == NETPLAY_STALL_INPUT_LATENCY) && netplay->self_frame_count < netplay->run_frame_count + netplay->input_latency_frames) { netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; } /* Only relevant if we're connected and not in a desynching operation */ if ((netplay->is_server && (netplay->connected_players<=1)) || (netplay->self_mode < NETPLAY_CONNECTION_CONNECTED) || (netplay->desync)) { netplay->other_frame_count = netplay->self_frame_count; netplay->other_ptr = netplay->self_ptr; /* FIXME: Duplication */ if (netplay->catch_up) { netplay->catch_up = false; input_state_get_ptr()->nonblocking_flag = false; driver_set_nonblock_state(); } return; } /* Reset if it was requested */ if (netplay->force_reset) { core_reset(); netplay->force_reset = false; } netplay->replay_ptr = netplay->other_ptr; netplay->replay_frame_count = netplay->other_frame_count; #ifndef DEBUG_NONDETERMINISTIC_CORES if (!netplay->force_rewind) { bool cont = true; /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ while (netplay->other_frame_count < netplay->unread_frame_count && netplay->other_frame_count < netplay->run_frame_count) { struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; /* If resolving the input changes it, we used bad input */ if (netplay_resolve_input(netplay, netplay->other_ptr, true)) { cont = false; break; } netplay_handle_frame_hash(netplay, ptr); netplay->other_ptr = NEXT_PTR(netplay->other_ptr); netplay->other_frame_count++; } netplay->replay_ptr = netplay->other_ptr; netplay->replay_frame_count = netplay->other_frame_count; if (cont) { while (netplay->replay_frame_count < netplay->run_frame_count) { if (netplay_resolve_input(netplay, netplay->replay_ptr, true)) break; netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; } } } #endif /* Now replay the real input if we've gotten ahead of it */ if (netplay->force_rewind || netplay->replay_frame_count < netplay->run_frame_count) { retro_ctx_serialize_info_t serial_info; /* Replay frames. */ netplay->is_replay = true; /* If we have a keyboard device, we replay the previous frame's input * just to assert that the keydown/keyup events work if the core * translates them in that way */ if (netplay->have_updown_device) { netplay->replay_ptr = PREV_PTR(netplay->replay_ptr); netplay->replay_frame_count--; #ifdef HAVE_THREADS autosave_lock(); #endif core_run(); #ifdef HAVE_THREADS autosave_unlock(); #endif netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; } if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) /* Make sure we're initialized before we start loading things */ netplay_wait_and_init_serialization(netplay); serial_info.data = NULL; serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; serial_info.size = netplay->state_size; if (!core_unserialize(&serial_info)) { RARCH_ERR("Netplay savestate loading failed: Prepare for desync!\n"); } while (netplay->replay_frame_count < netplay->run_frame_count) { retro_time_t start, tm; struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr]; serial_info.data = ptr->state; serial_info.size = netplay->state_size; serial_info.data_const = NULL; start = cpu_features_get_time_usec(); /* Remember the current state */ memset(serial_info.data, 0, serial_info.size); core_serialize(&serial_info); if (netplay->replay_frame_count < netplay->unread_frame_count) netplay_handle_frame_hash(netplay, ptr); /* Re-simulate this frame's input */ netplay_resolve_input(netplay, netplay->replay_ptr, true); #ifdef HAVE_THREADS autosave_lock(); #endif core_run(); #ifdef HAVE_THREADS autosave_unlock(); #endif netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; #ifdef DEBUG_NONDETERMINISTIC_CORES if (ptr->have_remote && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->replay_ptr], netplay->replay_frame_count)) { RARCH_LOG("PRE %u: %X\n", netplay->replay_frame_count-1, netplay->state_size ? netplay_delta_frame_crc(netplay, ptr) : 0); if (netplay->is_server) RARCH_LOG("INP %X %X\n", ptr->real_input_state[0], ptr->self_state[0]); else RARCH_LOG("INP %X %X\n", ptr->self_state[0], ptr->real_input_state[0]); ptr = &netplay->buffer[netplay->replay_ptr]; serial_info.data = ptr->state; memset(serial_info.data, 0, serial_info.size); core_serialize(&serial_info); RARCH_LOG("POST %u: %X\n", netplay->replay_frame_count-1, netplay->state_size ? netplay_delta_frame_crc(netplay, ptr) : 0); } #endif /* Get our time window */ tm = cpu_features_get_time_usec() - start; netplay->frame_run_time_sum -= netplay->frame_run_time[netplay->frame_run_time_ptr]; netplay->frame_run_time[netplay->frame_run_time_ptr] = tm; netplay->frame_run_time_sum += tm; netplay->frame_run_time_ptr++; if (netplay->frame_run_time_ptr >= NETPLAY_FRAME_RUN_TIME_WINDOW) netplay->frame_run_time_ptr = 0; } /* Average our time */ netplay->frame_run_time_avg = netplay->frame_run_time_sum / NETPLAY_FRAME_RUN_TIME_WINDOW; if (netplay->unread_frame_count < netplay->run_frame_count) { netplay->other_ptr = netplay->unread_ptr; netplay->other_frame_count = netplay->unread_frame_count; } else { netplay->other_ptr = netplay->run_ptr; netplay->other_frame_count = netplay->run_frame_count; } netplay->is_replay = false; netplay->force_rewind = false; } if (netplay->is_server) { uint32_t client; lo_frame_count = hi_frame_count = netplay->unread_frame_count; /* Look for players that are ahead of us */ for (client = 0; client < MAX_CLIENTS; client++) { if (!(netplay->connected_players & (1 << client))) continue; if (netplay->read_frame_count[client] > hi_frame_count) hi_frame_count = netplay->read_frame_count[client]; } } else lo_frame_count = hi_frame_count = netplay->server_frame_count; /* If we're behind, try to catch up */ if (netplay->catch_up) { /* Are we caught up? */ if (netplay->self_frame_count + 1 >= lo_frame_count) { netplay->catch_up = false; input_state_get_ptr()->nonblocking_flag = false; driver_set_nonblock_state(); } } else if (!stalled) { if (netplay->self_frame_count + 3 < lo_frame_count) { retro_time_t cur_time = cpu_features_get_time_usec(); uint32_t cur_behind = lo_frame_count - netplay->self_frame_count; /* We're behind, but we'll only try to catch up if we're actually * falling behind, i.e. if we're more behind after some time */ if (netplay->catch_up_time == 0) { /* Record our current time to check for catch-up later */ netplay->catch_up_time = cur_time; netplay->catch_up_behind = cur_behind; } else if (cur_time - netplay->catch_up_time > CATCH_UP_CHECK_TIME_USEC) { /* Time to check how far behind we are */ if (netplay->catch_up_behind <= cur_behind) { /* We're definitely falling behind! */ netplay->catch_up = true; netplay->catch_up_time = 0; input_state_get_ptr()->nonblocking_flag = true; driver_set_nonblock_state(); } else { /* Check again in another period */ netplay->catch_up_time = cur_time; netplay->catch_up_behind = cur_behind; } } } else if (netplay->self_frame_count + 3 < hi_frame_count) { size_t i; netplay->catch_up_time = 0; /* We're falling behind some clients but not others, so request that * clients ahead of us stall */ for (i = 0; i < netplay->connections_size; i++) { uint32_t client_num; struct netplay_connection *connection = &netplay->connections[i]; if (!connection->active || connection->mode != NETPLAY_CONNECTION_PLAYING) continue; client_num = (uint32_t)(i + 1); /* Are they ahead? */ if (netplay->self_frame_count + 3 < netplay->read_frame_count[client_num]) { /* Tell them to stall */ if (connection->stall_frame + NETPLAY_MAX_REQ_STALL_FREQUENCY < netplay->self_frame_count) { connection->stall_frame = netplay->self_frame_count; netplay_cmd_stall(netplay, connection, netplay->read_frame_count[client_num] - netplay->self_frame_count + 1); } } } } else netplay->catch_up_time = 0; } else netplay->catch_up_time = 0; } #if 0 #define DEBUG_NETPLAY_STEPS 1 static void print_state(netplay_t *netplay) { char msg[512]; size_t cur = 0; uint32_t client; #define APPEND(out) cur += snprintf out #define M msg + cur, sizeof(msg) - cur APPEND((M, "NETPLAY: S:%u U:%u O:%u", netplay->self_frame_count, netplay->unread_frame_count, netplay->other_frame_count)); if (!netplay->is_server) APPEND((M, " H:%u", netplay->server_frame_count)); for (client = 0; client < MAX_USERS; client++) { if ((netplay->connected_players & (1<read_frame_count[client])); } msg[sizeof(msg)-1] = '\0'; RARCH_LOG("[netplay] %s\n", msg); #undef APPEND #undef M } #endif /** * remote_unpaused * * Mark a particular remote connection as unpaused and, if relevant, inform * every one else that they may resume. */ static void remote_unpaused(netplay_t *netplay, struct netplay_connection *connection) { size_t i; connection->paused = false; netplay->remote_paused = false; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *sc = &netplay->connections[i]; if (sc->active && sc->paused) { netplay->remote_paused = true; break; } } if (!netplay->remote_paused && !netplay->local_paused) netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_RESUME, NULL, 0); } /** * netplay_hangup: * * Disconnects an active Netplay connection due to an error */ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) { char msg[512]; const char *dmsg; size_t i; bool was_playing = false; settings_t *settings = config_get_ptr(); bool extra_notifications = settings->bools.notification_show_netplay_extra; if (!netplay || !connection->active) return; was_playing = connection->mode == NETPLAY_CONNECTION_PLAYING || connection->mode == NETPLAY_CONNECTION_SLAVE; /* Report this disconnection */ if (netplay->is_server) { if (!string_is_empty(connection->nick)) { snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_NETPLAY_SERVER_NAMED_HANGUP), connection->nick); dmsg = msg; } else dmsg = msg_hash_to_str(MSG_NETPLAY_SERVER_HANGUP); } else { dmsg = msg_hash_to_str(MSG_NETPLAY_CLIENT_HANGUP); #ifdef HAVE_DISCORD if (discord_state_get_ptr()->inited) { discord_userdata_t userdata; userdata.status = DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED; command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); } #endif netplay->is_connected = false; } RARCH_LOG("%s\n", dmsg); /* This notification is really only important to the server if the client was playing. * Let it be optional if server and the client wasn't playing. */ if (!netplay->is_server || was_playing || extra_notifications) runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); socket_close(connection->fd); connection->active = false; netplay_deinit_socket_buffer(&connection->send_packet_buffer); netplay_deinit_socket_buffer(&connection->recv_packet_buffer); if (!netplay->is_server) { netplay->self_mode = NETPLAY_CONNECTION_NONE; netplay->connected_players &= (1L<self_client_num); for (i = 0; i < MAX_CLIENTS; i++) { if (i != netplay->self_client_num) netplay->client_devices[i] = 0; } for (i = 0; i < MAX_INPUT_DEVICES; i++) netplay->device_clients[i] &= (1L<self_client_num); netplay->stall = NETPLAY_STALL_NONE; } else { /* Mark the player for removal */ if (was_playing) { uint32_t client_num = (uint32_t) (connection - netplay->connections + 1); /* This special mode keeps the connection object alive long enough to send the disconnection message at the correct time */ connection->mode = NETPLAY_CONNECTION_DELAYED_DISCONNECT; connection->delay_frame = netplay->read_frame_count[client_num]; /* Mark them as not playing anymore */ netplay->connected_players &= ~(1L<connected_slaves &= ~(1L<client_devices[client_num] = 0; for (i = 0; i < MAX_INPUT_DEVICES; i++) netplay->device_clients[i] &= ~(1L<paused) remote_unpaused(netplay, connection); } /** * netplay_delayed_state_change: * * Handle any pending state changes which are ready * as of the beginning of the current frame. */ void netplay_delayed_state_change(netplay_t *netplay) { unsigned i; for (i = 0; i < netplay->connections_size; i++) { uint32_t client_num = (uint32_t)(i + 1); struct netplay_connection *connection = &netplay->connections[i]; if ((connection->active || connection->mode == NETPLAY_CONNECTION_DELAYED_DISCONNECT) && connection->delay_frame && connection->delay_frame <= netplay->self_frame_count) { /* Something was delayed! Prepare the MODE command */ uint32_t payload[15] = {0}; payload[0] = htonl(connection->delay_frame); payload[1] = htonl(client_num); payload[2] = htonl(0); memcpy(payload + 3, netplay->device_share_modes, sizeof(netplay->device_share_modes)); strncpy((char *) (payload + 7), connection->nick, NETPLAY_NICK_LEN); /* Remove the connection entirely if relevant */ if (connection->mode == NETPLAY_CONNECTION_DELAYED_DISCONNECT) connection->mode = NETPLAY_CONNECTION_NONE; /* Then send the mode change packet */ netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); /* And forget the delay frame */ connection->delay_frame = 0; } } } /* Send the specified input data */ static bool send_input_frame(netplay_t *netplay, struct delta_frame *dframe, struct netplay_connection *only, struct netplay_connection *except, uint32_t client_num, bool slave) { #define BUFSZ 16 /* FIXME: Arbitrary restriction */ uint32_t buffer[BUFSZ], devices, device; size_t bufused, i; /* Set up the basic buffer */ bufused = 4; buffer[0] = htonl(NETPLAY_CMD_INPUT); buffer[2] = htonl(dframe->frame); buffer[3] = htonl(client_num); /* Add the device data */ devices = netplay->client_devices[client_num]; for (device = 0; device < MAX_INPUT_DEVICES; device++) { netplay_input_state_t istate; if (!(devices & (1<real_input[device]; while (istate && (!istate->used || istate->client_num != (slave?MAX_CLIENTS:client_num))) istate = istate->next; if (!istate) continue; if (bufused + istate->size >= BUFSZ) continue; /* FIXME: More severe? */ for (i = 0; i < istate->size; i++) buffer[bufused+i] = htonl(istate->data[i]); bufused += istate->size; } buffer[1] = htonl((bufused-2) * sizeof(uint32_t)); #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Sending input for client %u\n", (unsigned) client_num); print_state(netplay); #endif if (only) { if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, bufused*sizeof(uint32_t))) { netplay_hangup(netplay, only); return false; } } else { for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection == except) continue; if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED && (connection->mode != NETPLAY_CONNECTION_PLAYING || i+1 != client_num)) { if (!netplay_send(&connection->send_packet_buffer, connection->fd, buffer, bufused*sizeof(uint32_t))) netplay_hangup(netplay, connection); } } } return true; #undef BUFSZ } /** * netplay_send_cur_input * * Send the current input frame to a given connection. * * Returns true if successful, false otherwise. */ bool netplay_send_cur_input(netplay_t *netplay, struct netplay_connection *connection) { uint32_t from_client, to_client; struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr]; if (netplay->is_server) { to_client = (uint32_t)(connection - netplay->connections + 1); /* Send the other players' input data (FIXME: This involves an * unacceptable amount of recalculating) */ for (from_client = 1; from_client < MAX_CLIENTS; from_client++) { if (from_client == to_client) continue; if ((netplay->connected_players & (1<have_real[from_client]) { if (!send_input_frame(netplay, dframe, connection, NULL, from_client, false)) return false; } } } /* If we're not playing, send a NOINPUT */ if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING) { uint32_t payload = htonl(netplay->self_frame_count); if (!netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NOINPUT, &payload, sizeof(payload))) return false; } } /* Send our own data */ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || netplay->self_mode == NETPLAY_CONNECTION_SLAVE) { if (!send_input_frame(netplay, dframe, connection, NULL, netplay->self_client_num, netplay->self_mode == NETPLAY_CONNECTION_SLAVE)) return false; } if (!netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) return false; return true; } /** * netplay_send_raw_cmd * * Send a raw Netplay command to the given connection. * * Returns true on success, false on failure. */ bool netplay_send_raw_cmd(netplay_t *netplay, struct netplay_connection *connection, uint32_t cmd, const void *data, size_t size) { uint32_t cmdbuf[2]; cmdbuf[0] = htonl(cmd); cmdbuf[1] = htonl(size); if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmdbuf, sizeof(cmdbuf))) return false; if (size > 0) if (!netplay_send(&connection->send_packet_buffer, connection->fd, data, size)) return false; return true; } /** * netplay_send_raw_cmd_all * * Send a raw Netplay command to all connections, optionally excluding one * (typically the client that the relevant command came from) */ void netplay_send_raw_cmd_all(netplay_t *netplay, struct netplay_connection *except, uint32_t cmd, const void *data, size_t size) { size_t i; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection == except) continue; if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) { if (!netplay_send_raw_cmd(netplay, connection, cmd, data, size)) netplay_hangup(netplay, connection); } } } /** * netplay_send_flush_all * * Flush all of our output buffers */ static void netplay_send_flush_all(netplay_t *netplay, struct netplay_connection *except) { size_t i; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection == except) continue; if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) { if (!netplay_send_flush(&connection->send_packet_buffer, connection->fd, true)) netplay_hangup(netplay, connection); } } } static bool netplay_cmd_nak(netplay_t *netplay, struct netplay_connection *connection) { netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NAK, NULL, 0); return false; } /** * netplay_settings_share_mode * * Get the preferred share mode */ static uint8_t netplay_settings_share_mode( unsigned share_digital, unsigned share_analog) { if (share_digital || share_analog) { uint8_t share_mode = 0; switch (share_digital) { case RARCH_NETPLAY_SHARE_DIGITAL_OR: share_mode |= NETPLAY_SHARE_DIGITAL_OR; break; case RARCH_NETPLAY_SHARE_DIGITAL_XOR: share_mode |= NETPLAY_SHARE_DIGITAL_XOR; break; case RARCH_NETPLAY_SHARE_DIGITAL_VOTE: share_mode |= NETPLAY_SHARE_DIGITAL_VOTE; break; default: share_mode |= NETPLAY_SHARE_NO_PREFERENCE; } switch (share_analog) { case RARCH_NETPLAY_SHARE_ANALOG_MAX: share_mode |= NETPLAY_SHARE_ANALOG_MAX; break; case RARCH_NETPLAY_SHARE_ANALOG_AVERAGE: share_mode |= NETPLAY_SHARE_ANALOG_AVERAGE; break; default: share_mode |= NETPLAY_SHARE_NO_PREFERENCE; } return share_mode; } return 0; } /** * netplay_cmd_mode * * Send a mode change request. As a server, the request is to ourself, and so * honored instantly. */ bool netplay_cmd_mode(netplay_t *netplay, enum rarch_netplay_connection_mode mode) { uint32_t cmd, device; uint32_t payload_buf = 0, *payload = NULL; uint8_t share_mode = 0; struct netplay_connection *connection = NULL; if (!netplay->is_server) connection = &netplay->one_connection; switch (mode) { case NETPLAY_CONNECTION_SPECTATING: cmd = NETPLAY_CMD_SPECTATE; break; case NETPLAY_CONNECTION_SLAVE: payload_buf = NETPLAY_CMD_PLAY_BIT_SLAVE; /* no break */ case NETPLAY_CONNECTION_PLAYING: { settings_t *settings = config_get_ptr(); payload = &payload_buf; /* Add a share mode if requested */ share_mode = netplay_settings_share_mode( settings->uints.netplay_share_digital, settings->uints.netplay_share_analog ); payload_buf |= ((uint32_t) share_mode) << 16; /* Request devices */ for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (settings->bools.netplay_request_devices[device]) payload_buf |= 1<is_server) { handle_play_spectate(netplay, 0, NULL, cmd, payload ? sizeof(uint32_t) : 0, payload); return true; } return netplay_send_raw_cmd(netplay, connection, cmd, payload, payload ? sizeof(uint32_t) : 0); } /** * announce_play_spectate * * Announce a play or spectate mode change */ static void announce_play_spectate(netplay_t *netplay, const char *nick, enum rarch_netplay_connection_mode mode, uint32_t devices) { char msg[512]; const char *dmsg = NULL; switch (mode) { case NETPLAY_CONNECTION_SPECTATING: if (nick) { snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_NETPLAY_PLAYER_S_LEFT), NETPLAY_NICK_LEN, nick); dmsg = msg; } else dmsg = msg_hash_to_str(MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME); break; case NETPLAY_CONNECTION_PLAYING: case NETPLAY_CONNECTION_SLAVE: { char device_str[256]; uint32_t device; uint32_t one_device = (uint32_t) -1; char *pdevice_str = NULL; for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (!(devices & (1< device_str) pdevice_str -= 2; *pdevice_str = '\0'; /* Then we make the final string */ if (nick) snprintf(msg, sizeof(msg), msg_hash_to_str( MSG_NETPLAY_S_HAS_JOINED_WITH_INPUT_DEVICES_S), NETPLAY_NICK_LEN, nick, sizeof(device_str), device_str); else snprintf(msg, sizeof(msg), msg_hash_to_str( MSG_NETPLAY_YOU_HAVE_JOINED_WITH_INPUT_DEVICES_S), sizeof(device_str), device_str); } dmsg = msg; break; } default: /* wrong usage */ return; } RARCH_LOG("%s\n", dmsg); runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } /** * handle_play_spectate * * Handle a play or spectate request */ static void handle_play_spectate(netplay_t *netplay, uint32_t client_num, struct netplay_connection *connection, uint32_t cmd, uint32_t cmd_size, uint32_t *in_payload) { /* * MODE payload: * word 0: frame number * word 1: mode info (playing, slave, client number) * word 2: device bitmap * words 3-6: share modes for all devices * words 7-14: client nick */ uint32_t payload[15] = {0}; switch (cmd) { case NETPLAY_CMD_SPECTATE: { size_t i; /* The frame we haven't received is their end frame */ if (connection) connection->delay_frame = netplay->read_frame_count[client_num]; /* Mark them as not playing anymore */ if (connection) connection->mode = NETPLAY_CONNECTION_SPECTATING; else { netplay->self_devices = 0; netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; } netplay->connected_players &= ~(1 << client_num); netplay->connected_slaves &= ~(1 << client_num); netplay->client_devices[client_num] = 0; for (i = 0; i < MAX_INPUT_DEVICES; i++) netplay->device_clients[i] &= ~(1 << client_num); /* Tell someone */ payload[0] = htonl(netplay->read_frame_count[client_num]); payload[2] = htonl(0); memcpy(payload + 3, netplay->device_share_modes, sizeof(netplay->device_share_modes)); if (connection) { /* Only tell the player. The others will be told at delay_frame */ payload[1] = htonl(NETPLAY_CMD_MODE_BIT_YOU | client_num); strncpy((char *) (payload + 7), connection->nick, NETPLAY_NICK_LEN); netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); } else { /* It was the server, so tell everyone else */ payload[1] = htonl(0); strncpy((char *) (payload + 7), netplay->nick, NETPLAY_NICK_LEN); netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload)); } /* Announce it */ announce_play_spectate(netplay, connection ? connection->nick : NULL, NETPLAY_CONNECTION_SPECTATING, 0); break; } case NETPLAY_CMD_PLAY: { uint32_t mode, devices = 0, device; uint8_t share_mode; bool slave = false; settings_t *settings = config_get_ptr(); if (cmd_size != sizeof(uint32_t) || !in_payload) return; mode = ntohl(in_payload[0]); /* Check the requested mode */ slave = (mode&NETPLAY_CMD_PLAY_BIT_SLAVE)?true:false; share_mode = (mode>>16)&0xFF; /* And the requested devices */ devices = mode&0xFFFF; /* Check if their slave mode request corresponds with what we allow */ if (connection) { if (settings->bools.netplay_require_slaves) slave = true; else if (!settings->bools.netplay_allow_slaves) slave = false; } else slave = false; /* Fix our share mode */ if (share_mode) { if ((share_mode & NETPLAY_SHARE_DIGITAL_BITS) == 0) share_mode |= NETPLAY_SHARE_DIGITAL_OR; if ((share_mode & NETPLAY_SHARE_ANALOG_BITS) == 0) share_mode |= NETPLAY_SHARE_ANALOG_MAX; share_mode &= ~NETPLAY_SHARE_NO_PREFERENCE; } /* They start at the next frame, but we start immediately */ if (connection) { netplay->read_ptr[client_num] = NEXT_PTR(netplay->self_ptr); netplay->read_frame_count[client_num] = netplay->self_frame_count + 1; } else { netplay->read_ptr[client_num] = netplay->self_ptr; netplay->read_frame_count[client_num] = netplay->self_frame_count; } payload[0] = htonl(netplay->read_frame_count[client_num]); if (devices) { /* Make sure the devices are available and/or shareable */ for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (!(devices & (1<device_clients[device]) continue; if (netplay->device_share_modes[device] && share_mode) continue; /* Device already taken and unshareable */ payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NOT_AVAILABLE); /* FIXME: Refusal message for the server */ if (connection) netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t)); devices = 0; break; } if (devices == 0) break; /* Set the share mode on any new devices */ for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (!(devices & (1<device_clients[device]) netplay->device_share_modes[device] = share_mode; } } else { /* Find an available device */ for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (netplay->config_devices[device] == RETRO_DEVICE_NONE) { device = MAX_INPUT_DEVICES; break; } if (!netplay->device_clients[device]) break; } if (device >= MAX_INPUT_DEVICES && netplay->config_devices[1] == RETRO_DEVICE_NONE && share_mode) { /* No device free and no device specifically asked for, but only * one device, so share it */ if (netplay->device_share_modes[0]) { device = 0; share_mode = netplay->device_share_modes[0]; break; } } if (device >= MAX_INPUT_DEVICES) { /* No slots free! */ payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS); /* FIXME: Message for the server */ if (connection) netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t)); break; } devices = 1<device_share_modes[device] = share_mode; } payload[2] = htonl(devices); /* Mark them as playing */ if (connection) connection->mode = slave ? NETPLAY_CONNECTION_SLAVE : NETPLAY_CONNECTION_PLAYING; else { netplay->self_devices = devices; netplay->self_mode = NETPLAY_CONNECTION_PLAYING; } netplay->connected_players |= 1 << client_num; if (slave) netplay->connected_slaves |= 1 << client_num; netplay->client_devices[client_num] = devices; for (device = 0; device < MAX_INPUT_DEVICES; device++) { if (!(devices & (1<device_clients[device] |= 1 << client_num; } /* Tell everyone */ payload[1] = htonl( NETPLAY_CMD_MODE_BIT_PLAYING | (slave ? NETPLAY_CMD_MODE_BIT_SLAVE : 0) | client_num); memcpy(payload + 3, netplay->device_share_modes, sizeof(netplay->device_share_modes)); if (connection) strncpy((char *) (payload + 7), connection->nick, NETPLAY_NICK_LEN); else strncpy((char *) (payload + 7), netplay->nick, NETPLAY_NICK_LEN); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); /* Tell the player */ if (connection) { payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | ((connection->mode == NETPLAY_CONNECTION_SLAVE)? NETPLAY_CMD_MODE_BIT_SLAVE:0) | NETPLAY_CMD_MODE_BIT_YOU | client_num); netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); } /* Announce it */ announce_play_spectate(netplay, connection ? connection->nick : NULL, NETPLAY_CONNECTION_PLAYING, devices); break; } } } static bool chat_check(netplay_t *netplay) { if (!netplay) return false; /* Do nothing if we don't have a nickname. */ if (string_is_empty(netplay->nick)) return false; /* Do nothing if we are not playing. */ if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING && netplay->self_mode != NETPLAY_CONNECTION_SLAVE) return false; /* If we are the server, check if someone is able to read us. */ if (netplay->is_server) { size_t i; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *conn = &netplay->connections[i]; if (conn->active && (conn->mode == NETPLAY_CONNECTION_PLAYING || conn->mode == NETPLAY_CONNECTION_SLAVE)) return true; } } /* Otherwise, just check whether our connection is active. */ else { if (netplay->connections[0].active) return true; } return false; } static INLINE size_t format_chat(char *s, size_t len, const char *nick, const char *msg) { /* Truncate the message if necessary. */ snprintf(s, len, "%s: %s", nick, msg); return strlen(s); } static INLINE void relay_chat(netplay_t *netplay, const char *msg, size_t len) { size_t i; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *conn = &netplay->connections[i]; /* Only playing clients can receive chat. */ if (conn->active && (conn->mode == NETPLAY_CONNECTION_PLAYING || conn->mode == NETPLAY_CONNECTION_SLAVE)) netplay_send_raw_cmd(netplay, conn, NETPLAY_CMD_PLAYER_CHAT, msg, len); } /* We don't flush. Chat is not time essential. */ } static INLINE void show_chat(const char *msg) { RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, CHAT_FRAME_TIME, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } static void send_chat(void *userdata, const char *line) { char chat_msg[MAX_CHAT_SIZE]; size_t chat_len; net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; /* We perform the same checks, just in case something has changed. */ if (!string_is_empty(line) && chat_check(netplay)) { /* For servers, we need to format and relay it ourselves. */ if (netplay->is_server) { chat_len = format_chat(chat_msg, sizeof(chat_msg), netplay->nick, line); relay_chat(netplay, chat_msg, chat_len); show_chat(chat_msg); } /* For clients, we just send it to the server. */ else { strlcpy(chat_msg, line, sizeof(chat_msg)); netplay_send_raw_cmd(netplay, &netplay->connections[0], NETPLAY_CMD_PLAYER_CHAT, chat_msg, strlen(chat_msg)); /* We don't flush. Chat is not time essential. */ } } menu_input_dialog_end(); retroarch_menu_running_finished(false); } void netplay_input_chat(netplay_t *netplay) { #if NETPLAY_PROTOCOL_VERSION >= 6 && defined(HAVE_MENU) if (chat_check(netplay)) { menu_input_ctx_line_t chat_input = {0}; retroarch_menu_running(); chat_input.label = msg_hash_to_str(MSG_NETPLAY_ENTER_CHAT); chat_input.label_setting = "no_setting"; chat_input.cb = send_chat; menu_input_dialog_start(&chat_input); } #endif } /** * handle_chat * * Handle a received chat message */ static bool handle_chat(netplay_t *netplay, struct netplay_connection *connection, const char *msg) { char chat_msg[MAX_CHAT_SIZE]; size_t chat_len; if (!connection->active || string_is_empty(msg)) return false; /* Client sent a chat message; format it and then relay it to the other clients, including the one who sent it. */ if (netplay->is_server) { /* No point displaying a chat message without a nickname. */ if (string_is_empty(connection->nick)) return false; /* Only playing clients can send chat. */ if (connection->mode != NETPLAY_CONNECTION_PLAYING && connection->mode != NETPLAY_CONNECTION_SLAVE) return false; chat_len = format_chat(chat_msg, sizeof(chat_msg), connection->nick, msg); msg = chat_msg; relay_chat(netplay, chat_msg, chat_len); } /* If we still got a message even though we are not playing, ignore it! */ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING || netplay->self_mode == NETPLAY_CONNECTION_SLAVE) show_chat(msg); return true; } #undef RECV #define RECV(buf, sz) \ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \ (sz), false); \ if (recvd >= 0 && recvd < (ssize_t) (sz)) goto shrt; \ else if (recvd < 0) static bool netplay_get_cmd(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { uint32_t cmd; uint32_t cmd_size; ssize_t recvd; /* We don't handle the initial handshake here */ if (connection->mode < NETPLAY_CONNECTION_CONNECTED) return netplay_handshake(netplay, connection, had_input); RECV(&cmd, sizeof(cmd)) return false; cmd = ntohl(cmd); RECV(&cmd_size, sizeof(cmd_size)) return false; cmd_size = ntohl(cmd_size); #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Received netplay command %X (%u) from %u\n", cmd, cmd_size, (unsigned) (connection - netplay->connections)); #endif netplay->timeout_cnt = 0; switch (cmd) { case NETPLAY_CMD_ACK: /* Why are we even bothering? */ break; case NETPLAY_CMD_NAK: /* Disconnect now! */ return false; case NETPLAY_CMD_INPUT: { uint32_t frame_num, client_num, input_size, devices, device; struct delta_frame *dframe; if (cmd_size < 2*sizeof(uint32_t)) { RARCH_ERR("NETPLAY_CMD_INPUT too short, no frame/client number."); return netplay_cmd_nak(netplay, connection); } RECV(&frame_num, sizeof(frame_num)) return false; RECV(&client_num, sizeof(client_num)) return false; frame_num = ntohl(frame_num); client_num = ntohl(client_num); client_num &= 0xFFFF; if (netplay->is_server) { /* Ignore the claimed client #, must be this client */ if (connection->mode != NETPLAY_CONNECTION_PLAYING && connection->mode != NETPLAY_CONNECTION_SLAVE) { RARCH_ERR("Netplay input from non-participating player.\n"); return netplay_cmd_nak(netplay, connection); } client_num = (uint32_t)(connection - netplay->connections + 1); } if (client_num >= MAX_CLIENTS) { RARCH_ERR("NETPLAY_CMD_INPUT received data for an unsupported client.\n"); return netplay_cmd_nak(netplay, connection); } if (!(netplay->connected_players & (1<client_devices[client_num]; input_size = netplay_expected_input_size(netplay, devices); if (cmd_size != (2+input_size) * sizeof(uint32_t)) { RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } /* Check the frame number only if they're not in slave mode */ if (connection->mode == NETPLAY_CONNECTION_PLAYING) { if (frame_num < netplay->read_frame_count[client_num]) { uint32_t buf; /* We already had this, so ignore the new transmission */ for (; input_size; input_size--) { RECV(&buf, sizeof(uint32_t)) return false; } break; } else if (frame_num > netplay->read_frame_count[client_num]) { /* Out of order = out of luck */ RARCH_ERR("Netplay input out of order.\n"); return netplay_cmd_nak(netplay, connection); } } /* The data's good! */ dframe = &netplay->buffer[netplay->read_ptr[client_num]]; if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[client_num])) { /* Hopefully we'll be ready after another round of input */ goto shrt; } /* Copy in the input */ for (device = 0; device < MAX_INPUT_DEVICES; device++) { netplay_input_state_t istate; uint32_t dsize, di; if (!(devices & (1<real_input[device], client_num, dsize, false /* Must be false because of slave-mode clients */, false); if (!istate) { /* Catastrophe! */ return netplay_cmd_nak(netplay, connection); } RECV(istate->data, dsize*sizeof(uint32_t)) return false; for (di = 0; di < dsize; di++) istate->data[di] = ntohl(istate->data[di]); } dframe->have_real[client_num] = true; /* Slaves may go through several packets of data in the same frame * if latency is choppy, so we advance and send their data after * handling all network data this frame */ if (connection->mode == NETPLAY_CONNECTION_PLAYING) { netplay->read_ptr[client_num] = NEXT_PTR(netplay->read_ptr[client_num]); netplay->read_frame_count[client_num]++; if (netplay->is_server) { /* Forward it on if it's past data */ if (dframe->frame <= netplay->self_frame_count) send_input_frame(netplay, dframe, NULL, connection, client_num, false); } } /* If this was server data, advance our server pointer too */ if (!netplay->is_server && client_num == 0) { netplay->server_ptr = netplay->read_ptr[0]; netplay->server_frame_count = netplay->read_frame_count[0]; } #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Received input from %u\n", client_num); print_state(netplay); #endif break; } case NETPLAY_CMD_NOINPUT: { uint32_t frame; if (netplay->is_server) { RARCH_ERR("NETPLAY_CMD_NOINPUT from a client.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(frame)) { RARCH_ERR("NETPLAY_CMD_NOINPUT received" " an unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(&frame, sizeof(frame)) return false; frame = ntohl(frame); /* We already had this, so ignore the new transmission */ if (frame < netplay->server_frame_count) break; if (frame != netplay->server_frame_count) { RARCH_ERR("NETPLAY_CMD_NOINPUT for invalid frame.\n"); return netplay_cmd_nak(netplay, connection); } netplay->server_ptr = NEXT_PTR(netplay->server_ptr); netplay->server_frame_count++; #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Received server noinput\n"); print_state(netplay); #endif break; } case NETPLAY_CMD_SPECTATE: { uint32_t client_num; if (!netplay->is_server) { RARCH_ERR("NETPLAY_CMD_SPECTATE from a server.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != 0) { RARCH_ERR("Unexpected payload in NETPLAY_CMD_SPECTATE.\n"); return netplay_cmd_nak(netplay, connection); } if ( connection->mode != NETPLAY_CONNECTION_PLAYING && connection->mode != NETPLAY_CONNECTION_SLAVE) { /* They were confused */ RARCH_ERR("NETPLAY_CMD_SPECTATE from client not currently playing.\n"); return netplay_cmd_nak(netplay, connection); } client_num = (uint32_t)(connection - netplay->connections + 1); handle_play_spectate(netplay, client_num, connection, cmd, 0, NULL); break; } case NETPLAY_CMD_PLAY: { uint32_t client_num; uint32_t payload; if (!netplay->is_server) { RARCH_ERR("NETPLAY_CMD_PLAY from a server.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(payload)) { RARCH_ERR("Incorrect NETPLAY_CMD_PLAY payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(&payload, sizeof(payload)) return false; if (connection->delay_frame) { /* Can't switch modes while a mode switch is already in progress. */ payload = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_TOO_FAST); netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, &payload, sizeof(payload)); break; } if (!connection->can_play) { /* Not allowed to play */ payload = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED); netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, &payload, sizeof(payload)); break; } /* They were obviously confused */ if ( connection->mode == NETPLAY_CONNECTION_PLAYING || connection->mode == NETPLAY_CONNECTION_SLAVE) { RARCH_ERR("NETPLAY_CMD_PLAY from client already playing.\n"); return netplay_cmd_nak(netplay, connection); } client_num = (unsigned)(connection - netplay->connections + 1); handle_play_spectate(netplay, client_num, connection, cmd, cmd_size, &payload); break; } case NETPLAY_CMD_MODE: { uint32_t payload[15]; uint32_t frame, mode, client_num, devices, device; size_t ptr; struct delta_frame *dframe; const char *nick; #define START(which) \ do { \ ptr = which; \ dframe = &netplay->buffer[ptr]; \ } while (0) #define NEXT() \ do { \ ptr = NEXT_PTR(ptr); \ dframe = &netplay->buffer[ptr]; \ } while (0) if (netplay->is_server) { RARCH_ERR("NETPLAY_CMD_MODE from client.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(payload)) { RARCH_ERR("Invalid payload size for NETPLAY_CMD_MODE.\n"); return netplay_cmd_nak(netplay, connection); } RECV(payload, sizeof(payload)) return false; frame = ntohl(payload[0]); /* We're changing past input, so must replay it */ if (frame < netplay->self_frame_count) netplay->force_rewind = true; mode = ntohl(payload[1]); client_num = mode & 0xFFFF; if (client_num >= MAX_CLIENTS) { RARCH_ERR("Received NETPLAY_CMD_MODE for a higher player number than we support.\n"); return netplay_cmd_nak(netplay, connection); } devices = ntohl(payload[2]); memcpy(netplay->device_share_modes, payload + 3, sizeof(netplay->device_share_modes)); nick = (const char *) (payload + 7); if (mode & NETPLAY_CMD_MODE_BIT_YOU) { /* A change to me! */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { if (frame != netplay->server_frame_count) { RARCH_ERR("Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); } /* Hooray, I get to play now! */ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { RARCH_ERR("Received player mode change even though I'm already a player.\n"); return netplay_cmd_nak(netplay, connection); } /* Our mode is based on whether we have the slave bit set */ if (mode & NETPLAY_CMD_MODE_BIT_SLAVE) netplay->self_mode = NETPLAY_CONNECTION_SLAVE; else netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay->connected_players |= (1<client_devices[client_num] = devices; for (device = 0; device < MAX_INPUT_DEVICES; device++) if (devices & (1<device_clients[device] |= (1<self_devices = devices; netplay->read_ptr[client_num] = netplay->server_ptr; netplay->read_frame_count[client_num] = netplay->server_frame_count; /* Fix up current frame info */ if (!(mode & NETPLAY_CMD_MODE_BIT_SLAVE) && frame <= netplay->self_frame_count) { /* It wanted past frames, better send 'em! */ START(netplay->server_ptr); while (dframe->used && dframe->frame <= netplay->self_frame_count) { for (device = 0; device < MAX_INPUT_DEVICES; device++) { uint32_t dsize; netplay_input_state_t istate; if (!(devices & (1<real_input[device], client_num, dsize, false, false); if (!istate) continue; memset(istate->data, 0, dsize*sizeof(uint32_t)); } dframe->have_local = true; dframe->have_real[client_num] = true; send_input_frame(netplay, dframe, connection, NULL, client_num, false); if (dframe->frame == netplay->self_frame_count) break; NEXT(); } } else { uint32_t frame_count; /* It wants future frames, make sure we don't capture or send intermediate ones */ START(netplay->self_ptr); frame_count = netplay->self_frame_count; for (;;) { if (!dframe->used) { /* Make sure it's ready */ if (!netplay_delta_frame_ready(netplay, dframe, frame_count)) { RARCH_ERR("Received mode change but delta frame isn't ready!\n"); return netplay_cmd_nak(netplay, connection); } } dframe->have_local = true; /* Go on to the next delta frame */ NEXT(); frame_count++; if (frame_count >= frame) break; } } /* Announce it */ announce_play_spectate(netplay, NULL, NETPLAY_CONNECTION_PLAYING, devices); #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Received mode change self->%X\n", devices); print_state(netplay); #endif } else /* YOU && !PLAYING */ { /* I'm no longer playing, but I should already know this */ if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING) { RARCH_ERR("Received mode change to spectator unprompted.\n"); return netplay_cmd_nak(netplay, connection); } /* Unmark ourself, in case we were in slave mode */ netplay->connected_players &= ~(1<client_devices[client_num] = 0; for (device = 0; device < MAX_INPUT_DEVICES; device++) netplay->device_clients[device] &= ~(1<spectating\n"); print_state(netplay); #endif } } else /* !YOU */ { /* Somebody else is joining or parting */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { if (frame != netplay->server_frame_count) { RARCH_ERR("Received mode change out of order.\n"); return netplay_cmd_nak(netplay, connection); } netplay->connected_players |= (1<client_devices[client_num] = devices; for (device = 0; device < MAX_INPUT_DEVICES; device++) if (devices & (1<device_clients[device] |= (1<read_ptr[client_num] = netplay->server_ptr; netplay->read_frame_count[client_num] = netplay->server_frame_count; /* Announce it */ announce_play_spectate(netplay, nick, NETPLAY_CONNECTION_PLAYING, devices); #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Received mode change %u->%u\n", client_num, devices); print_state(netplay); #endif } else { netplay->connected_players &= ~(1<client_devices[client_num] = 0; for (device = 0; device < MAX_INPUT_DEVICES; device++) netplay->device_clients[device] &= ~(1<spectator\n", client_num); print_state(netplay); #endif } } break; #undef START #undef NEXT } case NETPLAY_CMD_MODE_REFUSED: { uint32_t reason; const char *dmsg = NULL; if (netplay->is_server) { RARCH_ERR("NETPLAY_CMD_MODE_REFUSED from client.\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(reason)) { RARCH_ERR("Received invalid payload size for NETPLAY_CMD_MODE_REFUSED.\n"); return netplay_cmd_nak(netplay, connection); } RECV(&reason, sizeof(reason)) return false; reason = ntohl(reason); switch (reason) { case NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED: dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED); break; case NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS: dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS); break; case NETPLAY_CMD_MODE_REFUSED_REASON_NOT_AVAILABLE: dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_NOT_AVAILABLE); break; default: dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY); } RARCH_LOG("%s\n", dmsg); runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } break; case NETPLAY_CMD_DISCONNECT: netplay_hangup(netplay, connection); return true; case NETPLAY_CMD_CRC: { uint32_t buffer[2]; size_t tmp_ptr = netplay->run_ptr; bool found = false; if (cmd_size != sizeof(buffer)) { RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(buffer, sizeof(buffer)) return false; buffer[0] = ntohl(buffer[0]); buffer[1] = ntohl(buffer[1]); /* Received a CRC for some frame. If we still have it, check if it * matched. This approach could be improved with some quick modular * arithmetic. */ do { if ( netplay->buffer[tmp_ptr].used && netplay->buffer[tmp_ptr].frame == buffer[0]) { found = true; break; } tmp_ptr = PREV_PTR(tmp_ptr); } while (tmp_ptr != netplay->run_ptr); /* Oh well, we got rid of it! */ if (!found) break; if (buffer[0] <= netplay->other_frame_count) { /* We've already replayed up to this frame, so we can check it * directly */ uint32_t local_crc = 0; if (netplay->state_size) local_crc = netplay_delta_frame_crc( netplay, &netplay->buffer[tmp_ptr]); /* Problem! */ if (buffer[1] != local_crc) netplay_cmd_request_savestate(netplay); } else { /* We'll have to check it when we catch up */ netplay->buffer[tmp_ptr].crc = buffer[1]; } break; } case NETPLAY_CMD_REQUEST_SAVESTATE: /* Delay until next frame so we don't send the savestate after the * input */ netplay->force_send_savestate = true; break; case NETPLAY_CMD_LOAD_SAVESTATE: case NETPLAY_CMD_RESET: { uint32_t frame; uint32_t isize; uint32_t rd, wn; uint32_t client; uint32_t load_frame_count; size_t load_ptr; struct compression_transcoder *ctrans = NULL; uint32_t client_num = (uint32_t) (connection - netplay->connections + 1); /* Make sure we're ready for it */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) { if (!netplay->is_replay) { netplay->is_replay = true; netplay->replay_ptr = netplay->run_ptr; netplay->replay_frame_count = netplay->run_frame_count; netplay_wait_and_init_serialization(netplay); netplay->is_replay = false; } else netplay_wait_and_init_serialization(netplay); } /* Only players may load states */ if (connection->mode != NETPLAY_CONNECTION_PLAYING && connection->mode != NETPLAY_CONNECTION_SLAVE) { RARCH_ERR("Netplay state load from a spectator.\n"); return netplay_cmd_nak(netplay, connection); } /* We only allow players to load state if we're in a simple * two-player situation */ if (netplay->is_server && netplay->connections_size > 1) { RARCH_ERR("Netplay state load from a client with other clients connected disallowed.\n"); return netplay_cmd_nak(netplay, connection); } /* There is a subtlty in whether the load comes before or after the * current frame: * * If it comes before the current frame, then we need to force a * rewind to that point. * * If it comes after the current frame, we need to jump ahead, then * (strangely) force a rewind to the frame we're already on, so it * gets loaded. This is just to avoid having reloading implemented in * too many places. */ /* Check the payload size */ if ((cmd == NETPLAY_CMD_LOAD_SAVESTATE && (cmd_size < 2*sizeof(uint32_t) || cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t))) || (cmd == NETPLAY_CMD_RESET && cmd_size != sizeof(frame))) { RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(&frame, sizeof(frame)) return false; frame = ntohl(frame); if (netplay->is_server) { load_ptr = netplay->read_ptr[client_num]; load_frame_count = netplay->read_frame_count[client_num]; } else { load_ptr = netplay->server_ptr; load_frame_count = netplay->server_frame_count; } if (frame != load_frame_count) { RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); return netplay_cmd_nak(netplay, connection); } if (!netplay_delta_frame_ready(netplay, &netplay->buffer[load_ptr], load_frame_count)) { /* Hopefully it will be after another round of input */ goto shrt; } /* Now we switch based on whether we're loading a state or resetting */ if (cmd == NETPLAY_CMD_LOAD_SAVESTATE) { RECV(&isize, sizeof(isize)) return false; isize = ntohl(isize); if (isize != netplay->state_size) { RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)) return false; /* And decompress it */ switch (connection->compression_supported) { case NETPLAY_COMPRESSION_ZLIB: ctrans = &netplay->compress_zlib; break; default: ctrans = &netplay->compress_nil; } ctrans->decompression_backend->set_in(ctrans->decompression_stream, netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); ctrans->decompression_backend->set_out(ctrans->decompression_stream, (uint8_t*)netplay->buffer[load_ptr].state, (unsigned)netplay->state_size); ctrans->decompression_backend->trans(ctrans->decompression_stream, true, &rd, &wn, NULL); /* Force a rewind to the relevant frame */ netplay->force_rewind = true; } else { /* Resetting */ netplay->force_reset = true; } /* Skip ahead if it's past where we are */ if (load_frame_count > netplay->run_frame_count || cmd == NETPLAY_CMD_RESET) { /* This is squirrely: We need to assure that when we advance the * frame in post_frame, THEN we're referring to the frame to * load into. If we refer directly to read_ptr, then we'll end * up never reading the input for read_frame_count itself, which * will make the other side unhappy. */ netplay->run_ptr = PREV_PTR(load_ptr); netplay->run_frame_count = load_frame_count - 1; if (frame > netplay->self_frame_count) { netplay->self_ptr = netplay->run_ptr; netplay->self_frame_count = netplay->run_frame_count; } } /* Don't expect earlier data from other clients */ for (client = 0; client < MAX_CLIENTS; client++) { if (!(netplay->connected_players & (1< netplay->read_frame_count[client]) { netplay->read_ptr[client] = load_ptr; netplay->read_frame_count[client] = load_frame_count; } } /* Make sure our states are correct */ netplay->savestate_request_outstanding = false; netplay->other_ptr = load_ptr; netplay->other_frame_count = load_frame_count; #ifdef DEBUG_NETPLAY_STEPS RARCH_LOG("[netplay] Loading state at %u\n", load_frame_count); print_state(netplay); #endif break; } case NETPLAY_CMD_PAUSE: { char msg[512], nick[NETPLAY_NICK_LEN]; /* Read in the paused nick */ if (cmd_size != sizeof(nick)) { RARCH_ERR("NETPLAY_CMD_PAUSE received invalid payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(nick, sizeof(nick)) return false; nick[sizeof(nick)-1] = '\0'; /* We outright ignore pausing from spectators and slaves */ if (connection->mode != NETPLAY_CONNECTION_PLAYING) break; connection->paused = true; netplay->remote_paused = true; if (netplay->is_server) { /* Inform peers */ snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), connection->nick); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE, connection->nick, NETPLAY_NICK_LEN); /* We may not reach post_frame soon, so flush the pause message * immediately. */ netplay_send_flush_all(netplay, connection); } else snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), nick); RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); break; } case NETPLAY_CMD_RESUME: remote_unpaused(netplay, connection); break; case NETPLAY_CMD_STALL: { uint32_t frames; if (netplay->is_server) { /* Only servers can request a stall! */ RARCH_ERR("Netplay client requested a stall?\n"); return netplay_cmd_nak(netplay, connection); } if (cmd_size != sizeof(frames)) { RARCH_ERR("NETPLAY_CMD_STALL with incorrect payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(&frames, sizeof(frames)) return false; frames = ntohl(frames); if (frames > NETPLAY_MAX_REQ_STALL_TIME) frames = NETPLAY_MAX_REQ_STALL_TIME; /* We can only stall for one reason at a time */ if (!netplay->stall) { connection->stall = netplay->stall = NETPLAY_STALL_SERVER_REQUESTED; netplay->stall_time = 0; connection->stall_frame = frames; } break; } #if NETPLAY_PROTOCOL_VERSION >= 6 case NETPLAY_CMD_PLAYER_CHAT: { char payload[MAX_CHAT_SIZE]; /* We do not send the sentinel/null character on chat messages and we do not allow empty messages. */ if (!cmd_size || cmd_size >= sizeof(payload)) { RARCH_ERR("NETPLAY_CMD_PLAYER_CHAT with incorrect payload size.\n"); return netplay_cmd_nak(netplay, connection); } RECV(payload, cmd_size) return false; payload[recvd] = '\0'; if (!handle_chat(netplay, connection, payload)) { RARCH_ERR("NETPLAY_CMD_PLAYER_CHAT with invalid message or from an invalid peer.\n"); return netplay_cmd_nak(netplay, connection); } } break; #endif default: { unsigned char buf[1024]; while (cmd_size) { RECV(buf, (cmd_size > sizeof(buf)) ? sizeof(buf) : cmd_size) return false; cmd_size -= recvd; } RARCH_ERR("%s\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); } break; } netplay_recv_flush(&connection->recv_packet_buffer); netplay->timeout_cnt = 0; if (had_input) *had_input = true; return true; shrt: /* No more data, reset and try again */ netplay_recv_reset(&connection->recv_packet_buffer); return true; #undef RECV } /** * netplay_poll_net_input * * Poll input from the network */ int netplay_poll_net_input(netplay_t *netplay, bool block) { bool had_input = false; int max_fd = 0; size_t i; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active && connection->fd >= max_fd) max_fd = connection->fd + 1; } if (max_fd == 0) return 0; netplay->timeout_cnt = 0; do { had_input = false; netplay->timeout_cnt++; /* Read input from each connection */ for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active && !netplay_get_cmd(netplay, connection, &had_input)) netplay_hangup(netplay, connection); } if (block) { netplay_update_unread_ptr(netplay); /* If we were blocked for input, pass if we have this frame's input */ if (netplay->unread_frame_count > netplay->run_frame_count) break; /* If we're supposed to block but we didn't have enough input, wait for it */ if (!had_input) { fd_set fds; struct timeval tv = {0}; tv.tv_usec = RETRY_MS * 1000; FD_ZERO(&fds); for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active) FD_SET(connection->fd, &fds); } if (socket_select(max_fd, &fds, NULL, NULL, &tv) < 0) return -1; RARCH_LOG( "Network is stalling at frame %u, count %u of %d ...\n", netplay->run_frame_count, netplay->timeout_cnt, MAX_RETRIES); if ( netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused) return -1; } } } while (had_input || block); return 0; } /** * netplay_handle_slaves * * Handle any slave connections */ void netplay_handle_slaves(netplay_t *netplay) { struct delta_frame *oframe, *frame = &netplay->buffer[netplay->self_ptr]; size_t i; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active && connection->mode == NETPLAY_CONNECTION_SLAVE) { uint32_t devices, device; uint32_t client_num = (uint32_t)(i + 1); /* This is a slave connection. First, should we do anything at all? If * we've already "read" this data, then we can just ignore it */ if (netplay->read_frame_count[client_num] > netplay->self_frame_count) continue; /* Alright, we have to send something. Do we need to generate it first? */ if (!frame->have_real[client_num]) { devices = netplay->client_devices[client_num]; /* Copy the previous frame's data */ oframe = &netplay->buffer[PREV_PTR(netplay->self_ptr)]; for (device = 0; device < MAX_INPUT_DEVICES; device++) { netplay_input_state_t istate_out, istate_in; if (!(devices & (1<real_input[device]; while (istate_in && istate_in->client_num != client_num) istate_in = istate_in->next; if (!istate_in) { /* Start with blank input */ netplay_input_state_for(&frame->real_input[device], client_num, netplay_expected_input_size(netplay, 1 << device), true, false); } else { /* Copy the previous input */ istate_out = netplay_input_state_for(&frame->real_input[device], client_num, istate_in->size, true, false); memcpy(istate_out->data, istate_in->data, istate_in->size * sizeof(uint32_t)); } } frame->have_real[client_num] = true; } /* Send it along */ send_input_frame(netplay, frame, NULL, NULL, client_num, false); /* And mark it as "read" */ netplay->read_ptr[client_num] = NEXT_PTR(netplay->self_ptr); netplay->read_frame_count[client_num] = netplay->self_frame_count + 1; } } } /** * netplay_announce_nat_traversal * * Announce successful NAT traversal. */ void netplay_announce_nat_traversal(netplay_t *netplay) { #ifndef HAVE_SOCKET_LEGACY char msg[512], host[256], port[6]; const char *dmsg = NULL; if (netplay->nat_traversal_state.have_inet4) { if (!getnameinfo( (const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, sizeof(netplay->nat_traversal_state.ext_inet4_addr), host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV)) { snprintf(msg, sizeof(msg), "%s: %s:%s", msg_hash_to_str(MSG_PUBLIC_ADDRESS), host, port); dmsg = msg; } } #ifdef HAVE_INET6 else if (netplay->nat_traversal_state.have_inet6) { if (!getnameinfo( (const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, sizeof(netplay->nat_traversal_state.ext_inet6_addr), host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV)) { snprintf(msg, sizeof(msg), "%s: %s|%s", msg_hash_to_str(MSG_PUBLIC_ADDRESS), host, port); dmsg = msg; } } #endif else dmsg = msg_hash_to_str(MSG_UPNP_FAILED); if (dmsg) { RARCH_LOG("%s\n", dmsg); runloop_msg_queue_push(dmsg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } #endif } /** * netplay_init_nat_traversal * * Initialize the NAT traversal library and try to open a port */ void netplay_init_nat_traversal(netplay_t *netplay) { memset(&netplay->nat_traversal_state, 0, sizeof(netplay->nat_traversal_state)); netplay->nat_traversal_task_oustanding = true; task_push_netplay_nat_traversal(&netplay->nat_traversal_state, netplay->tcp_port); } static int init_tcp_connection(const struct addrinfo *res, bool is_server, bool use_mitm) { #ifndef HAVE_SOCKET_LEGACY char msg[512]; char host[256], port[6]; #endif const char *dmsg = NULL; int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd < 0) return -1; #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) { int on = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*) &on, sizeof(on)) < 0) RARCH_WARN("Could not set netplay TCP socket to nodelay. Expect jitter.\n"); } #endif #if defined(F_SETFD) && defined(FD_CLOEXEC) /* Don't let any inherited processes keep open our port */ if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) RARCH_WARN("Cannot set Netplay port to close-on-exec. It may fail to reopen if the client disconnects.\n"); #endif if (!is_server) { if (!socket_connect(fd, (void*)res, false)) return fd; #ifndef HAVE_SOCKET_LEGACY if (!getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV)) { snprintf(msg, sizeof(msg), "Failed to connect to host %s on port %s.", host, port); dmsg = msg; } #else dmsg = "Failed to connect to host."; #endif } else { #ifdef __NETPLAY_MITM_NEW if (use_mitm) { if (!socket_connect(fd, (void*)res, false)) { net_driver_state_t *net_st = &networking_driver_st; /* Relay server should provide us with our session ID. */ if (socket_receive_all_blocking(fd, net_st->mitm_id, sizeof(net_st->mitm_id))) { /* Initialize data for handling tunneled client connections. */ int *fds; uint8_t *ids; retro_time_t *timeouts; fds = malloc( NETPLAY_MITM_MAX_PENDING * sizeof(*fds)); ids = malloc( NETPLAY_MITM_MAX_PENDING * NETPLAY_MITM_ID_SIZE * sizeof(*ids)); timeouts = malloc( NETPLAY_MITM_MAX_PENDING * sizeof(*timeouts)); if (fds && ids && timeouts) { size_t i; memset(fds, -1, NETPLAY_MITM_MAX_PENDING * sizeof(*fds)); for (i = 0; i < NETPLAY_MITM_MAX_PENDING; i++, ids += NETPLAY_MITM_ID_SIZE) net_st->mitm_pending.ids[i] = ids; net_st->mitm_pending.fds = fds; net_st->mitm_pending.timeouts = timeouts; net_st->mitm_pending.current = 0; net_st->mitm_pending.next = -1; return fd; } free(fds); free(ids); free(timeouts); } dmsg = "Failed to create a tunnel session."; } else { #ifndef HAVE_SOCKET_LEGACY if (!getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV)) { snprintf(msg, sizeof(msg), "Failed to connect to relay server %s on port %s.", host, port); dmsg = msg; } #else dmsg = "Failed to connect to relay server."; #endif } } else { #endif #if defined(HAVE_INET6) && defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) /* Make sure we accept connections on both IPv6 and IPv4 */ if (res->ai_family == AF_INET6) { int on = 0; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(on)) < 0) RARCH_WARN("Failed to listen on both IPv6 and IPv4\n"); } #endif if (socket_bind(fd, (void*)res)) { if (!listen(fd, 1024)) return fd; } else { #ifndef HAVE_SOCKET_LEGACY if (!getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0, port, sizeof(port), NI_NUMERICSERV)) { snprintf(msg, sizeof(msg), "Failed to bind port %s.", port); dmsg = msg; } #else dmsg = "Failed to bind port."; #endif } #ifdef __NETPLAY_MITM_NEW } #endif } socket_close(fd); if (dmsg) RARCH_ERR("%s\n", dmsg); return -1; } static bool init_tcp_socket(netplay_t *netplay, const char *server, const char *mitm, uint16_t port) { struct addrinfo *res; const struct addrinfo *tmp_info; char port_buf[6]; struct addrinfo hints = {0}; bool use_mitm = !server && mitm; int fd = -1; if (!server) { if (!use_mitm) { hints.ai_flags = AI_PASSIVE; #ifdef HAVE_INET6 /* Default to hosting on IPv6 and IPv4 */ hints.ai_family = AF_INET6; #else hints.ai_family = AF_INET; #endif } else { /* IPv4 only for relay servers. */ hints.ai_family = AF_INET; } } hints.ai_socktype = SOCK_STREAM; snprintf(port_buf, sizeof(port_buf), "%hu", port); if (getaddrinfo_retro(use_mitm ? mitm : server, port_buf, &hints, &res)) { if (!server && !use_mitm) { #ifdef HAVE_INET6 try_ipv4: /* Didn't work with IPv6, try IPv4 */ hints.ai_family = AF_INET; if (getaddrinfo_retro(server, port_buf, &hints, &res)) #endif { RARCH_ERR("Failed to set a hosting address.\n"); return false; } } else { char msg[512]; snprintf(msg, sizeof(msg), "Failed to resolve host: %s\n", use_mitm ? mitm : server); RARCH_ERR(msg); return false; } } if (!res) return false; /* If we're serving on IPv6, make sure we accept all connections, including * IPv4 */ #ifdef HAVE_INET6 if (!server && !use_mitm && res->ai_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) res->ai_addr; #if defined(_MSC_VER) && _MSC_VER <= 1200 IN6ADDR_SETANY(sin6); #else sin6->sin6_addr = in6addr_any; #endif } #endif /* If "localhost" is used, it is important to check every possible * address for IPv4/IPv6. */ tmp_info = res; do { fd = init_tcp_connection(tmp_info, !server, use_mitm); if (fd >= 0) break; } while ((tmp_info = tmp_info->ai_next)); freeaddrinfo_retro(res); res = NULL; if (fd < 0) { #ifdef HAVE_INET6 if (!server && !use_mitm && hints.ai_family == AF_INET6) goto try_ipv4; #endif RARCH_ERR("Failed to set up netplay sockets.\n"); return false; } if (server) { netplay->connections[0].active = true; netplay->connections[0].fd = fd; memset(&netplay->connections[0].addr, 0, sizeof(netplay->connections[0].addr)); } else netplay->listen_fd = fd; return true; } static bool init_socket(netplay_t *netplay, void *direct_host, const char *server, uint16_t port) { if (!network_init()) return false; if (!init_tcp_socket(netplay, server, NULL, port)) return false; if (netplay->is_server && netplay->nat_traversal) netplay_init_nat_traversal(netplay); return true; } static bool netplay_init_socket_buffers(netplay_t *netplay) { /* Make our packet buffer big enough for a save state and stall-frames-many * frames of input data, plus the headers for each of them */ size_t i; size_t packet_buffer_size = netplay->zbuffer_size + NETPLAY_MAX_STALL_FRAMES * 16; netplay->packet_buffer_size = packet_buffer_size; for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active) { if (connection->send_packet_buffer.data) { if (!netplay_resize_socket_buffer(&connection->send_packet_buffer, packet_buffer_size) || !netplay_resize_socket_buffer(&connection->recv_packet_buffer, packet_buffer_size)) return false; } else { if (!netplay_init_socket_buffer(&connection->send_packet_buffer, packet_buffer_size) || !netplay_init_socket_buffer(&connection->recv_packet_buffer, packet_buffer_size)) return false; } } } return true; } static bool netplay_init_serialization(netplay_t *netplay) { unsigned i; retro_ctx_size_info_t info; if (netplay->state_size) return true; core_serialize_size(&info); if (!info.size) return false; netplay->state_size = info.size; for (i = 0; i < netplay->buffer_size; i++) { netplay->buffer[i].state = calloc(netplay->state_size, 1); if (!netplay->buffer[i].state) { netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES; return false; } } netplay->zbuffer_size = netplay->state_size * 2; netplay->zbuffer = (uint8_t *) calloc(netplay->zbuffer_size, 1); if (!netplay->zbuffer) { netplay->quirks |= NETPLAY_QUIRK_NO_TRANSMISSION; netplay->zbuffer_size = 0; return false; } return true; } /** * netplay_try_init_serialization * * Try to initialize serialization. For quirky cores. * * Returns true if serialization is now ready, false otherwise. */ bool netplay_try_init_serialization(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; if (netplay->state_size) return true; if (!netplay_init_serialization(netplay)) return false; /* Check if we can actually save */ serial_info.data_const = NULL; serial_info.data = netplay->buffer[netplay->run_ptr].state; serial_info.size = netplay->state_size; if (!core_serialize(&serial_info)) return false; /* Once initialized, we no longer exhibit this quirk */ netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION); return netplay_init_socket_buffers(netplay); } /** * netplay_wait_and_init_serialization * * Try very hard to initialize serialization, simulating multiple frames if * necessary. For quirky cores. * * Returns true if serialization is now ready, false otherwise. */ bool netplay_wait_and_init_serialization(netplay_t *netplay) { int frame; if (netplay->state_size) return true; /* Wait a maximum of 60 frames */ for (frame = 0; frame < 60; frame++) { if (netplay_try_init_serialization(netplay)) return true; #if defined(HAVE_THREADS) autosave_lock(); #endif core_run(); #if defined(HAVE_THREADS) autosave_unlock(); #endif } return false; } static bool netplay_init_buffers(netplay_t *netplay) { struct delta_frame *delta_frames = NULL; /* Enough to get ahead or behind by MAX_STALL_FRAMES frames, plus one for * other remote clients, plus one to send the stall message */ netplay->buffer_size = NETPLAY_MAX_STALL_FRAMES + 2; /* If we're the server, we need enough to get ahead AND behind by * MAX_STALL_FRAMES frame */ if (netplay->is_server) netplay->buffer_size *= 2; delta_frames = (struct delta_frame*)calloc(netplay->buffer_size, sizeof(*delta_frames)); if (!delta_frames) return false; netplay->buffer = delta_frames; if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_INITIALIZATION))) netplay_init_serialization(netplay); return netplay_init_socket_buffers(netplay); } /** * netplay_new: * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. * @port : Port of server. * @stateless_mode : Shall we use stateless mode? * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. * @nat_traversal : If true, attempt NAT traversal. * @nick : Nickname of user. * @quirks : Netplay quirks required for this session. * * Creates a new netplay handle. A NULL server means we're * hosting. * * Returns: new netplay data. */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, bool stateless_mode, int check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks) { netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay)); if (!netplay) return NULL; netplay->listen_fd = -1; netplay->tcp_port = port; netplay->cbs = *cb; netplay->is_server = (direct_host == NULL && server == NULL); netplay->is_connected = false; netplay->nat_traversal = netplay->is_server ? nat_traversal : false; netplay->stateless_mode = stateless_mode; netplay->check_frames = check_frames; netplay->crc_validity_checked = false; netplay->crcs_valid = true; netplay->quirks = quirks; netplay->self_mode = netplay->is_server ? NETPLAY_CONNECTION_SPECTATING : NETPLAY_CONNECTION_NONE; if (netplay->is_server) { netplay->connections = NULL; netplay->connections_size = 0; } else { netplay->connections = &netplay->one_connection; netplay->connections_size = 1; netplay->connections[0].fd = -1; } strlcpy(netplay->nick, !string_is_empty(nick) ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); if (!init_socket(netplay, direct_host, server, port)) { free(netplay); return NULL; } if (!netplay_init_buffers(netplay)) { free(netplay); return NULL; } if (netplay->is_server) { /* Clients get device info from the server */ unsigned i; for (i = 0; i < MAX_INPUT_DEVICES; i++) { uint32_t dtype = input_config_get_device(i); netplay->config_devices[i] = dtype; if ((dtype&RETRO_DEVICE_MASK) == RETRO_DEVICE_KEYBOARD) { netplay->have_updown_device = true; netplay_key_hton_init(); } if (dtype != RETRO_DEVICE_NONE && !netplay_expected_input_size(netplay, 1<connections[0]); netplay->connections[0].mode = NETPLAY_CONNECTION_INIT; netplay->self_mode = NETPLAY_CONNECTION_INIT; } /* FIXME: Not really the right place to do this, * socket initialization needs to be fixed in general. */ if (netplay->is_server) { if (!socket_nonblock(netplay->listen_fd)) goto error; } else { if (!socket_nonblock(netplay->connections[0].fd)) goto error; } return netplay; error: if (netplay->listen_fd >= 0) socket_close(netplay->listen_fd); if (netplay->connections && netplay->connections[0].fd >= 0) socket_close(netplay->connections[0].fd); free(netplay); return NULL; } /** * netplay_free * @netplay : pointer to netplay object * * Frees netplay data/ */ void netplay_free(netplay_t *netplay) { size_t i; if (netplay->listen_fd >= 0) socket_close(netplay->listen_fd); for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active) { socket_close(connection->fd); netplay_deinit_socket_buffer(&connection->send_packet_buffer); netplay_deinit_socket_buffer(&connection->recv_packet_buffer); } } if (netplay->connections && netplay->connections != &netplay->one_connection) free(netplay->connections); if (netplay->nat_traversal) natt_free(&netplay->nat_traversal_state); if (netplay->buffer) { for (i = 0; i < netplay->buffer_size; i++) netplay_delta_frame_free(&netplay->buffer[i]); free(netplay->buffer); } if (netplay->zbuffer) free(netplay->zbuffer); if (netplay->compress_nil.compression_stream) netplay->compress_nil.compression_backend->stream_free( netplay->compress_nil.compression_stream); if (netplay->compress_nil.decompression_stream) netplay->compress_nil.decompression_backend->stream_free( netplay->compress_nil.decompression_stream); if (netplay->compress_zlib.compression_stream) netplay->compress_zlib.compression_backend->stream_free(netplay->compress_zlib.compression_stream); if (netplay->compress_zlib.decompression_stream) netplay->compress_zlib.decompression_backend->stream_free(netplay->compress_zlib.decompression_stream); if (netplay->addr) freeaddrinfo_retro(netplay->addr); 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); } void netplay_post_frame(netplay_t *netplay) { size_t i; netplay_update_unread_ptr(netplay); netplay_sync_post_frame(netplay, false); for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active && !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) netplay_hangup(netplay, connection); } } bool init_netplay_deferred(const char* server, unsigned port) { net_driver_state_t *net_st = &networking_driver_st; if (!string_is_empty(server) && port != 0) { strlcpy(net_st->server_address_deferred, server, sizeof(net_st->server_address_deferred)); net_st->server_port_deferred = port; net_st->netplay_client_deferred = true; } else net_st->netplay_client_deferred = false; return net_st->netplay_client_deferred; } /** * input_poll_net * * Poll the network if necessary. */ void input_poll_net(void) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay_should_skip(netplay) && netplay && netplay->can_poll) { input_driver_state_t *input_st = input_state_get_ptr(); netplay->can_poll = false; netplay_poll( input_st->block_libretro_input, config_get_ptr(), netplay); } } /* Netplay polling callbacks */ void video_frame_net(const void *data, unsigned width, unsigned height, size_t pitch) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay_should_skip(netplay)) netplay->cbs.frame_cb(data, width, height, pitch); } void audio_sample_net(int16_t left, int16_t right) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay_should_skip(netplay) && !netplay->stall) netplay->cbs.sample_cb(left, right); } size_t audio_sample_batch_net(const int16_t *data, size_t frames) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (!netplay_should_skip(netplay) && !netplay->stall) return netplay->cbs.sample_batch_cb(data, frames); return frames; } static void netplay_announce_cb(retro_task_t *task, void *task_data, void *user_data, const char *error) { net_driver_state_t *net_st = &networking_driver_st; if (task_data) { unsigned i, ip_len, port_len; http_transfer_data_t *data = (http_transfer_data_t*)task_data; struct netplay_room *host_room = &net_st->host_room; struct string_list *lines = NULL; char *mitm_ip = NULL; char *mitm_port = NULL; char *buf = NULL; char *host_string = NULL; if (data->len == 0) return; buf = (char*)calloc(1, data->len + 1); memcpy(buf, data->data, data->len); lines = string_split(buf, "\n"); if (lines->size == 0) { string_list_free(lines); free(buf); return; } memset(host_room, 0, sizeof(*host_room)); for (i = 0; i < lines->size; i++) { const char *line = lines->elems[i].data; if (!string_is_empty(line)) { struct string_list *kv = string_split(line, "="); const char *key = NULL; const char *val = NULL; if (!kv) continue; if (kv->size != 2) { string_list_free(kv); continue; } key = kv->elems[0].data; val = kv->elems[1].data; if (string_is_equal(key, "id")) sscanf(val, "%i", &host_room->id); if (string_is_equal(key, "username")) strlcpy(host_room->nickname, val, sizeof(host_room->nickname)); if (string_is_equal(key, "ip")) strlcpy(host_room->address, val, sizeof(host_room->address)); if (string_is_equal(key, "mitm_ip")) { mitm_ip = strdup(val); strlcpy(host_room->mitm_address, val, sizeof(host_room->mitm_address)); } if (string_is_equal(key, "port")) sscanf(val, "%i", &host_room->port); if (string_is_equal(key, "mitm_port")) { mitm_port = strdup(val); sscanf(mitm_port, "%i", &host_room->mitm_port); } if (string_is_equal(key, "core_name")) strlcpy(host_room->corename, val, sizeof(host_room->corename)); if (string_is_equal(key, "frontend")) strlcpy(host_room->frontend, val, sizeof(host_room->frontend)); if (string_is_equal(key, "core_version")) strlcpy(host_room->coreversion, val, sizeof(host_room->coreversion)); if (string_is_equal(key, "game_name")) strlcpy(host_room->gamename, val, sizeof(host_room->gamename)); if (string_is_equal(key, "game_crc")) sscanf(val, "%08d", &host_room->gamecrc); if (string_is_equal(key, "host_method")) sscanf(val, "%i", &host_room->host_method); if (string_is_equal(key, "has_password")) { if ( string_is_equal_noncase(val, "true") || string_is_equal(val, "1")) host_room->has_password = true; else host_room->has_password = false; } if (string_is_equal(key, "has_spectate_password")) { if ( string_is_equal_noncase(val, "true") || string_is_equal(val, "1")) host_room->has_spectate_password = true; else host_room->has_spectate_password = false; } if (string_is_equal(key, "fixed")) { if ( string_is_equal_noncase(val, "true") || string_is_equal(val, "1")) host_room->fixed = true; else host_room->fixed = false; } if (string_is_equal(key, "retroarch_version")) strlcpy(host_room->retroarch_version, val, sizeof(host_room->retroarch_version)); if (string_is_equal(key, "country")) strlcpy(host_room->country, val, sizeof(host_room->country)); string_list_free(kv); } } if (mitm_ip && mitm_port) { ip_len = (unsigned)strlen(mitm_ip); port_len = (unsigned)strlen(mitm_port); /* Enable Netplay client mode */ if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) { command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); net_st->is_mitm = true; host_room->host_method = NETPLAY_HOST_METHOD_MITM; } netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL); host_string = (char*)calloc(1, ip_len + port_len + 2); memcpy(host_string, mitm_ip, ip_len); memcpy(host_string + ip_len, "|", 1); memcpy(host_string + ip_len + 1, mitm_port, port_len); /* Enable Netplay */ command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, (void*)host_string); command_event(CMD_EVENT_NETPLAY_INIT, (void*)host_string); free(host_string); } #ifdef HAVE_DISCORD if (discord_state_get_ptr()->inited) { discord_userdata_t userdata; userdata.status = DISCORD_PRESENCE_NETPLAY_HOSTING; command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); } #endif string_list_free(lines); free(buf); if (mitm_ip) free(mitm_ip); if (mitm_port) free(mitm_port); } } static void netplay_announce(void) { char buf[4600]; char frontend_architecture[PATH_MAX_LENGTH]; char frontend_architecture_tmp[32]; const frontend_ctx_driver_t *frontend_drv = NULL; char url[2048] = "http://lobby.libretro.com/add/"; char *username = NULL; char *corename = NULL; char *gamename = NULL; char *subsystemname = NULL; char *coreversion = NULL; char *frontend_ident = NULL; settings_t *settings = config_get_ptr(); runloop_state_t *runloop_st = runloop_state_get_ptr(); struct retro_system_info *system = &runloop_st->system.info; uint32_t content_crc = content_get_crc(); struct string_list *subsystem = path_get_subsystem_list(); frontend_architecture[0] = '\0'; buf[0] = '\0'; if (subsystem) { unsigned i; for (i = 0; i < subsystem->size; i++) { strlcat(buf, path_basename(subsystem->elems[i].data), sizeof(buf)); if (i < subsystem->size - 1) strlcat(buf, "|", sizeof(buf)); } net_http_urlencode(&gamename, buf); net_http_urlencode(&subsystemname, path_get(RARCH_PATH_SUBSYSTEM)); content_crc = 0; } else { const char *base = path_basename(path_get(RARCH_PATH_BASENAME)); net_http_urlencode(&gamename, !string_is_empty(base) ? base : "N/A"); /* TODO/FIXME - subsystem should be implemented later? */ net_http_urlencode(&subsystemname, "N/A"); } frontend_drv = (const frontend_ctx_driver_t*)frontend_driver_get_cpu_architecture_str( frontend_architecture_tmp, sizeof(frontend_architecture_tmp)); snprintf(frontend_architecture, sizeof(frontend_architecture), "%s %s", frontend_drv->ident, frontend_architecture_tmp); #ifdef HAVE_DISCORD if (discord_is_ready()) net_http_urlencode(&username, discord_get_own_username()); else #endif net_http_urlencode(&username, settings->paths.username); net_http_urlencode(&corename, system->library_name); net_http_urlencode(&coreversion, system->library_version); net_http_urlencode(&frontend_ident, frontend_architecture); buf[0] = '\0'; snprintf(buf, sizeof(buf), "username=%s&core_name=%s&core_version=%s&" "game_name=%s&game_crc=%08lX&port=%d&mitm_server=%s" "&has_password=%d&has_spectate_password=%d&force_mitm=%d" "&retroarch_version=%s&frontend=%s&subsystem_name=%s", username, corename, coreversion, gamename, (unsigned long)content_crc, settings->uints.netplay_port, settings->arrays.netplay_mitm_server, *settings->paths.netplay_password ? 1 : 0, *settings->paths.netplay_spectate_password ? 1 : 0, settings->bools.netplay_use_mitm_server, PACKAGE_VERSION, frontend_architecture, subsystemname); task_push_http_post_transfer(url, buf, true, NULL, netplay_announce_cb, NULL); if (username) free(username); if (corename) free(corename); if (gamename) free(gamename); if (coreversion) free(coreversion); if (frontend_ident) free(frontend_ident); } int16_t input_state_net(unsigned port, unsigned device, unsigned idx, unsigned id) { net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; if (netplay) { if (netplay_is_alive(netplay)) return netplay_input_state(netplay, port, device, idx, id); return netplay->cbs.state_cb(port, device, idx, id); } return 0; } /* ^^^ Netplay polling callbacks */ /** * netplay_disconnect * @netplay : pointer to netplay object * * Disconnect netplay. * * Returns: true (1) if successful. At present, cannot fail. **/ static void netplay_disconnect(netplay_t *netplay) { size_t i; for (i = 0; i < netplay->connections_size; i++) netplay_hangup(netplay, &netplay->connections[i]); deinit_netplay(); #ifdef HAVE_DISCORD if (discord_state_get_ptr()->inited) { discord_userdata_t userdata; userdata.status = DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED; command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); } #endif } /** * netplay_pre_frame: * @netplay : pointer to netplay object * * Pre-frame for Netplay. * Call this before running retro_run(). * * Returns: true (1) if the frontend is cleared to emulate the frame, false (0) * if we're stalled or paused **/ static bool netplay_pre_frame( bool netplay_public_announce, bool netplay_use_mitm_server, netplay_t *netplay) { bool sync_stalled = false; net_driver_state_t *net_st = &networking_driver_st; retro_assert(netplay); if (netplay_public_announce) { net_st->reannounce++; if ( (netplay->is_server || net_st->is_mitm) && (net_st->reannounce % 300 == 0)) netplay_announce(); } /* Make sure that if announcement is turned on mid-game, it gets announced */ else net_st->reannounce = -1; /* FIXME: This is an ugly way to learn we're not paused anymore */ if (netplay->local_paused) if (netplay->local_paused != false) netplay_frontend_paused(netplay, false); /* Are we ready now? */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) netplay_try_init_serialization(netplay); if (netplay->is_server && !netplay_use_mitm_server) { #ifdef HAVE_NETPLAYDISCOVERY /* Advertise our server */ netplay_lan_ad_server(netplay); #endif /* NAT traversal if applicable */ if (netplay->nat_traversal && !netplay->nat_traversal_task_oustanding && netplay->nat_traversal_state.request_outstanding && !netplay->nat_traversal_state.have_inet4) { struct timeval tmptv = {0}; fd_set fds = netplay->nat_traversal_state.fds; if (socket_select(netplay->nat_traversal_state.nfds, &fds, NULL, NULL, &tmptv) > 0) natt_read(&netplay->nat_traversal_state); #ifndef HAVE_SOCKET_LEGACY if (!netplay->nat_traversal_state.request_outstanding || netplay->nat_traversal_state.have_inet4) netplay_announce_nat_traversal(netplay); #endif } } sync_stalled = !netplay_sync_pre_frame(netplay); /* If we're disconnected, deinitialize */ if (!netplay->is_server && !netplay->connections[0].active) { netplay_disconnect(netplay); return true; } if (sync_stalled || ((!netplay->is_server || (netplay->connected_players>1)) && (netplay->stall || netplay->remote_paused))) { /* We may have received data even if we're stalled, so run post-frame * sync */ netplay_sync_post_frame(netplay, true); return false; } return true; } void deinit_netplay(void) { net_driver_state_t *net_st = &networking_driver_st; if (net_st->data) { netplay_free(net_st->data); net_st->data = NULL; net_st->netplay_enabled = false; net_st->netplay_is_client = false; net_st->is_mitm = false; } if (net_st->mitm_pending.fds) { free(net_st->mitm_pending.fds); net_st->mitm_pending.fds = NULL; } if (net_st->mitm_pending.ids[0]) { free(net_st->mitm_pending.ids[0]); memset(net_st->mitm_pending.ids, 0, sizeof(net_st->mitm_pending.ids)); } if (net_st->mitm_pending.timeouts) { free(net_st->mitm_pending.timeouts); net_st->mitm_pending.timeouts = NULL; } core_unset_netplay_callbacks(); } bool init_netplay(void *direct_host, const char *server, unsigned port) { struct retro_callbacks cbs = {0}; uint64_t serialization_quirks = 0; uint64_t quirks = 0; settings_t *settings = config_get_ptr(); net_driver_state_t *net_st = &networking_driver_st; bool _netplay_is_client = net_st->netplay_is_client; bool _netplay_enabled = net_st->netplay_enabled; if (!_netplay_enabled) return false; core_set_default_callbacks(&cbs); if (!core_set_netplay_callbacks()) return false; /* Map the core's quirks to our quirks */ serialization_quirks = core_serialization_quirks(); /* Quirks we don't support! Just disable everything. */ if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD)) quirks |= NETPLAY_QUIRK_NO_SAVESTATES; if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_SAVESTATES) quirks |= NETPLAY_QUIRK_NO_SAVESTATES; if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_TRANSMISSION) quirks |= NETPLAY_QUIRK_NO_TRANSMISSION; if (serialization_quirks & NETPLAY_QUIRK_MAP_INITIALIZATION) quirks |= NETPLAY_QUIRK_INITIALIZATION; if (serialization_quirks & NETPLAY_QUIRK_MAP_ENDIAN_DEPENDENT) quirks |= NETPLAY_QUIRK_ENDIAN_DEPENDENT; if (serialization_quirks & NETPLAY_QUIRK_MAP_PLATFORM_DEPENDENT) quirks |= NETPLAY_QUIRK_PLATFORM_DEPENDENT; if (!_netplay_is_client) { runloop_msg_queue_push( msg_hash_to_str(MSG_WAITING_FOR_CLIENT), 0, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); if (settings->bools.netplay_public_announce) netplay_announce(); } net_st->data = (netplay_t*)netplay_new( _netplay_is_client ? direct_host : NULL, _netplay_is_client ? (!net_st->netplay_client_deferred ? server : net_st->server_address_deferred) : NULL, _netplay_is_client ? (!net_st->netplay_client_deferred ? port : net_st->server_port_deferred) : (port != 0 ? port : RARCH_DEFAULT_PORT), settings->bools.netplay_stateless_mode, settings->ints.netplay_check_frames, &cbs, settings->bools.netplay_nat_traversal && !settings->bools.netplay_use_mitm_server, #ifdef HAVE_DISCORD discord_get_own_username() ? discord_get_own_username() : #endif settings->paths.username, quirks); if (net_st->data) { if ( net_st->data->is_server && !settings->bools.netplay_start_as_spectator) netplay_toggle_play_spectate(net_st->data); return true; } RARCH_WARN("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED)); runloop_msg_queue_push( msg_hash_to_str(MSG_NETPLAY_FAILED), 0, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); return false; } /** * netplay_driver_ctl * * Frontend access to Netplay functionality */ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) { settings_t *settings = config_get_ptr(); net_driver_state_t *net_st = &networking_driver_st; netplay_t *netplay = net_st->data; bool ret = true; if (net_st->in_netplay) return true; net_st->in_netplay = true; if (!netplay) { switch (state) { case RARCH_NETPLAY_CTL_ENABLE_SERVER: net_st->netplay_enabled = true; net_st->netplay_is_client = false; goto done; case RARCH_NETPLAY_CTL_ENABLE_CLIENT: net_st->netplay_enabled = true; net_st->netplay_is_client = true; break; case RARCH_NETPLAY_CTL_DISABLE: net_st->netplay_enabled = false; #ifdef HAVE_DISCORD if (discord_state_get_ptr()->inited) { discord_userdata_t userdata; userdata.status = DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED; command_event(CMD_EVENT_DISCORD_UPDATE, &userdata); } #endif goto done; case RARCH_NETPLAY_CTL_IS_ENABLED: ret = net_st->netplay_enabled; goto done; case RARCH_NETPLAY_CTL_IS_REPLAYING: case RARCH_NETPLAY_CTL_IS_DATA_INITED: ret = false; goto done; case RARCH_NETPLAY_CTL_IS_SERVER: ret = net_st->netplay_enabled && !net_st->netplay_is_client; goto done; case RARCH_NETPLAY_CTL_IS_CONNECTED: ret = false; goto done; default: goto done; } } switch (state) { case RARCH_NETPLAY_CTL_ENABLE_SERVER: case RARCH_NETPLAY_CTL_ENABLE_CLIENT: case RARCH_NETPLAY_CTL_IS_DATA_INITED: goto done; case RARCH_NETPLAY_CTL_DISABLE: ret = false; goto done; case RARCH_NETPLAY_CTL_IS_ENABLED: goto done; case RARCH_NETPLAY_CTL_IS_REPLAYING: ret = netplay->is_replay; goto done; case RARCH_NETPLAY_CTL_IS_SERVER: ret = net_st->netplay_enabled && !net_st->netplay_is_client; goto done; case RARCH_NETPLAY_CTL_IS_CONNECTED: ret = netplay->is_connected; goto done; case RARCH_NETPLAY_CTL_POST_FRAME: netplay_post_frame(netplay); /* If we're disconnected, deinitialize */ if (!netplay->is_server && !netplay->connections[0].active) netplay_disconnect(netplay); break; case RARCH_NETPLAY_CTL_PRE_FRAME: ret = netplay_pre_frame( settings->bools.netplay_public_announce, settings->bools.netplay_use_mitm_server, netplay); goto done; case RARCH_NETPLAY_CTL_GAME_WATCH: netplay_toggle_play_spectate(netplay); break; case RARCH_NETPLAY_CTL_PLAYER_CHAT: netplay_input_chat(netplay); break; case RARCH_NETPLAY_CTL_PAUSE: if (netplay->local_paused != true) netplay_frontend_paused(netplay, true); break; case RARCH_NETPLAY_CTL_UNPAUSE: if (netplay->local_paused != false) netplay_frontend_paused(netplay, false); break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: netplay_load_savestate(netplay, (retro_ctx_serialize_info_t*)data, true); break; case RARCH_NETPLAY_CTL_RESET: netplay_core_reset(netplay); break; case RARCH_NETPLAY_CTL_DISCONNECT: ret = true; if (netplay) netplay_disconnect(netplay); goto done; case RARCH_NETPLAY_CTL_FINISHED_NAT_TRAVERSAL: netplay->nat_traversal_task_oustanding = false; #ifndef HAVE_SOCKET_LEGACY netplay_announce_nat_traversal(netplay); #endif goto done; case RARCH_NETPLAY_CTL_DESYNC_PUSH: netplay->desync++; break; case RARCH_NETPLAY_CTL_DESYNC_POP: if (netplay->desync) { netplay->desync--; if (!netplay->desync) netplay_load_savestate(netplay, NULL, true); } break; default: case RARCH_NETPLAY_CTL_NONE: ret = false; } done: net_st->in_netplay = false; return ret; } #undef MAX_CHAT_SIZE #undef CHAT_FRAME_TIME