/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2016 - Daniel De Matteis * Copyright (C) 2016 - Gregor Richards * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include #include "netplay_private.h" #include "retro_assert.h" #include "../../autosave.h" /** * netplay_spectate_pre_frame: * @netplay : pointer to netplay object * * Pre-frame for Netplay (spectator version). **/ static bool netplay_spectate_pre_frame(netplay_t *netplay) { if (netplay_is_server(netplay)) { fd_set fds; struct timeval tmp_tv = {0}; int new_fd, idx, i; struct sockaddr_storage their_addr; socklen_t addr_size; retro_ctx_serialize_info_t serial_info; uint32_t header[3]; netplay->can_poll = true; input_poll_net(); /* Send our input to any connected spectators */ for (i = 0; i < MAX_SPECTATORS; i++) { if (netplay->spectate.fds[i] >= 0) { netplay->packet_buffer[2] = htonl(netplay->self_frame_count - netplay->spectate.frames[i]); if (!socket_send_all_blocking(netplay->spectate.fds[i], netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) { socket_close(netplay->spectate.fds[i]); netplay->spectate.fds[i] = -1; } } } /* Check for connections */ FD_ZERO(&fds); FD_SET(netplay->fd, &fds); if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) return true; if (!FD_ISSET(netplay->fd, &fds)) return true; addr_size = sizeof(their_addr); new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size); if (new_fd < 0) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_ACCEPT_INCOMING_SPECTATOR)); return true; } 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 true; } if (!netplay_get_nickname(netplay, new_fd)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT)); socket_close(new_fd); return true; } if (!netplay_send_nickname(netplay, new_fd)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT)); socket_close(new_fd); return true; } /* Wait until it's safe to serialize */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) { netplay->is_replay = true; netplay->replay_ptr = netplay->self_ptr; netplay->replay_frame_count = netplay->self_frame_count; netplay_wait_and_init_serialization(netplay); netplay->is_replay = false; } /* Start them at the current frame */ netplay->spectate.frames[idx] = netplay->self_frame_count; serial_info.data_const = NULL; serial_info.data = netplay->buffer[netplay->self_ptr].state; serial_info.size = netplay->state_size; if (core_serialize(&serial_info)) { /* Send them the savestate */ header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); header[1] = htonl(serial_info.size + sizeof(uint32_t)); header[2] = htonl(0); if (!socket_send_all_blocking(new_fd, header, sizeof(header), false)) { socket_close(new_fd); return true; } if (!socket_send_all_blocking(new_fd, serial_info.data, serial_info.size, false)) { socket_close(new_fd); return true; } } /* And send them this frame's input */ netplay->packet_buffer[2] = htonl(0); if (!socket_send_all_blocking(new_fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) { socket_close(new_fd); return true; } netplay->spectate.fds[idx] = new_fd; } else { if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) { /* Mark our own data as already read, so we ignore local input */ netplay->buffer[netplay->self_ptr].have_local = true; } netplay->can_poll = true; input_poll_net(); /* Only proceed if we have data */ if (netplay->read_frame_count <= netplay->self_frame_count) return false; } return true; } /** * netplay_spectate_post_frame: * @netplay : pointer to netplay object * * Post-frame for Netplay (spectator version). * Not much here, just fast forward if we're behind the server. **/ static void netplay_spectate_post_frame(netplay_t *netplay) { netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; if (netplay_is_server(netplay)) { /* Not expecting any client data */ netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; } else { /* If we must rewind, it's because we got a save state */ if (netplay->force_rewind) { retro_ctx_serialize_info_t serial_info; /* Replay frames. */ netplay->is_replay = true; netplay->replay_ptr = netplay->other_ptr; netplay->replay_frame_count = netplay->other_frame_count; /* Wait until it's safe to serialize */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) 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; core_unserialize(&serial_info); while (netplay->replay_frame_count < netplay->self_frame_count) { autosave_lock(); core_run(); autosave_unlock(); netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; } netplay->is_replay = false; netplay->force_rewind = false; } /* We're in sync by definition */ if (netplay->read_frame_count < netplay->self_frame_count) { netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = netplay->read_frame_count; } else { netplay->other_ptr = netplay->self_ptr; netplay->other_frame_count = netplay->self_frame_count; } /* If the server gets significantly ahead, skip to catch up */ if (netplay->self_frame_count + netplay->stall_frames <= netplay->read_frame_count) { /* "Replay" into the future */ netplay->is_replay = true; netplay->replay_ptr = netplay->self_ptr; netplay->replay_frame_count = netplay->self_frame_count; while (netplay->replay_frame_count < netplay->read_frame_count - 1) { autosave_lock(); core_run(); autosave_unlock(); netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; netplay->self_ptr = netplay->replay_ptr; netplay->self_frame_count = netplay->replay_frame_count; } netplay->is_replay = false; } } } static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) { if (netplay_is_server(netplay)) { int i; for (i = 0; i < MAX_SPECTATORS; i++) netplay->spectate.fds[i] = -1; } else { if (!netplay_send_nickname(netplay, netplay->fd)) return false; if (!netplay_get_nickname(netplay, netplay->fd)) return false; } netplay->has_connection = true; return true; } struct netplay_callbacks* netplay_get_cbs_spectate(void) { static struct netplay_callbacks cbs = { &netplay_spectate_pre_frame, &netplay_spectate_post_frame, &netplay_spectate_info_cb }; return &cbs; }