diff --git a/Makefile.common b/Makefile.common index 619bf22ab2..00b8690f56 100644 --- a/Makefile.common +++ b/Makefile.common @@ -881,7 +881,7 @@ ifeq ($(HAVE_NETWORKING), 1) ifeq ($(HAVE_NETPLAY), 1) DEFINES += -DHAVE_NETPLAY -DHAVE_NETWORK_CMD -DHAVE_NETWORK_GAMEPAD - OBJ += netplay.o + OBJ += netplay/netplay.o netplay/netplay_net.o netplay/netplay_spectate.o netplay/netplay_common.o endif # Retro Achievements (also depends on threads) diff --git a/command_event.c b/command_event.c index 737426cef0..ce62ed42df 100644 --- a/command_event.c +++ b/command_event.c @@ -53,7 +53,7 @@ #endif #ifdef HAVE_NETPLAY -#include "netplay.h" +#include "netplay/netplay.h" #endif #ifdef HAVE_NETWORKING diff --git a/libretro_version_1.c b/libretro_version_1.c index e38cc3af7e..de5874bbe6 100644 --- a/libretro_version_1.c +++ b/libretro_version_1.c @@ -31,7 +31,7 @@ #include "audio/audio_driver.h" #ifdef HAVE_NETPLAY -#include "netplay.h" +#include "netplay/netplay.h" #endif struct retro_callbacks retro_ctx; diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 72a4be7598..8b8157cd5b 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -610,7 +610,7 @@ void *menu_init(const void *data) menu->help_screen_type = MENU_HELP_EXTRACT; menu->push_help_screen = true; rarch_task_push_decompress(settings->bundle_assets_src_path, settings->bundle_assets_dst_path, - settings->bundle_assets_dst_path_subdir, NULL, bundle_decompressed, NULL); + settings->bundle_assets_dst_path_subdir, NULL, bundle_decompressed, NULL); } menu_shader_manager_init(menu); diff --git a/netplay.c b/netplay/netplay.c similarity index 63% rename from netplay.c rename to netplay/netplay.c index 24c6e9d55d..452fb89ec5 100644 --- a/netplay.c +++ b/netplay/netplay.c @@ -25,26 +25,7 @@ #include #include -#include "netplay.h" -#include "command_event.h" -#include "general.h" -#include "autosave.h" -#include "dynamic.h" -#include "movie.h" -#include "msg_hash.h" -#include "system.h" -#include "runloop.h" -#include "verbosity.h" - -#ifdef ANDROID -#define HAVE_IPV6 -#endif - -#define UDP_FRAME_PACKETS 16 -#define MAX_SPECTATORS 16 - -#define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) -#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) +#include "netplay_private.h" enum { @@ -55,92 +36,6 @@ enum CMD_OPT_REQUIRE_SYNC = 0x10 }; -struct delta_frame -{ - void *state; - - uint16_t real_input_state; - uint16_t simulated_input_state; - uint16_t self_state; - - bool is_simulated; - bool used_real; -}; - -struct netplay -{ - char nick[32]; - char other_nick[32]; - struct sockaddr_storage other_addr; - - struct retro_callbacks cbs; - /* TCP connection for state sending, etc. Also used for commands */ - int fd; - /* UDP connection for game state updates. */ - int udp_fd; - /* Which port is governed by netplay (other user)? */ - unsigned port; - bool has_connection; - - struct delta_frame *buffer; - size_t buffer_size; - - /* Pointer where we are now. */ - size_t self_ptr; - /* Points to the last reliable state that self ever had. */ - size_t other_ptr; - /* Pointer to where we are reading. - * Generally, other_ptr <= read_ptr <= self_ptr. */ - size_t read_ptr; - /* A temporary pointer used on replay. */ - size_t tmp_ptr; - - size_t state_size; - - /* Are we replaying old frames? */ - bool is_replay; - /* We don't want to poll several times on a frame. */ - bool can_poll; - - /* To compat UDP packet loss we also send - * old data along with the packets. */ - uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; - uint32_t frame_count; - uint32_t read_frame_count; - uint32_t other_frame_count; - uint32_t tmp_frame_count; - struct addrinfo *addr; - struct sockaddr_storage their_addr; - bool has_client_addr; - - unsigned timeout_cnt; - - /* Spectating. */ - struct { - bool enabled; - bool client; - int fds[MAX_SPECTATORS]; - uint16_t *input; - size_t input_ptr; - size_t input_sz; - } spectate; - - /* User flipping - * Flipping state. If ptr >= flip_frame, we apply the flip. - * If not, we apply the opposite, effectively creating a trigger point. - * To avoid collition we need to make sure our client/host is synced up - * well after flip_frame before allowing another flip. */ - bool flip; - uint32_t flip_frame; - - /* Netplay pausing - */ - bool pause; - uint32_t pause_frame; -}; - -static void *netplay_data; - /** * warn_hangup: * @@ -164,6 +59,10 @@ bool check_netplay_synched(netplay_t* netplay) return netplay->frame_count < (netplay->flip_frame + 2 * UDP_FRAME_PACKETS); } +static bool info_cb(netplay_t* netplay, unsigned frames) { + return netplay->net_cbs->info_cb(netplay, frames); +} + /** * netplay_should_skip: * @netplay : pointer to netplay object @@ -638,7 +537,7 @@ int16_t input_state_net(unsigned port, unsigned device, #ifndef HAVE_SOCKET_LEGACY /* Custom inet_ntop. Win32 doesn't seem to support this ... */ -static void log_connection(const struct sockaddr_storage *their_addr, +void np_log_connection(const struct sockaddr_storage *their_addr, unsigned slot, const char *nick) { union @@ -873,351 +772,6 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) return true; } - -/** - * implementation_magic_value: - * - * Not really a hash, but should be enough to differentiate - * implementations from each other. - * - * Subtle differences in the implementation will not be possible to spot. - * The alternative would have been checking serialization sizes, but it - * was troublesome for cross platform compat. - **/ -static uint32_t implementation_magic_value(void) -{ - size_t i, len; - uint32_t res = 0; - rarch_system_info_t *info = NULL; - const char *lib = NULL; - const char *ver = PACKAGE_VERSION; - unsigned api = core.retro_api_version(); - - runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); - - if (info) - lib = info->info.library_name; - - res |= api; - - len = strlen(lib); - for (i = 0; i < len; i++) - res ^= lib[i] << (i & 0xf); - - lib = info->info.library_version; - len = strlen(lib); - - for (i = 0; i < len; i++) - res ^= lib[i] << (i & 0xf); - - len = strlen(ver); - for (i = 0; i < len; i++) - res ^= ver[i] << ((i & 0xf) + 16); - - return res; -} - -static bool send_nickname(netplay_t *netplay, int fd) -{ - uint8_t nick_size = strlen(netplay->nick); - - if (!socket_send_all_blocking(fd, &nick_size, sizeof(nick_size))) - { - RARCH_ERR("Failed to send nick size.\n"); - return false; - } - - if (!socket_send_all_blocking(fd, netplay->nick, nick_size)) - { - RARCH_ERR("Failed to send nick.\n"); - return false; - } - - return true; -} - -static bool get_nickname(netplay_t *netplay, int fd) -{ - uint8_t nick_size; - - if (!socket_receive_all_blocking(fd, &nick_size, sizeof(nick_size))) - { - RARCH_ERR("Failed to receive nick size from host.\n"); - return false; - } - - if (nick_size >= sizeof(netplay->other_nick)) - { - RARCH_ERR("Invalid nick size.\n"); - return false; - } - - if (!socket_receive_all_blocking(fd, netplay->other_nick, nick_size)) - { - RARCH_ERR("Failed to receive nick.\n"); - return false; - } - - return true; -} - -static bool send_info(netplay_t *netplay) -{ - unsigned sram_size; - char msg[512] = {0}; - void *sram = NULL; - uint32_t header[3] = {0}; - global_t *global = global_get_ptr(); - - header[0] = htonl(global->content_crc); - header[1] = htonl(implementation_magic_value()); - header[2] = htonl(core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM)); - - if (!socket_send_all_blocking(netplay->fd, header, sizeof(header))) - return false; - - if (!send_nickname(netplay, netplay->fd)) - { - RARCH_ERR("Failed to send nick to host.\n"); - return false; - } - - /* Get SRAM data from User 1. */ - sram = core.retro_get_memory_data(RETRO_MEMORY_SAVE_RAM); - sram_size = core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM); - - if (!socket_receive_all_blocking(netplay->fd, sram, sram_size)) - { - RARCH_ERR("Failed to receive SRAM data from host.\n"); - return false; - } - - if (!get_nickname(netplay, netplay->fd)) - { - RARCH_ERR("Failed to receive nick from host.\n"); - return false; - } - - snprintf(msg, sizeof(msg), "Connected to: \"%s\"", netplay->other_nick); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - - return true; -} - -static bool get_info(netplay_t *netplay) -{ - unsigned sram_size; - uint32_t header[3]; - const void *sram = NULL; - global_t *global = global_get_ptr(); - - if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header))) - { - RARCH_ERR("Failed to receive header from client.\n"); - return false; - } - - if (global->content_crc != ntohl(header[0])) - { - RARCH_ERR("Content CRC32s differ. Cannot use different games.\n"); - return false; - } - - if (implementation_magic_value() != ntohl(header[1])) - { - RARCH_ERR("Implementations differ, make sure you're using exact same libretro implementations and RetroArch version.\n"); - return false; - } - - if (core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM) != ntohl(header[2])) - { - RARCH_ERR("Content SRAM sizes do not correspond.\n"); - return false; - } - - if (!get_nickname(netplay, netplay->fd)) - { - RARCH_ERR("Failed to get nickname from client.\n"); - return false; - } - - /* Send SRAM data to our User 2. */ - sram = core.retro_get_memory_data(RETRO_MEMORY_SAVE_RAM); - sram_size = core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM); - - if (!socket_send_all_blocking(netplay->fd, sram, sram_size)) - { - RARCH_ERR("Failed to send SRAM data to client.\n"); - return false; - } - - if (!send_nickname(netplay, netplay->fd)) - { - RARCH_ERR("Failed to send nickname to client.\n"); - return false; - } - -#ifndef HAVE_SOCKET_LEGACY - log_connection(&netplay->other_addr, 0, netplay->other_nick); -#endif - - return true; -} - -static uint32_t *bsv_header_generate(size_t *size, uint32_t magic) -{ - uint32_t *header, bsv_header[4] = {0}; - size_t serialize_size = core.retro_serialize_size(); - size_t header_size = sizeof(bsv_header) + serialize_size; - global_t *global = global_get_ptr(); - - *size = header_size; - - header = (uint32_t*)malloc(header_size); - if (!header) - return NULL; - - bsv_header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC); - bsv_header[SERIALIZER_INDEX] = swap_if_big32(magic); - bsv_header[CRC_INDEX] = swap_if_big32(global->content_crc); - bsv_header[STATE_SIZE_INDEX] = swap_if_big32(serialize_size); - - if (serialize_size && !core.retro_serialize(header + 4, serialize_size)) - { - free(header); - return NULL; - } - - memcpy(header, bsv_header, sizeof(bsv_header)); - return header; -} - -static bool bsv_parse_header(const uint32_t *header, uint32_t magic) -{ - uint32_t in_crc, in_magic, in_state_size; - uint32_t in_bsv = swap_if_little32(header[MAGIC_INDEX]); - global_t *global = global_get_ptr(); - - if (in_bsv != BSV_MAGIC) - { - RARCH_ERR("BSV magic mismatch, got 0x%x, expected 0x%x.\n", - in_bsv, BSV_MAGIC); - return false; - } - - in_magic = swap_if_big32(header[SERIALIZER_INDEX]); - if (in_magic != magic) - { - RARCH_ERR("Magic mismatch, got 0x%x, expected 0x%x.\n", in_magic, magic); - return false; - } - - in_crc = swap_if_big32(header[CRC_INDEX]); - if (in_crc != global->content_crc) - { - RARCH_ERR("CRC32 mismatch, got 0x%x, expected 0x%x.\n", in_crc, - global->content_crc); - return false; - } - - in_state_size = swap_if_big32(header[STATE_SIZE_INDEX]); - if (in_state_size != core.retro_serialize_size()) - { - RARCH_ERR("Serialization size mismatch, got 0x%x, expected 0x%x.\n", - (unsigned)in_state_size, (unsigned)core.retro_serialize_size()); - return false; - } - - return true; -} - -static bool get_info_spectate(netplay_t *netplay) -{ - size_t save_state_size, size; - void *buf = NULL; - uint32_t header[4] = {0}; - char msg[512] = {0}; - bool ret = true; - - if (!send_nickname(netplay, netplay->fd)) - { - RARCH_ERR("Failed to send nickname to host.\n"); - return false; - } - - if (!get_nickname(netplay, netplay->fd)) - { - RARCH_ERR("Failed to receive nickname from host.\n"); - return false; - } - - snprintf(msg, sizeof(msg), "Connected to \"%s\"", netplay->other_nick); - runloop_msg_queue_push(msg, 1, 180, false); - RARCH_LOG("%s\n", msg); - - - if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header))) - { - RARCH_ERR("Cannot get header from host.\n"); - return false; - } - - save_state_size = core.retro_serialize_size(); - if (!bsv_parse_header(header, implementation_magic_value())) - { - RARCH_ERR("Received invalid BSV header from host.\n"); - return false; - } - - buf = malloc(save_state_size); - if (!buf) - return false; - - size = save_state_size; - - if (!socket_receive_all_blocking(netplay->fd, buf, size)) - { - RARCH_ERR("Failed to receive save state from host.\n"); - free(buf); - return false; - } - - if (save_state_size) - ret = core.retro_unserialize(buf, save_state_size); - - free(buf); - return ret; -} - -static bool init_buffers(netplay_t *netplay) -{ - unsigned i; - - if (!netplay) - return false; - - netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, - sizeof(*netplay->buffer)); - - if (!netplay->buffer) - return false; - - netplay->state_size = core.retro_serialize_size(); - - for (i = 0; i < netplay->buffer_size; i++) - { - netplay->buffer[i].state = malloc(netplay->state_size); - - if (!netplay->buffer[i].state) - return false; - - netplay->buffer[i].is_simulated = true; - } - - return true; -} - /** * netplay_new: * @server : IP address of server. @@ -1252,46 +806,22 @@ netplay_t *netplay_new(const char *server, uint16_t port, netplay->cbs = *cb; netplay->port = server ? 0 : 1; netplay->spectate.enabled = spectate; - netplay->spectate.client = server != NULL; + netplay->is_server = server == NULL; strlcpy(netplay->nick, nick, sizeof(netplay->nick)); + if(spectate) + netplay->net_cbs = netplay_get_cbs_spectate(); + else + netplay->net_cbs = netplay_get_cbs_net(); + if (!init_socket(netplay, server, port)) { free(netplay); return NULL; } - if (spectate) - { - if (server) - { - if (!get_info_spectate(netplay)) - goto error; - } - - for (i = 0; i < MAX_SPECTATORS; i++) - netplay->spectate.fds[i] = -1; - } - else - { - if (server) - { - if (!send_info(netplay)) - goto error; - } - else - { - if (!get_info(netplay)) - goto error; - } - - netplay->buffer_size = frames + 1; - - if (!init_buffers(netplay)) - goto error; - - netplay->has_connection = true; - } + if(!info_cb(netplay, frames)) + goto error; return netplay; @@ -1441,20 +971,6 @@ void netplay_free(netplay_t *netplay) free(netplay); } -/** - * netplay_pre_frame_net: - * @netplay : pointer to netplay object - * - * Pre-frame for Netplay (normal version). - **/ -static void netplay_pre_frame_net(netplay_t *netplay) -{ - core.retro_serialize(netplay->buffer[netplay->self_ptr].state, - netplay->state_size); - netplay->can_poll = true; - - input_poll_net(); -} static void netplay_set_spectate_input(netplay_t *netplay, int16_t input) { @@ -1501,104 +1017,6 @@ int16_t input_state_spectate_client(unsigned port, unsigned device, device, idx, id); } -/** - * netplay_pre_frame_spectate: - * @netplay : pointer to netplay object - * - * Pre-frame for Netplay (spectate mode version). - **/ -static void netplay_pre_frame_spectate(netplay_t *netplay) -{ - unsigned i; - uint32_t *header; - int new_fd, idx, bufsize; - size_t header_size; - struct sockaddr_storage their_addr; - socklen_t addr_size; - fd_set fds; - struct timeval tmp_tv = {0}; - - if (netplay->spectate.client) - return; - - FD_ZERO(&fds); - FD_SET(netplay->fd, &fds); - - if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - return; - - if (!FD_ISSET(netplay->fd, &fds)) - return; - - addr_size = sizeof(their_addr); - new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size); - if (new_fd < 0) - { - RARCH_ERR("Failed to accept incoming spectator.\n"); - return; - } - - idx = -1; - for (i = 0; i < MAX_SPECTATORS; i++) - { - if (netplay->spectate.fds[i] == -1) - { - idx = i; - break; - } - } - - /* No vacant client streams :( */ - if (idx == -1) - { - socket_close(new_fd); - return; - } - - if (!get_nickname(netplay, new_fd)) - { - RARCH_ERR("Failed to get nickname from client.\n"); - socket_close(new_fd); - return; - } - - if (!send_nickname(netplay, new_fd)) - { - RARCH_ERR("Failed to send nickname to client.\n"); - socket_close(new_fd); - return; - } - - header = bsv_header_generate(&header_size, - implementation_magic_value()); - - if (!header) - { - RARCH_ERR("Failed to generate BSV header.\n"); - socket_close(new_fd); - return; - } - - bufsize = header_size; - setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&bufsize, - sizeof(int)); - - if (!socket_send_all_blocking(new_fd, header, header_size)) - { - RARCH_ERR("Failed to send header to client.\n"); - socket_close(new_fd); - free(header); - return; - } - - free(header); - netplay->spectate.fds[idx] = new_fd; - -#ifndef HAVE_SOCKET_LEGACY - log_connection(&their_addr, idx, netplay->other_nick); -#endif -} - /** * netplay_pre_frame: * @netplay : pointer to netplay object @@ -1608,111 +1026,8 @@ static void netplay_pre_frame_spectate(netplay_t *netplay) **/ void netplay_pre_frame(netplay_t *netplay) { - if (netplay->spectate.enabled) - netplay_pre_frame_spectate(netplay); - else - netplay_pre_frame_net(netplay); -} - -/** - * netplay_post_frame_net: - * @netplay : pointer to netplay object - * - * Post-frame for Netplay (normal version). - * We check if we have new input and replay from recorded input. - **/ -static void netplay_post_frame_net(netplay_t *netplay) -{ - netplay->frame_count++; - - /* Nothing to do... */ - if (netplay->other_frame_count == netplay->read_frame_count) - return; - - /* Skip ahead if we predicted correctly. - * Skip until our simulation failed. */ - while (netplay->other_frame_count < netplay->read_frame_count) - { - const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; - - if ((ptr->simulated_input_state != ptr->real_input_state) - && !ptr->used_real) - break; - netplay->other_ptr = NEXT_PTR(netplay->other_ptr); - netplay->other_frame_count++; - } - - if (netplay->other_frame_count < netplay->read_frame_count) - { - bool first = true; - - /* Replay frames. */ - netplay->is_replay = true; - netplay->tmp_ptr = netplay->other_ptr; - netplay->tmp_frame_count = netplay->other_frame_count; - - core.retro_unserialize(netplay->buffer[netplay->other_ptr].state, - netplay->state_size); - - while (first || (netplay->tmp_ptr != netplay->self_ptr)) - { - core.retro_serialize(netplay->buffer[netplay->tmp_ptr].state, - netplay->state_size); -#if defined(HAVE_THREADS) && !defined(RARCH_CONSOLE) - lock_autosave(); -#endif - core.retro_run(); -#if defined(HAVE_THREADS) && !defined(RARCH_CONSOLE) - unlock_autosave(); -#endif - netplay->tmp_ptr = NEXT_PTR(netplay->tmp_ptr); - netplay->tmp_frame_count++; - first = false; - } - - netplay->other_ptr = netplay->read_ptr; - netplay->other_frame_count = netplay->read_frame_count; - netplay->is_replay = false; - } -} - -/** - * netplay_post_frame_spectate: - * @netplay : pointer to netplay object - * - * Post-frame for Netplay (spectate mode version). - * We check if we have new input and replay from recorded input. - **/ -static void netplay_post_frame_spectate(netplay_t *netplay) -{ - unsigned i; - - if (netplay->spectate.client) - return; - - for (i = 0; i < MAX_SPECTATORS; i++) - { - char msg[128]; - - if (netplay->spectate.fds[i] == -1) - continue; - - if (socket_send_all_blocking(netplay->spectate.fds[i], - netplay->spectate.input, - netplay->spectate.input_ptr * sizeof(int16_t))) - continue; - - RARCH_LOG("Client (#%u) disconnected ...\n", i); - - snprintf(msg, sizeof(msg), "Client (#%u) disconnected.", i); - runloop_msg_queue_push(msg, 1, 180, false); - - socket_close(netplay->spectate.fds[i]); - netplay->spectate.fds[i] = -1; - break; - } - - netplay->spectate.input_ptr = 0; + assert(netplay && netplay->net_cbs->pre_frame); + netplay->net_cbs->pre_frame(netplay); } /** @@ -1725,10 +1040,8 @@ static void netplay_post_frame_spectate(netplay_t *netplay) **/ void netplay_post_frame(netplay_t *netplay) { - if (netplay->spectate.enabled) - netplay_post_frame_spectate(netplay); - else - netplay_post_frame_net(netplay); + assert(netplay && netplay->net_cbs->post_frame); + netplay->net_cbs->post_frame(netplay); } void deinit_netplay(void) @@ -1739,8 +1052,6 @@ void deinit_netplay(void) netplay_data = NULL; } -#define RARCH_DEFAULT_PORT 55435 - /** * init_netplay: * diff --git a/netplay.h b/netplay/netplay.h similarity index 100% rename from netplay.h rename to netplay/netplay.h diff --git a/netplay/netplay_common.c b/netplay/netplay_common.c new file mode 100644 index 0000000000..58cb9b96e4 --- /dev/null +++ b/netplay/netplay_common.c @@ -0,0 +1,283 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2015 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "netplay_private.h" +bool np_get_nickname(netplay_t *netplay, int fd) +{ + uint8_t nick_size; + + if (!socket_receive_all_blocking(fd, &nick_size, sizeof(nick_size))) + { + RARCH_ERR("Failed to receive nick size from host.\n"); + return false; + } + + if (nick_size >= sizeof(netplay->other_nick)) + { + RARCH_ERR("Invalid nick size.\n"); + return false; + } + + if (!socket_receive_all_blocking(fd, netplay->other_nick, nick_size)) + { + RARCH_ERR("Failed to receive nick.\n"); + return false; + } + + return true; +} +bool np_send_nickname(netplay_t *netplay, int fd) +{ + uint8_t nick_size = strlen(netplay->nick); + + if (!socket_send_all_blocking(fd, &nick_size, sizeof(nick_size))) + { + RARCH_ERR("Failed to send nick size.\n"); + return false; + } + + if (!socket_send_all_blocking(fd, netplay->nick, nick_size)) + { + RARCH_ERR("Failed to send nick.\n"); + return false; + } + + return true; +} + +uint32_t *np_bsv_header_generate(size_t *size, uint32_t magic) +{ + uint32_t *header, bsv_header[4] = {0}; + size_t serialize_size = core.retro_serialize_size(); + size_t header_size = sizeof(bsv_header) + serialize_size; + global_t *global = global_get_ptr(); + + *size = header_size; + + header = (uint32_t*)malloc(header_size); + if (!header) + return NULL; + + bsv_header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC); + bsv_header[SERIALIZER_INDEX] = swap_if_big32(magic); + bsv_header[CRC_INDEX] = swap_if_big32(global->content_crc); + bsv_header[STATE_SIZE_INDEX] = swap_if_big32(serialize_size); + + if (serialize_size && !core.retro_serialize(header + 4, serialize_size)) + { + free(header); + return NULL; + } + + memcpy(header, bsv_header, sizeof(bsv_header)); + return header; +} + +bool np_bsv_parse_header(const uint32_t *header, uint32_t magic) +{ + uint32_t in_crc, in_magic, in_state_size; + uint32_t in_bsv = swap_if_little32(header[MAGIC_INDEX]); + global_t *global = global_get_ptr(); + + if (in_bsv != BSV_MAGIC) + { + RARCH_ERR("BSV magic mismatch, got 0x%x, expected 0x%x.\n", + in_bsv, BSV_MAGIC); + return false; + } + + in_magic = swap_if_big32(header[SERIALIZER_INDEX]); + if (in_magic != magic) + { + RARCH_ERR("Magic mismatch, got 0x%x, expected 0x%x.\n", in_magic, magic); + return false; + } + + in_crc = swap_if_big32(header[CRC_INDEX]); + if (in_crc != global->content_crc) + { + RARCH_ERR("CRC32 mismatch, got 0x%x, expected 0x%x.\n", in_crc, + global->content_crc); + return false; + } + + in_state_size = swap_if_big32(header[STATE_SIZE_INDEX]); + if (in_state_size != core.retro_serialize_size()) + { + RARCH_ERR("Serialization size mismatch, got 0x%x, expected 0x%x.\n", + (unsigned)in_state_size, (unsigned)core.retro_serialize_size()); + return false; + } + + return true; +} + +/** + * np_impl_magic: + * + * Not really a hash, but should be enough to differentiate + * implementations from each other. + * + * Subtle differences in the implementation will not be possible to spot. + * The alternative would have been checking serialization sizes, but it + * was troublesome for cross platform compat. + **/ +uint32_t np_impl_magic(void) +{ + size_t i, len; + uint32_t res = 0; + rarch_system_info_t *info = NULL; + const char *lib = NULL; + const char *ver = PACKAGE_VERSION; + unsigned api = core.retro_api_version(); + + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); + + if (info) + lib = info->info.library_name; + + res |= api; + + len = strlen(lib); + for (i = 0; i < len; i++) + res ^= lib[i] << (i & 0xf); + + lib = info->info.library_version; + len = strlen(lib); + + for (i = 0; i < len; i++) + res ^= lib[i] << (i & 0xf); + + len = strlen(ver); + for (i = 0; i < len; i++) + res ^= ver[i] << ((i & 0xf) + 16); + + return res; +} + +bool np_send_info(netplay_t *netplay) +{ + unsigned sram_size; + char msg[512] = {0}; + void *sram = NULL; + uint32_t header[3] = {0}; + global_t *global = global_get_ptr(); + + header[0] = htonl(global->content_crc); + header[1] = htonl(np_impl_magic()); + header[2] = htonl(core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM)); + + if (!socket_send_all_blocking(netplay->fd, header, sizeof(header))) + return false; + + if (!np_send_nickname(netplay, netplay->fd)) + { + RARCH_ERR("Failed to send nick to host.\n"); + return false; + } + + /* Get SRAM data from User 1. */ + sram = core.retro_get_memory_data(RETRO_MEMORY_SAVE_RAM); + sram_size = core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM); + + if (!socket_receive_all_blocking(netplay->fd, sram, sram_size)) + { + RARCH_ERR("Failed to receive SRAM data from host.\n"); + return false; + } + + if (!np_get_nickname(netplay, netplay->fd)) + { + RARCH_ERR("Failed to receive nick from host.\n"); + return false; + } + + snprintf(msg, sizeof(msg), "Connected to: \"%s\"", netplay->other_nick); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + + return true; +} + +bool np_get_info(netplay_t *netplay) +{ + unsigned sram_size; + uint32_t header[3]; + const void *sram = NULL; + global_t *global = global_get_ptr(); + + if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header))) + { + RARCH_ERR("Failed to receive header from client.\n"); + return false; + } + + if (global->content_crc != ntohl(header[0])) + { + RARCH_ERR("Content CRC32s differ. Cannot use different games.\n"); + return false; + } + + if (np_impl_magic() != ntohl(header[1])) + { + RARCH_ERR("Implementations differ, make sure you're using exact same libretro implementations and RetroArch version.\n"); + return false; + } + + if (core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM) != ntohl(header[2])) + { + RARCH_ERR("Content SRAM sizes do not correspond.\n"); + return false; + } + + if (!np_get_nickname(netplay, netplay->fd)) + { + RARCH_ERR("Failed to get nickname from client.\n"); + return false; + } + + /* Send SRAM data to our User 2. */ + sram = core.retro_get_memory_data(RETRO_MEMORY_SAVE_RAM); + sram_size = core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM); + + if (!socket_send_all_blocking(netplay->fd, sram, sram_size)) + { + RARCH_ERR("Failed to send SRAM data to client.\n"); + return false; + } + + if (!np_send_nickname(netplay, netplay->fd)) + { + RARCH_ERR("Failed to send nickname to client.\n"); + return false; + } + +#ifndef HAVE_SOCKET_LEGACY + np_log_connection(&netplay->other_addr, 0, netplay->other_nick); +#endif + + return true; +} + +bool np_is_server(netplay_t* netplay) +{ + return netplay->is_server; +} + +bool np_is_spectate(netplay_t* netplay) +{ + return netplay->spectate.enabled; +} \ No newline at end of file diff --git a/netplay/netplay_net.c b/netplay/netplay_net.c new file mode 100644 index 0000000000..5fafed64ad --- /dev/null +++ b/netplay/netplay_net.c @@ -0,0 +1,153 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2015 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "netplay_private.h" +/** + * pre_frame: + * @netplay : pointer to netplay object + * + * Pre-frame for Netplay (normal version). + **/ +static void pre_frame(netplay_t *netplay) +{ + core.retro_serialize(netplay->buffer[netplay->self_ptr].state, + netplay->state_size); + netplay->can_poll = true; + + input_poll_net(); +} + +/** + * post_frame: + * @netplay : pointer to netplay object + * + * Post-frame for Netplay (normal version). + * We check if we have new input and replay from recorded input. + **/ +static void post_frame(netplay_t *netplay) +{ + netplay->frame_count++; + + /* Nothing to do... */ + if (netplay->other_frame_count == netplay->read_frame_count) + return; + + /* Skip ahead if we predicted correctly. + * Skip until our simulation failed. */ + while (netplay->other_frame_count < netplay->read_frame_count) + { + const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; + + if ((ptr->simulated_input_state != ptr->real_input_state) + && !ptr->used_real) + break; + netplay->other_ptr = NEXT_PTR(netplay->other_ptr); + netplay->other_frame_count++; + } + + if (netplay->other_frame_count < netplay->read_frame_count) + { + bool first = true; + + /* Replay frames. */ + netplay->is_replay = true; + netplay->tmp_ptr = netplay->other_ptr; + netplay->tmp_frame_count = netplay->other_frame_count; + + core.retro_unserialize(netplay->buffer[netplay->other_ptr].state, + netplay->state_size); + + while (first || (netplay->tmp_ptr != netplay->self_ptr)) + { + core.retro_serialize(netplay->buffer[netplay->tmp_ptr].state, + netplay->state_size); +#if defined(HAVE_THREADS) && !defined(RARCH_CONSOLE) + lock_autosave(); +#endif + core.retro_run(); +#if defined(HAVE_THREADS) && !defined(RARCH_CONSOLE) + unlock_autosave(); +#endif + netplay->tmp_ptr = NEXT_PTR(netplay->tmp_ptr); + netplay->tmp_frame_count++; + first = false; + } + + netplay->other_ptr = netplay->read_ptr; + netplay->other_frame_count = netplay->read_frame_count; + netplay->is_replay = false; + } +} +static bool init_buffers(netplay_t *netplay) +{ + unsigned i; + + if (!netplay) + return false; + + netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, + sizeof(*netplay->buffer)); + + if (!netplay->buffer) + return false; + + netplay->state_size = core.retro_serialize_size(); + + for (i = 0; i < netplay->buffer_size; i++) + { + netplay->buffer[i].state = malloc(netplay->state_size); + + if (!netplay->buffer[i].state) + return false; + + netplay->buffer[i].is_simulated = true; + } + + return true; +} + +static bool info_cb(netplay_t* netplay, unsigned frames) +{ + if (np_is_server(netplay)) + { + if (!np_send_info(netplay)) + return false; + } + else + { + if (!np_get_info(netplay)) + return false; + } + + netplay->buffer_size = frames + 1; + + if (!init_buffers(netplay)) + return false; + + netplay->has_connection = true; + + return true; +} + +struct netplay_callbacks* netplay_get_cbs_net(void) +{ + static struct netplay_callbacks cbs = { + &pre_frame, + &post_frame, + &info_cb + }; + return &cbs; +} \ No newline at end of file diff --git a/netplay/netplay_private.h b/netplay/netplay_private.h new file mode 100644 index 0000000000..feb9e86a6b --- /dev/null +++ b/netplay/netplay_private.h @@ -0,0 +1,151 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2015 - Daniel De Matteis + * + * 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 . + */ + +#ifndef __RARCH_NETPLAY_PRIVATE_H +#define __RARCH_NETPLAY_PRIVATE_H +#include "netplay.h" +#include +#include +#include "../command_event.h" +#include "../general.h" +#include "../autosave.h" +#include "../dynamic.h" +#include "../movie.h" +#include "../msg_hash.h" +#include "../system.h" +#include "../runloop.h" +#include "../verbosity.h" + +#ifdef ANDROID +#define HAVE_IPV6 +#endif + +#define UDP_FRAME_PACKETS 16 +#define MAX_SPECTATORS 16 +#define RARCH_DEFAULT_PORT 55435 + +#define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) +#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) + +struct delta_frame +{ + void *state; + + uint16_t real_input_state; + uint16_t simulated_input_state; + uint16_t self_state; + + bool is_simulated; + bool used_real; +}; + +struct netplay_callbacks { + void (*pre_frame) (netplay_t *netplay); + void (*post_frame)(netplay_t *netplay); + bool (*info_cb) (netplay_t *netplay, unsigned frames); +}; + +struct netplay +{ + char nick[32]; + char other_nick[32]; + struct sockaddr_storage other_addr; + + struct retro_callbacks cbs; + /* TCP connection for state sending, etc. Also used for commands */ + int fd; + /* UDP connection for game state updates. */ + int udp_fd; + /* Which port is governed by netplay (other user)? */ + unsigned port; + bool has_connection; + + struct delta_frame *buffer; + size_t buffer_size; + + /* Pointer where we are now. */ + size_t self_ptr; + /* Points to the last reliable state that self ever had. */ + size_t other_ptr; + /* Pointer to where we are reading. + * Generally, other_ptr <= read_ptr <= self_ptr. */ + size_t read_ptr; + /* A temporary pointer used on replay. */ + size_t tmp_ptr; + + size_t state_size; + + /* Are we replaying old frames? */ + bool is_replay; + /* We don't want to poll several times on a frame. */ + bool can_poll; + + /* To compat UDP packet loss we also send + * old data along with the packets. */ + uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; + uint32_t frame_count; + uint32_t read_frame_count; + uint32_t other_frame_count; + uint32_t tmp_frame_count; + struct addrinfo *addr; + struct sockaddr_storage their_addr; + bool has_client_addr; + + unsigned timeout_cnt; + + /* Spectating. */ + struct { + bool enabled; + int fds[MAX_SPECTATORS]; + uint16_t *input; + size_t input_ptr; + size_t input_sz; + } spectate; + bool is_server; + /* User flipping + * Flipping state. If ptr >= flip_frame, we apply the flip. + * If not, we apply the opposite, effectively creating a trigger point. + * To avoid collition we need to make sure our client/host is synced up + * well after flip_frame before allowing another flip. */ + bool flip; + uint32_t flip_frame; + + /* Netplay pausing + */ + bool pause; + uint32_t pause_frame; + + struct netplay_callbacks* net_cbs; +}; + +void *netplay_data; + +struct netplay_callbacks* netplay_get_cbs_net(void); +struct netplay_callbacks* netplay_get_cbs_spectate(void); +void np_log_connection(const struct sockaddr_storage *their_addr, + unsigned slot, const char *nick); + +bool np_get_nickname(netplay_t *netplay, int fd); +bool np_send_nickname(netplay_t *netplay, int fd); +bool np_send_info(netplay_t *netplay); +uint32_t *np_bsv_header_generate(size_t *size, uint32_t magic); +bool np_bsv_parse_header(const uint32_t *header, uint32_t magic); +uint32_t np_impl_magic(void); +bool np_send_info(netplay_t *netplay); +bool np_get_info(netplay_t *netplay); +bool np_is_server(netplay_t* netplay); +bool np_is_spectate(netplay_t* netplay); +#endif \ No newline at end of file diff --git a/netplay/netplay_spectate.c b/netplay/netplay_spectate.c new file mode 100644 index 0000000000..82886e916c --- /dev/null +++ b/netplay/netplay_spectate.c @@ -0,0 +1,179 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2015 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "netplay_private.h" + +/** + * netplay_pre_frame_spectate: + * @netplay : pointer to netplay object + * + * Pre-frame for Netplay (spectate mode version). + **/ +static void pre_frame(netplay_t *netplay) +{ + unsigned i; + uint32_t *header; + int new_fd, idx, bufsize; + size_t header_size; + struct sockaddr_storage their_addr; + socklen_t addr_size; + fd_set fds; + struct timeval tmp_tv = {0}; + + if (!np_is_server(netplay)) + return; + + FD_ZERO(&fds); + FD_SET(netplay->fd, &fds); + + if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + return; + + if (!FD_ISSET(netplay->fd, &fds)) + return; + + addr_size = sizeof(their_addr); + new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size); + if (new_fd < 0) + { + RARCH_ERR("Failed to accept incoming spectator.\n"); + return; + } + + idx = -1; + for (i = 0; i < MAX_SPECTATORS; i++) + { + if (netplay->spectate.fds[i] == -1) + { + idx = i; + break; + } + } + + /* No vacant client streams :( */ + if (idx == -1) + { + socket_close(new_fd); + return; + } + + if (!np_get_nickname(netplay, new_fd)) + { + RARCH_ERR("Failed to get nickname from client.\n"); + socket_close(new_fd); + return; + } + + if (!np_send_nickname(netplay, new_fd)) + { + RARCH_ERR("Failed to send nickname to client.\n"); + socket_close(new_fd); + return; + } + + header = np_bsv_header_generate(&header_size, + np_impl_magic()); + + if (!header) + { + RARCH_ERR("Failed to generate BSV header.\n"); + socket_close(new_fd); + return; + } + + bufsize = header_size; + setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&bufsize, + sizeof(int)); + + if (!socket_send_all_blocking(new_fd, header, header_size)) + { + RARCH_ERR("Failed to send header to client.\n"); + socket_close(new_fd); + free(header); + return; + } + + free(header); + netplay->spectate.fds[idx] = new_fd; + +#ifndef HAVE_SOCKET_LEGACY + np_log_connection(&their_addr, idx, netplay->other_nick); +#endif +} + +/** + * netplay_post_frame_spectate: + * @netplay : pointer to netplay object + * + * Post-frame for Netplay (spectate mode version). + * We check if we have new input and replay from recorded input. + **/ +static void post_frame(netplay_t *netplay) +{ + unsigned i; + + if (!np_is_server(netplay)) + return; + + for (i = 0; i < MAX_SPECTATORS; i++) + { + char msg[128]; + + if (netplay->spectate.fds[i] == -1) + continue; + + if (socket_send_all_blocking(netplay->spectate.fds[i], + netplay->spectate.input, + netplay->spectate.input_ptr * sizeof(int16_t))) + continue; + + RARCH_LOG("Client (#%u) disconnected ...\n", i); + + snprintf(msg, sizeof(msg), "Client (#%u) disconnected.", i); + runloop_msg_queue_push(msg, 1, 180, false); + + socket_close(netplay->spectate.fds[i]); + netplay->spectate.fds[i] = -1; + break; + } + + netplay->spectate.input_ptr = 0; +} + +static bool info_cb(netplay_t *netplay, unsigned frames) +{ + unsigned i; + if(np_is_server(netplay)) + { + if(!np_get_info(netplay)) + return false; + } + + for (i = 0; i < MAX_SPECTATORS; i++) + netplay->spectate.fds[i] = -1; + return true; +} + +struct netplay_callbacks* netplay_get_cbs_spectate(void) +{ + static struct netplay_callbacks cbs = { + &pre_frame, + &post_frame, + &info_cb + }; + + return &cbs; +} \ No newline at end of file diff --git a/runloop.c b/runloop.c index 83d037d0cb..2a927facb5 100644 --- a/runloop.c +++ b/runloop.c @@ -60,7 +60,7 @@ #endif #ifdef HAVE_NETPLAY -#include "netplay.h" +#include "netplay/netplay.h" #endif #include "verbosity.h"