From 900e5a79ec7cf1da07fe62bd1789c919e31caf5d Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 25 Sep 2016 11:05:50 -0400 Subject: [PATCH 01/89] Use nonblocking sockets for Netplay to avoid some stalls. --- Makefile.common | 1 + libretro-common/include/net/net_socket.h | 3 + libretro-common/net/net_socket.c | 35 ++- network/netplay/netplay.c | 124 ++++++++--- network/netplay/netplay_buf.c | 272 +++++++++++++++++++++++ network/netplay/netplay_net.c | 11 + network/netplay/netplay_private.h | 33 ++- network/netplay/netplay_spectate.c | 8 +- 8 files changed, 441 insertions(+), 46 deletions(-) create mode 100644 network/netplay/netplay_buf.c diff --git a/Makefile.common b/Makefile.common index 8245497b45..c81cf37b91 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1126,6 +1126,7 @@ ifeq ($(HAVE_NETWORKING), 1) network/netplay/netplay_spectate.o \ network/netplay/netplay_common.o \ network/netplay/netplay_discovery.o \ + network/netplay/netplay_buf.o \ network/netplay/netplay.o # Retro Achievements (also depends on threads) diff --git a/libretro-common/include/net/net_socket.h b/libretro-common/include/net/net_socket.h index d2e73d33e4..3196e128c9 100644 --- a/libretro-common/include/net/net_socket.h +++ b/libretro-common/include/net/net_socket.h @@ -69,6 +69,9 @@ int socket_select(int nfds, fd_set *readfs, fd_set *writefds, int socket_send_all_blocking(int fd, const void *data_, size_t size, bool no_signal); +ssize_t socket_send_all_nonblocking(int fd, const void *data_, size_t size, + bool no_signal); + int socket_receive_all_blocking(int fd, void *data_, size_t size); ssize_t socket_receive_all_nonblocking(int fd, bool *error, diff --git a/libretro-common/net/net_socket.c b/libretro-common/net/net_socket.c index 7715e2b41a..44482c1868 100644 --- a/libretro-common/net/net_socket.c +++ b/libretro-common/net/net_socket.c @@ -74,12 +74,9 @@ ssize_t socket_receive_all_nonblocking(int fd, bool *error, const uint8_t *data = (const uint8_t*)data_; ssize_t ret = recv(fd, (char*)data, size, 0); - if (ret > 0) + if (ret >= 0) return ret; - if (ret == 0) - return -1; - if (isagain(ret)) return 0; @@ -179,6 +176,36 @@ int socket_send_all_blocking(int fd, const void *data_, size_t size, return true; } +ssize_t socket_send_all_nonblocking(int fd, const void *data_, size_t size, + bool no_signal) +{ + const uint8_t *data = (const uint8_t*)data_; + ssize_t sent = 0; + + while (size) + { + ssize_t ret = send(fd, (const char*)data, size, + no_signal ? MSG_NOSIGNAL : 0); + if (ret < 0) + { + if (isagain(ret)) + break; + + return -1; + } + else if (ret == 0) + { + break; + } + + data += ret; + size -= ret; + sent += ret; + } + + return sent; +} + bool socket_bind(int fd, void *data) { int yes = 1; diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 53ebcffed3..beb9277787 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -263,6 +263,9 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve if (!init_tcp_socket(netplay, direct_host, server, port, netplay->spectate.enabled)) return false; + netplay_clear_socket_buffer(&netplay->send_packet_buffer); + netplay_clear_socket_buffer(&netplay->recv_packet_buffer); + if (netplay->is_server && netplay->nat_traversal) init_nat_traversal(netplay); @@ -399,17 +402,17 @@ static bool get_self_input_state(netplay_t *netplay) * frame * } */ - netplay->packet_buffer[0] = htonl(NETPLAY_CMD_INPUT); - netplay->packet_buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); - netplay->packet_buffer[2] = htonl(netplay->self_frame_count); - netplay->packet_buffer[3] = htonl(state[0]); - netplay->packet_buffer[4] = htonl(state[1]); - netplay->packet_buffer[5] = htonl(state[2]); + netplay->input_packet_buffer[0] = htonl(NETPLAY_CMD_INPUT); + netplay->input_packet_buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); + netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); + netplay->input_packet_buffer[3] = htonl(state[0]); + netplay->input_packet_buffer[4] = htonl(state[1]); + netplay->input_packet_buffer[5] = htonl(state[2]); if (!netplay->spectate.enabled) /* Spectate sends in its own way */ { - if (!socket_send_all_blocking(netplay->fd, - netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmdbuf, + sizeof(cmdbuf))) { hangup(netplay); return false; @@ -429,11 +432,12 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, cmdbuf[0] = htonl(cmd); cmdbuf[1] = htonl(size); - if (!socket_send_all_blocking(netplay->fd, cmdbuf, sizeof(cmdbuf), false)) + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmdbuf, + sizeof(cmdbuf))) return false; if (size > 0) - if (!socket_send_all_blocking(netplay->fd, data, size, false)) + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, data, size)) return false; return true; @@ -460,31 +464,46 @@ bool netplay_cmd_request_savestate(netplay_t *netplay) return netplay_send_raw_cmd(netplay, NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); } +static ssize_t netplay_recva(netplay_t *netplay, void *buf, size_t len) +{ + return netplay_recv(&netplay->read_packet_buffer, netplay->fd, buf, len, false); +} + static bool netplay_get_cmd(netplay_t *netplay) { uint32_t cmd; uint32_t flip_frame; uint32_t cmd_size; + ssize_t recvd; /* FIXME: This depends on delta_frame_ready */ - netplay->timeout_cnt = 0; +#define RECV(buf, sz) \ + recvd = netplay_recva(netplay, (buf), (sz)); \ + if (recvd >= 0 && recvd < (sz)) goto shrt; \ + else if (recvd < 0) - if (!socket_receive_all_blocking(netplay->fd, &cmd, sizeof(cmd))) + /* Keep receiving commands until there's no input left */ + while (true) + { + + RECV(&cmd, sizeof(cmd)) return false; cmd = ntohl(cmd); - if (!socket_receive_all_blocking(netplay->fd, &cmd_size, sizeof(cmd))) + RECV(&cmd_size, sizeof(cmd_size)); return false; cmd_size = ntohl(cmd_size); + netplay->timeout_cnt = 0; + switch (cmd) { case NETPLAY_CMD_ACK: /* Why are we even bothering? */ - return true; + break; case NETPLAY_CMD_NAK: /* Disconnect now! */ @@ -501,7 +520,7 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); } - if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) + RECV(buffer, sizeof(buffer)) { RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); return netplay_cmd_nak(netplay); @@ -513,7 +532,7 @@ static bool netplay_get_cmd(netplay_t *netplay) if (buffer[0] < netplay->read_frame_count) { /* We already had this, so ignore the new transmission */ - return true; + break; } else if (buffer[0] > netplay->read_frame_count) { @@ -527,7 +546,7 @@ static bool netplay_get_cmd(netplay_t *netplay) buffer + 1, sizeof(buffer) - sizeof(uint32_t)); netplay->read_ptr = NEXT_PTR(netplay->read_ptr); netplay->read_frame_count++; - return true; + break; } case NETPLAY_CMD_FLIP_PLAYERS: @@ -537,8 +556,7 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); } - if (!socket_receive_all_blocking( - netplay->fd, &flip_frame, sizeof(flip_frame))) + RECV(&flip_frame, sizeof(flip_frame)) { RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n"); return netplay_cmd_nak(netplay); @@ -565,7 +583,7 @@ static bool netplay_get_cmd(netplay_t *netplay) runloop_msg_queue_push( msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED), 1, 180, false); - return true; + break; case NETPLAY_CMD_SPECTATE: RARCH_ERR("NETPLAY_CMD_SPECTATE unimplemented.\n"); @@ -587,7 +605,7 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); } - if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) + RECV(buffer, sizeof(buffer)) { RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n"); return netplay_cmd_nak(netplay); @@ -614,7 +632,7 @@ static bool netplay_get_cmd(netplay_t *netplay) if (!found) { /* Oh well, we got rid of it! */ - return true; + break; } if (buffer[0] <= netplay->other_frame_count) @@ -636,14 +654,14 @@ static bool netplay_get_cmd(netplay_t *netplay) netplay->buffer[tmp_ptr].crc = buffer[1]; } - return true; + 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; - return true; + break; case NETPLAY_CMD_LOAD_SAVESTATE: { @@ -684,7 +702,7 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); } - if (!socket_receive_all_blocking(netplay->fd, &frame, sizeof(frame))) + RECV(&frame, sizeof(frame)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n"); return netplay_cmd_nak(netplay); @@ -697,7 +715,7 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); } - if (!socket_receive_all_blocking(netplay->fd, &isize, sizeof(isize))) + RECV(&isize, sizeof(isize)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); return netplay_cmd_nak(netplay); @@ -710,8 +728,7 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); } - if (!socket_receive_all_blocking(netplay->fd, - netplay->zbuffer, cmd_size - 2*sizeof(uint32_t))) + RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); return netplay_cmd_nak(netplay); @@ -742,23 +759,30 @@ static bool netplay_get_cmd(netplay_t *netplay) netplay->savestate_request_outstanding = false; netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = frame; - return true; + break; } case NETPLAY_CMD_PAUSE: netplay->remote_paused = true; - return true; + break; case NETPLAY_CMD_RESUME: netplay->remote_paused = false; - return true; + break; default: - break; + RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); + return netplay_cmd_nak(netplay); } - RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); - return netplay_cmd_nak(netplay); + netplay_recv_flush(&netplay->recv_packet_buffer); + + } + +shrt: + /* No more data, reset and try again */ + netplay_recv_reset(&netplay->recv_packet_buffer); + return true; } static int poll_input(netplay_t *netplay, bool block) @@ -1262,6 +1286,8 @@ bool netplay_init_serialization(netplay_t *netplay) static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) { + size_t packet_buffer_size; + if (!netplay) return false; @@ -1280,6 +1306,15 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)) netplay_init_serialization(netplay); + /* Make our packet buffer big enough for a save state and frames-many frames + * of input data, plus the headers for each of them */ + packet_buffer_size = netplay->state_size + frames * WORDS_PER_FRAME + (frames+1)*3; + + if (!netplay_init_socket_buffer(&netplay->send_packet_buffer, packet_buffer_size)) + return false; + if (!netplay_init_socket_buffer(&netplay->recv_packet_buffer, packet_buffer_size)) + return false; + return true; } @@ -1343,6 +1378,10 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, if(!netplay_info_cb(netplay, delay_frames)) goto error; + /* FIXME: Our initial connection should also be nonblocking */ + if (!socket_nonblock(netplay->fd)) + goto error; + return netplay; error: @@ -1464,6 +1503,9 @@ void netplay_free(netplay_t *netplay) free(netplay->buffer); } + netplay_deinit_socket_buffer(&netplay->send_packet_buffer); + netplay_deinit_socket_buffer(&netplay->recv_packet_buffer); + if (netplay->zbuffer) free(netplay->zbuffer); @@ -1542,6 +1584,8 @@ void netplay_post_frame(netplay_t *netplay) { retro_assert(netplay && netplay->net_cbs->post_frame); netplay->net_cbs->post_frame(netplay); + if (!netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) + hangup(netplay); } /** @@ -1559,8 +1603,13 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) netplay->local_paused = paused; if (netplay->has_connection && !netplay->spectate.enabled) + { netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); + + /* We're not going to be polled, so we need to flush this command now */ + netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, true); + } } /** @@ -1651,14 +1700,15 @@ void netplay_load_savestate(netplay_t *netplay, header[2] = htonl(netplay->self_frame_count); header[3] = htonl(serial_info->size); - if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false)) + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, header, + sizeof(header))) { hangup(netplay); return; } - if (!socket_send_all_blocking(netplay->fd, - netplay->zbuffer, wn, false)) + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, + netplay->zbuffer, wn)) { hangup(netplay); return; diff --git a/network/netplay/netplay_buf.c b/network/netplay/netplay_buf.c new file mode 100644 index 0000000000..6ab81369ef --- /dev/null +++ b/network/netplay/netplay_buf.c @@ -0,0 +1,272 @@ +/* RetroArch - A frontend for libretro. + * 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 "netplay_private.h" + +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; +} + +bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size) +{ + sbuf->data = malloc(size); + if (sbuf->data == NULL) + return false; + sbuf->bufsz = size; + sbuf->start = sbuf->read = sbuf->end = 0; + return true; +} + +void netplay_deinit_socket_buffer(struct socket_buffer *sbuf) +{ + if (sbuf->data) + free(sbuf->data); +} + +void netplay_clear_socket_buffer(struct socket_buffer *sbuf) +{ + sbuf->start = sbuf->read = sbuf->end = 0; +} + +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; + + } + + /* Flush what we can immediately */ + return netplay_send_flush(sbuf, sockfd, false); +} + +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), false)) + return false; + sbuf->start = sbuf->end = 0; + + } + else + { + sent = socket_send_all_nonblocking(sockfd, sbuf->data + sbuf->start, buf_used(sbuf), false); + 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, false)) + 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, false); + 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; +} + +ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t len, bool block) +{ + bool error; + ssize_t recvd; + + /* Receive whatever we can into the buffer */ + if (sbuf->end > sbuf->start) + { + error = false; + 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 + { + error = false; + 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 < len) + { + if (!socket_receive_all_blocking(sockfd, (unsigned char *) buf + recvd, len - recvd)) + return -1; + recvd = len; + + } + } + + return recvd; +} + +void netplay_recv_reset(struct socket_buffer *sbuf) +{ + sbuf->read = sbuf->start; +} + +void netplay_recv_flush(struct socket_buffer *sbuf) +{ + sbuf->start = sbuf->read; +} diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index f90d2362ef..3847e2f798 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -157,11 +157,22 @@ static bool netplay_net_pre_frame(netplay_t *netplay) RARCH_WARN("Cannot set Netplay port to close-on-exec. It may fail to reopen if the client disconnects.\n"); #endif + netplay_clear_socket_buffer(&netplay->send_packet_buffer); + netplay_clear_socket_buffer(&netplay->recv_packet_buffer); + /* Establish the connection */ if (netplay_handshake(netplay)) { netplay->has_connection = true; + /* FIXME: Not the best place for this, needs to happen after initial + * connection in get_info */ + if (!socket_nonblock(netplay->fd)) + { + free(netplay); + return NULL; + } + /* Send them the savestate */ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index faff16b4a2..fc03b6e800 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -103,6 +103,14 @@ struct delta_frame bool used_real; }; +struct socket_buffer +{ + unsigned char *data; + size_t bufsz; + size_t start, end; + size_t read; +}; + struct netplay_callbacks { bool (*pre_frame) (netplay_t *netplay); void (*post_frame)(netplay_t *netplay); @@ -180,11 +188,18 @@ struct netplay bool savestate_request_outstanding; /* A buffer for outgoing input packets. */ - uint32_t packet_buffer[2 + WORDS_PER_FRAME]; + uint32_t input_packet_buffer[2 + WORDS_PER_FRAME]; + + /* And buffers for sending and receiving our actual data */ + struct socket_buffer send_packet_buffer, recv_packet_buffer; + + /* All of our frame counts */ uint32_t self_frame_count; uint32_t read_frame_count; uint32_t other_frame_count; uint32_t replay_frame_count; + + /* And socket info */ struct addrinfo *addr; struct sockaddr_storage their_addr; bool has_client_addr; @@ -263,4 +278,20 @@ bool netplay_cmd_request_savestate(netplay_t *netplay); bool netplay_lan_ad_server(netplay_t *netplay); +bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size); + +void netplay_deinit_socket_buffer(struct socket_buffer *sbuf); + +void netplay_clear_socket_buffer(struct socket_buffer *sbuf); + +bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf, size_t len); + +bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block); + +ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t len, bool block); + +void netplay_recv_reset(struct socket_buffer *sbuf); + +void netplay_recv_flush(struct socket_buffer *sbuf); + #endif diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index 7f20f7e976..81ab4ef21c 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -54,8 +54,8 @@ static bool netplay_spectate_pre_frame(netplay_t *netplay) { 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)) + netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count - netplay->spectate.frames[i]); + if (!socket_send_all_blocking(netplay->spectate.fds[i], netplay->input_packet_buffer, sizeof(netplay->input_packet_buffer), false)) { socket_close(netplay->spectate.fds[i]); netplay->spectate.fds[i] = -1; @@ -146,8 +146,8 @@ static bool netplay_spectate_pre_frame(netplay_t *netplay) } /* 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)) + netplay->input_packet_buffer[2] = htonl(0); + if (!socket_send_all_blocking(new_fd, netplay->input_packet_buffer, sizeof(netplay->input_packet_buffer), false)) { socket_close(new_fd); return true; From 36a93e5697fb5cd9f4227b316ca792da62b3cb36 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 30 Nov 2016 23:07:39 -0500 Subject: [PATCH 02/89] Fix a few bits broken by merging. --- network/netplay/netplay.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index beb9277787..2cac0b1afe 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -411,8 +411,9 @@ static bool get_self_input_state(netplay_t *netplay) if (!netplay->spectate.enabled) /* Spectate sends in its own way */ { - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmdbuf, - sizeof(cmdbuf))) + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, + netplay->input_packet_buffer, + sizeof(netplay->input_packet_buffer))) { hangup(netplay); return false; @@ -466,7 +467,7 @@ bool netplay_cmd_request_savestate(netplay_t *netplay) static ssize_t netplay_recva(netplay_t *netplay, void *buf, size_t len) { - return netplay_recv(&netplay->read_packet_buffer, netplay->fd, buf, len, false); + return netplay_recv(&netplay->recv_packet_buffer, netplay->fd, buf, len, false); } static bool netplay_get_cmd(netplay_t *netplay) @@ -783,6 +784,8 @@ shrt: /* No more data, reset and try again */ netplay_recv_reset(&netplay->recv_packet_buffer); return true; + +#undef RECV } static int poll_input(netplay_t *netplay, bool block) From 121454141080e3b880bc36f67f8155d139400758 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 30 Nov 2016 23:21:34 -0500 Subject: [PATCH 03/89] Added packet buffer resizing to cope with initialization quirks. --- network/netplay/netplay.c | 9 ++++++++ network/netplay/netplay_buf.c | 34 +++++++++++++++++++++++++++++++ network/netplay/netplay_private.h | 2 ++ 3 files changed, 45 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 2cac0b1afe..bab32e80c9 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1204,6 +1204,7 @@ static void announce_nat_traversal(netplay_t *netplay) bool netplay_try_init_serialization(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; + size_t packet_buffer_size; if (netplay->state_size) return true; @@ -1222,6 +1223,14 @@ bool netplay_try_init_serialization(netplay_t *netplay) /* Once initialized, we no longer exhibit this quirk */ netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION); + /* Now we need a different packet buffer size because we actually know our + * state size */ + /* FIXME: Duplication */ + packet_buffer_size = netplay->state_size + netplay->stall_frames * + WORDS_PER_FRAME + (netplay->stall_frames+1)*3; + netplay_resize_socket_buffer(&netplay->send_packet_buffer, packet_buffer_size); + netplay_resize_socket_buffer(&netplay->recv_packet_buffer, packet_buffer_size); + return true; } diff --git a/network/netplay/netplay_buf.c b/network/netplay/netplay_buf.c index 6ab81369ef..ae704070d8 100644 --- a/network/netplay/netplay_buf.c +++ b/network/netplay/netplay_buf.c @@ -57,6 +57,40 @@ bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size) return true; } +bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize) +{ + unsigned char *newdata = malloc(newsize); + if (newdata == NULL) + 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; +} + void netplay_deinit_socket_buffer(struct socket_buffer *sbuf) { if (sbuf->data) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index fc03b6e800..9fa61a26a4 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -280,6 +280,8 @@ bool netplay_lan_ad_server(netplay_t *netplay); bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size); +bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize); + void netplay_deinit_socket_buffer(struct socket_buffer *sbuf); void netplay_clear_socket_buffer(struct socket_buffer *sbuf); From 0f5eec9987de080328a0a3947caee8a69df95a3c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 10:24:00 -0500 Subject: [PATCH 04/89] Use zbuffer_size instead of state_size for the packet buffer --- network/netplay/netplay.c | 42 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index bab32e80c9..6a89327546 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1223,14 +1223,6 @@ bool netplay_try_init_serialization(netplay_t *netplay) /* Once initialized, we no longer exhibit this quirk */ netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION); - /* Now we need a different packet buffer size because we actually know our - * state size */ - /* FIXME: Duplication */ - packet_buffer_size = netplay->state_size + netplay->stall_frames * - WORDS_PER_FRAME + (netplay->stall_frames+1)*3; - netplay_resize_socket_buffer(&netplay->send_packet_buffer, packet_buffer_size); - netplay_resize_socket_buffer(&netplay->recv_packet_buffer, packet_buffer_size); - return true; } @@ -1258,6 +1250,28 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay) return false; } +static bool netplay_init_socket_buffers(netplay_t *netplay) +{ + /* Make our packet buffer big enough for a save state and frames-many frames + * of input data, plus the headers for each of them */ + size_t packet_buffer_size = netplay->zbuffer_size + + netplay->delay_frames * WORDS_PER_FRAME + (netplay->delay_frames+1)*3; + + if (netplay->send_packet_buffer.data) + { + netplay_deinit_socket_buffer(&netplay->send_packet_buffer); + netplay_deinit_socket_buffer(&netplay->recv_packet_buffer); + netplay->send_packet_buffer.data = netplay->recv_packet_buffer.data = NULL; + } + + if (!netplay_init_socket_buffer(&netplay->send_packet_buffer, packet_buffer_size)) + return false; + if (!netplay_init_socket_buffer(&netplay->recv_packet_buffer, packet_buffer_size)) + return false; + + return true; +} + bool netplay_init_serialization(netplay_t *netplay) { unsigned i; @@ -1293,7 +1307,7 @@ bool netplay_init_serialization(netplay_t *netplay) return false; } - return true; + return netplay_init_socket_buffers(netplay); } static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) @@ -1318,14 +1332,8 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)) netplay_init_serialization(netplay); - /* Make our packet buffer big enough for a save state and frames-many frames - * of input data, plus the headers for each of them */ - packet_buffer_size = netplay->state_size + frames * WORDS_PER_FRAME + (frames+1)*3; - - if (!netplay_init_socket_buffer(&netplay->send_packet_buffer, packet_buffer_size)) - return false; - if (!netplay_init_socket_buffer(&netplay->recv_packet_buffer, packet_buffer_size)) - return false; + if (!netplay->send_packet_buffer.data) + netplay_init_socket_buffers(netplay); return true; } From ba76528b8fcf7d6dc6979ad0f29814a0a2b6b008 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 11:52:27 -0500 Subject: [PATCH 05/89] A more sophisticated status variable for later making the initial connection nonblocking. --- network/netplay/netplay.c | 19 ++++++++++--------- network/netplay/netplay_net.c | 10 +++++----- network/netplay/netplay_private.h | 19 +++++++++++++++---- network/netplay/netplay_spectate.c | 2 +- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 6a89327546..88e969b71e 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -281,7 +281,7 @@ static void hangup(netplay_t *netplay) { if (!netplay) return; - if (!netplay->has_connection) + if (netplay->status == RARCH_NETPLAY_CONNECTION_NONE) return; RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); @@ -300,7 +300,7 @@ static void hangup(netplay_t *netplay) } } - netplay->has_connection = false; + netplay->status = RARCH_NETPLAY_CONNECTION_NONE; /* Reset things that will behave oddly if we get a new connection */ netplay->remote_paused = false; @@ -328,7 +328,7 @@ static bool netplay_should_skip(netplay_t *netplay) { if (!netplay) return false; - return netplay->is_replay && netplay->has_connection; + return netplay->is_replay && (netplay->status == RARCH_NETPLAY_CONNECTION_PLAYING); } static bool netplay_can_poll(netplay_t *netplay) @@ -907,7 +907,7 @@ static bool netplay_poll(void) { int res; - if (!netplay_data->has_connection) + if (netplay_data->status == RARCH_NETPLAY_CONNECTION_NONE) return false; netplay_data->can_poll = false; @@ -1009,7 +1009,7 @@ static bool netplay_is_alive(void) { if (!netplay_data) return false; - return netplay_data->has_connection; + return (netplay_data->status == RARCH_NETPLAY_CONNECTION_PLAYING); } static bool netplay_flip_port(netplay_t *netplay, bool port) @@ -1588,7 +1588,7 @@ bool netplay_pre_frame(netplay_t *netplay) if (!netplay->net_cbs->pre_frame(netplay)) return false; - return (!netplay->has_connection || + return ((netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING) || (!netplay->stall && !netplay->remote_paused)); } @@ -1622,7 +1622,8 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) return; netplay->local_paused = paused; - if (netplay->has_connection && !netplay->spectate.enabled) + if (netplay->status != RARCH_NETPLAY_CONNECTION_NONE && + !netplay->spectate.enabled) { netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); @@ -1649,7 +1650,7 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t tmp_serial_info; uint32_t rd, wn; - if (!netplay->has_connection) + if (netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING) return; /* Record it in our own buffer */ @@ -1745,7 +1746,7 @@ void netplay_load_savestate(netplay_t *netplay, **/ bool netplay_disconnect(netplay_t *netplay) { - if (!netplay || !netplay->has_connection) + if (!netplay || (netplay->status == RARCH_NETPLAY_CONNECTION_NONE)) return true; hangup(netplay); return true; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 3847e2f798..0c3d21ba35 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -105,13 +105,13 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } /* If we can't transmit savestates, we must stall until the client is ready */ - if (!netplay->has_connection && + if (netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING && netplay->self_frame_count > 0 && (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) netplay->stall = RARCH_NETPLAY_STALL_NO_CONNECTION; } - if (netplay->is_server && !netplay->has_connection) + if (netplay->is_server && netplay->status == RARCH_NETPLAY_CONNECTION_NONE) { fd_set fds; struct timeval tmp_tv = {0}; @@ -163,7 +163,7 @@ static bool netplay_net_pre_frame(netplay_t *netplay) /* Establish the connection */ if (netplay_handshake(netplay)) { - netplay->has_connection = true; + netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; /* FIXME: Not the best place for this, needs to happen after initial * connection in get_info */ @@ -223,7 +223,7 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (!netplay->has_connection) + if (netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING) { netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; @@ -353,7 +353,7 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) { if (!netplay_handshake(netplay)) return false; - netplay->has_connection = true; + netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; } return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 9fa61a26a4..19491d9b1f 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -117,11 +117,20 @@ struct netplay_callbacks { bool (*info_cb) (netplay_t *netplay, unsigned frames); }; +enum rarch_netplay_connection_status +{ + RARCH_NETPLAY_CONNECTION_NONE = 0, + RARCH_NETPLAY_CONNECTION_INIT, /* Waiting for header */ + RARCH_NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ + RARCH_NETPLAY_CONNECTION_PRE_SRAM, /* Waiting for SRAM */ + RARCH_NETPLAY_CONNECTION_PLAYING /* Normal ready state */ +}; + enum rarch_netplay_stall_reasons { - RARCH_NETPLAY_STALL_NONE = 0, - RARCH_NETPLAY_STALL_RUNNING_FAST, - RARCH_NETPLAY_STALL_NO_CONNECTION + RARCH_NETPLAY_STALL_NONE = 0, + RARCH_NETPLAY_STALL_RUNNING_FAST, + RARCH_NETPLAY_STALL_NO_CONNECTION }; struct netplay @@ -130,6 +139,9 @@ struct netplay char other_nick[32]; struct sockaddr_storage other_addr; + /* Status of our connection */ + enum rarch_netplay_connection_status status; + struct retro_callbacks cbs; /* TCP connection for state sending, etc. Also used for commands */ int fd; @@ -140,7 +152,6 @@ struct netplay struct natt_status nat_traversal_state; /* Which port is governed by netplay (other user)? */ unsigned port; - bool has_connection; struct delta_frame *buffer; size_t buffer_size; diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index 81ab4ef21c..d5670cbc7f 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -285,7 +285,7 @@ static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) return false; } - netplay->has_connection = true; + netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; return true; } From 91e7db3ecab284fec5c232e3aaedaf7a64e63603 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 12:15:54 -0500 Subject: [PATCH 06/89] This semicolon broke everything! --- network/netplay/netplay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 88e969b71e..f22619c1c4 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -493,7 +493,7 @@ static bool netplay_get_cmd(netplay_t *netplay) cmd = ntohl(cmd); - RECV(&cmd_size, sizeof(cmd_size)); + RECV(&cmd_size, sizeof(cmd_size)) return false; cmd_size = ntohl(cmd_size); From 3f8f9761f6a2c2a17c094671b6760d47aef5ef24 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 13:32:12 -0500 Subject: [PATCH 07/89] Fixes particularly affecting delay_frames=0 mode --- network/netplay/netplay.c | 78 +++++++++++++++++------------------ network/netplay/netplay_buf.c | 4 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index f22619c1c4..28fed2a5e6 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -413,7 +413,9 @@ static bool get_self_input_state(netplay_t *netplay) { if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, netplay->input_packet_buffer, - sizeof(netplay->input_packet_buffer))) + sizeof(netplay->input_packet_buffer)) || + !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, + false)) { hangup(netplay); return false; @@ -446,7 +448,8 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, static bool netplay_cmd_nak(netplay_t *netplay) { - return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); + netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); + return false; } bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) @@ -470,7 +473,7 @@ static ssize_t netplay_recva(netplay_t *netplay, void *buf, size_t len) return netplay_recv(&netplay->recv_packet_buffer, netplay->fd, buf, len, false); } -static bool netplay_get_cmd(netplay_t *netplay) +static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) { uint32_t cmd; uint32_t flip_frame; @@ -777,6 +780,8 @@ static bool netplay_get_cmd(netplay_t *netplay) } netplay_recv_flush(&netplay->recv_packet_buffer); + if (had_input) + *had_input = true; } @@ -792,54 +797,49 @@ static int poll_input(netplay_t *netplay, bool block) { bool had_input = false; int max_fd = netplay->fd + 1; - struct timeval tv = {0}; - tv.tv_sec = 0; - tv.tv_usec = block ? (RETRY_MS * 1000) : 0; do { - fd_set fds; - /* select() does not take pointer to const struct timeval. - * Technically possible for select() to modify tmp_tv, so - * we go paranoia mode. */ - struct timeval tmp_tv = tv; had_input = false; netplay->timeout_cnt++; - FD_ZERO(&fds); - FD_SET(netplay->fd, &fds); - - if (socket_select(max_fd, &fds, NULL, NULL, &tmp_tv) < 0) - return -1; - - if (FD_ISSET(netplay->fd, &fds)) + /* If we're not ready for input, wait until we are. + * Could fill the TCP buffer, stalling the other side. */ + if (netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->read_ptr], + netplay->read_frame_count)) { - /* If we're not ready for input, wait until we are. - * Could fill the TCP buffer, stalling the other side. */ - if (netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->read_ptr], - netplay->read_frame_count)) + if (!netplay_get_cmd(netplay, &had_input)) + return -1; + } + + if (block) + { + /* If we were blocked for input, pass if we have this frame's input */ + if (netplay->read_frame_count > netplay->self_frame_count) + break; + + /* If we're supposed to block but we didn't have enough input, wait for it */ + if (!had_input) { - had_input = true; - if (!netplay_get_cmd(netplay)) + fd_set fds; + struct timeval tv = {0}; + tv.tv_usec = RETRY_MS * 1000; + + FD_ZERO(&fds); + FD_SET(netplay->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->self_frame_count, netplay->timeout_cnt, MAX_RETRIES); + + if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused) return -1; } } - - /* If we were blocked for input, pass if we have this frame's input */ - if (block && netplay->read_frame_count > netplay->self_frame_count) - break; - - /* If we had input, we might have more */ - if (had_input || !block) - continue; - - RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n", - netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES); - - if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused) - return -1; } while (had_input || block); return 0; diff --git a/network/netplay/netplay_buf.c b/network/netplay/netplay_buf.c index ae704070d8..8a69930bcc 100644 --- a/network/netplay/netplay_buf.c +++ b/network/netplay/netplay_buf.c @@ -211,7 +211,7 @@ ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t l ssize_t recvd; /* Receive whatever we can into the buffer */ - if (sbuf->end > sbuf->start) + if (sbuf->end >= sbuf->start) { error = false; recvd = socket_receive_all_nonblocking(sockfd, &error, @@ -243,7 +243,7 @@ ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t l } /* Now copy it into the reader */ - if (sbuf->end > sbuf->read || (sbuf->bufsz - sbuf->read) >= len) + if (sbuf->end >= sbuf->read || (sbuf->bufsz - sbuf->read) >= len) { size_t unread = buf_unread(sbuf); if (len <= unread) From 1c8f05623964e8d27ad544dcbd3a199e7ce78153 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 15:17:38 -0500 Subject: [PATCH 08/89] Nonblocking initial connection. Haven't tested with delay_frames=0 yet --- network/netplay/netplay.c | 26 ++- network/netplay/netplay.h | 67 +++--- network/netplay/netplay_common.c | 327 ++++++++++++++++++++++++++++++ network/netplay/netplay_net.c | 16 ++ network/netplay/netplay_private.h | 8 +- 5 files changed, 406 insertions(+), 38 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 28fed2a5e6..33fcadbe93 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -409,7 +409,8 @@ static bool get_self_input_state(netplay_t *netplay) netplay->input_packet_buffer[4] = htonl(state[1]); netplay->input_packet_buffer[5] = htonl(state[2]); - if (!netplay->spectate.enabled) /* Spectate sends in its own way */ + if (!netplay->spectate.enabled && /* Spectate sends in its own way */ + netplay->status == RARCH_NETPLAY_CONNECTION_PLAYING) { if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, netplay->input_packet_buffer, @@ -480,6 +481,22 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) uint32_t cmd_size; ssize_t recvd; + /* We don't handle the initial handshake here */ + switch (netplay->status) + { + case RARCH_NETPLAY_CONNECTION_NONE: + /* Huh?! */ + return false; + case RARCH_NETPLAY_CONNECTION_INIT: + return netplay_handshake_init(netplay, had_input); + case RARCH_NETPLAY_CONNECTION_PRE_NICK: + return netplay_handshake_pre_nick(netplay, had_input); + case RARCH_NETPLAY_CONNECTION_PRE_SRAM: + return netplay_handshake_pre_sram(netplay, had_input); + default: + break; + } + /* FIXME: This depends on delta_frame_ready */ #define RECV(buf, sz) \ @@ -487,10 +504,6 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) if (recvd >= 0 && recvd < (sz)) goto shrt; \ else if (recvd < 0) - /* Keep receiving commands until there's no input left */ - while (true) - { - RECV(&cmd, sizeof(cmd)) return false; @@ -782,8 +795,7 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) netplay_recv_flush(&netplay->recv_packet_buffer); if (had_input) *had_input = true; - - } + return true; shrt: /* No more data, reset and try again */ diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 69fce43df8..7e4d453f2f 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -56,50 +56,59 @@ enum netplay_cmd /* Failed acknowlegement response */ NETPLAY_CMD_NAK = 0x0001, - /* Input data */ - NETPLAY_CMD_INPUT = 0x0002, - - /* Misc. commands */ - - /* Swap inputs between player 1 and player 2 */ - NETPLAY_CMD_FLIP_PLAYERS = 0x0003, - - /* Toggle spectate/join mode */ - NETPLAY_CMD_SPECTATE = 0x0004, - /* Gracefully disconnects from host */ - NETPLAY_CMD_DISCONNECT = 0x0005, + NETPLAY_CMD_DISCONNECT = 0x0002, - /* Sends multiple config requests over, - * See enum netplay_cmd_cfg */ - NETPLAY_CMD_CFG = 0x0006, + /* Input data */ + NETPLAY_CMD_INPUT = 0x0003, - /* CMD_CFG streamlines sending multiple - configurations. This acknowledges - each one individually */ - NETPLAY_CMD_CFG_ACK = 0x0007, + /* Initialization commands */ + + /* Inform the other side of our nick (must be first command) */ + NETPLAY_CMD_NICK = 0x0020, + + /* Send SRAM data (must be second command from server) */ + NETPLAY_CMD_SRAM = 0x0021, + + /* Join spectator mode */ + NETPLAY_CMD_SPECTATE = 0x0022, + + /* Join play mode */ + NETPLAY_CMD_PLAY = 0x0023, /* Loading and synchronization */ /* Send the CRC hash of a frame's state */ - NETPLAY_CMD_CRC = 0x0010, + NETPLAY_CMD_CRC = 0x0040, /* Request a savestate */ - NETPLAY_CMD_REQUEST_SAVESTATE = 0x0011, + NETPLAY_CMD_REQUEST_SAVESTATE = 0x0041, /* Send a savestate for the client to load */ - NETPLAY_CMD_LOAD_SAVESTATE = 0x0012, - - /* Sends over cheats enabled on client */ - NETPLAY_CMD_CHEATS = 0x0013, - - /* Controlling game playback */ + NETPLAY_CMD_LOAD_SAVESTATE = 0x0042, /* Pauses the game, takes no arguments */ - NETPLAY_CMD_PAUSE = 0x0030, + NETPLAY_CMD_PAUSE = 0x0043, /* Resumes the game, takes no arguments */ - NETPLAY_CMD_RESUME = 0x0031 + NETPLAY_CMD_RESUME = 0x0044, + + /* Sends over cheats enabled on client (unsupported) */ + NETPLAY_CMD_CHEATS = 0x0045, + + /* Misc. commands */ + + /* Swap inputs between player 1 and player 2 */ + NETPLAY_CMD_FLIP_PLAYERS = 0x0060, + + /* Sends multiple config requests over, + * See enum netplay_cmd_cfg */ + NETPLAY_CMD_CFG = 0x0061, + + /* CMD_CFG streamlines sending multiple + configurations. This acknowledges + each one individually */ + NETPLAY_CMD_CFG_ACK = 0x0062 }; /* These are the configurations sent by NETPLAY_CMD_CFG. */ diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 73e1632886..cbc1f1cedb 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -158,6 +158,332 @@ static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb) return (pma & ebit) != (pmb & ebit); } +bool netplay_handshake_init_send(netplay_t *netplay) +{ + uint32_t *content_crc_ptr = NULL; + uint32_t header[4] = {0}; + + content_get_crc(&content_crc_ptr); + + header[0] = htonl(netplay_impl_magic()); + header[1] = htonl(*content_crc_ptr); + header[2] = htonl(netplay_platform_magic()); + header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); + + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, header, + sizeof(header)) || + !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) + return false; + + return true; +} + +struct nick_buf_s +{ + uint32_t cmd[2]; + char nick[32]; +}; + +#define RECV(buf, sz) \ + recvd = netplay_recv(&netplay->recv_packet_buffer, netplay->fd, (buf), (sz), false); \ + if (recvd >= 0 && recvd < (sz)) \ + { \ + netplay_recv_reset(&netplay->recv_packet_buffer); \ + return true; \ + } \ + else if (recvd < 0) + +bool netplay_handshake_init(netplay_t *netplay, bool *had_input) +{ + uint32_t header[4] = {0}; + ssize_t recvd; + char msg[512]; + struct nick_buf_s nick_buf; + uint32_t *content_crc_ptr = NULL; + uint32_t local_pmagic, remote_pmagic; + uint32_t compression; + + msg[0] = '\0'; + + RECV(header, sizeof(header)) + { + strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT), sizeof(msg)); + goto error; + } + + if (netplay_impl_magic() != ntohl(header[0])) + { + strlcpy(msg, "Implementations differ. Make sure you're using exact same " + "libretro implementations and RetroArch version.", sizeof(msg)); + goto error; + } + + content_get_crc(&content_crc_ptr); + if (*content_crc_ptr != ntohl(header[1])) + { + strlcpy(msg, msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER), sizeof(msg)); + goto error; + } + + /* We only care about platform magic if our core is quirky */ + local_pmagic = netplay_platform_magic(); + remote_pmagic = ntohl(header[2]); + if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) && + netplay_endian_mismatch(local_pmagic, remote_pmagic)) + { + RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n"); + strlcpy(msg, "This core does not support inter-architecture netplay " + "between these systems.", sizeof(msg)); + goto error; + } + if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) && + (local_pmagic != remote_pmagic)) + { + RARCH_ERR("Platform mismatch with a platform-sensitive core.\n"); + strlcpy(msg, "This core does not support inter-architecture netplay.", + sizeof(msg)); + goto error; + } + + /* Clear any existing compression */ + if (netplay->compression_stream) + netplay->compression_backend->stream_free(netplay->compression_stream); + if (netplay->decompression_stream) + netplay->decompression_backend->stream_free(netplay->decompression_stream); + + /* Check what compression is supported */ + compression = ntohl(header[3]); + compression &= NETPLAY_COMPRESSION_SUPPORTED; + if (compression & NETPLAY_COMPRESSION_ZLIB) + { + netplay->compression_backend = trans_stream_get_zlib_deflate_backend(); + if (!netplay->compression_backend) + netplay->compression_backend = trans_stream_get_pipe_backend(); + } + else + { + netplay->compression_backend = trans_stream_get_pipe_backend(); + } + netplay->decompression_backend = netplay->compression_backend->reverse; + + /* Allocate our compression stream */ + netplay->compression_stream = netplay->compression_backend->stream_new(); + netplay->decompression_stream = netplay->decompression_backend->stream_new(); + if (!netplay->compression_stream || !netplay->decompression_stream) + { + RARCH_ERR("Failed to allocate compression transcoder!\n"); + return false; + } + + /* 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(&netplay->send_packet_buffer, netplay->fd, &nick_buf, + sizeof(nick_buf)) || + !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) + return false; + + /* Move on to the next mode */ + netplay->status = RARCH_NETPLAY_CONNECTION_PRE_NICK; + *had_input = true; + netplay_recv_flush(&netplay->recv_packet_buffer); + return true; + +error: + if (msg[0]) + { + RARCH_ERR("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } + return false; +} + +static void netplay_handshake_ready(netplay_t *netplay) +{ + size_t i; + char msg[512]; + + /* Reset our frame count so it's consistent between server and client */ + netplay->self_frame_count = netplay->other_frame_count = 0; + netplay->read_frame_count = 1; + for (i = 0; i < netplay->buffer_size; i++) + { + netplay->buffer[i].used = false; + if (i == netplay->self_ptr) + { + netplay_delta_frame_ready(netplay, &netplay->buffer[i], 0); + netplay->buffer[i].have_remote = true; + netplay->other_ptr = i; + netplay->read_ptr = NEXT_PTR(i); + } + else + { + netplay->buffer[i].used = false; + } + } + + if (netplay->is_server) + { + netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick); + + /* Send them the savestate */ + if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) + { + netplay->force_send_savestate = true; + } + else + { + /* FIXME: Still correct? */ + /* Because the first frame isn't serialized, we're actually at + * frame 1 */ + netplay->self_ptr = NEXT_PTR(netplay->self_ptr); + netplay->self_frame_count = 1; + } + } + else + { + snprintf(msg, sizeof(msg), "%s: \"%s\"", + msg_hash_to_str(MSG_CONNECTED_TO), + netplay->other_nick); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } + + netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; +} + +bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) +{ + struct nick_buf_s nick_buf; + ssize_t recvd; + char msg[512]; + + msg[0] = '\0'; + + RECV(&nick_buf, sizeof(nick_buf)); + + /* Expecting only a nick command */ + if (recvd < 0 || + ntohl(nick_buf.cmd[0]) != NETPLAY_CMD_NICK || + ntohl(nick_buf.cmd[1]) != sizeof(nick_buf.nick)) + { + if (netplay->is_server) + strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT), + sizeof(msg)); + else + strlcpy(msg, + msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST), + sizeof(msg)); + RARCH_ERR("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + return false; + } + + strlcpy(netplay->other_nick, nick_buf.nick, + (sizeof(netplay->other_nick) < sizeof(nick_buf.nick)) ? + sizeof(netplay->other_nick) : sizeof(nick_buf.nick)); + + if (netplay->is_server) + { + /* If we're the server, now we send our SRAM */ + uint32_t cmd[2]; + retro_ctx_memory_info_t mem_info; + + mem_info.id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&mem_info); + + cmd[0] = htonl(NETPLAY_CMD_SRAM); + cmd[1] = htonl(mem_info.size); + + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, + sizeof(cmd))) + return false; + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, + mem_info.data, mem_info.size) || + !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, + false)) + return false; + + /* Now we're ready! */ + netplay_handshake_ready(netplay); + + } + else + { + /* Client needs to wait for SRAM */ + netplay->status = RARCH_NETPLAY_CONNECTION_PRE_SRAM; + + } + + *had_input = true; + netplay_recv_flush(&netplay->recv_packet_buffer); + return true; +} + +bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input) +{ + uint32_t cmd[2]; + uint32_t local_sram_size, remote_sram_size; + ssize_t recvd; + retro_ctx_memory_info_t mem_info; + + RECV(cmd, sizeof(cmd)) + return false; + + /* Only expecting an SRAM command */ + if (ntohl(cmd[0]) != NETPLAY_CMD_SRAM) + { + RARCH_ERR("%s\n", + msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); + return false; + } + + mem_info.id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&mem_info); + + local_sram_size = mem_info.size; + remote_sram_size = ntohl(cmd[1]); + + 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)); + 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)); + return false; + } + if (remote_sram_size > sizeof(uint32_t)) + remote_sram_size -= sizeof(uint32_t); + else + remote_sram_size = 0; + } + + } + + /* We're ready! */ + netplay_handshake_ready(netplay); + *had_input = true; + netplay_recv_flush(&netplay->recv_packet_buffer); + return true; +} + +#if 0 bool netplay_handshake(netplay_t *netplay) { size_t i; @@ -392,6 +718,7 @@ error: } return false; } +#endif bool netplay_is_server(netplay_t* netplay) { diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 0c3d21ba35..4acce6323e 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -160,6 +160,17 @@ static bool netplay_net_pre_frame(netplay_t *netplay) netplay_clear_socket_buffer(&netplay->send_packet_buffer); netplay_clear_socket_buffer(&netplay->recv_packet_buffer); + /* Set the socket nonblocking */ + if (!socket_nonblock(netplay->fd)) + { + /* Catastrophe! */ + return false; + } + + netplay_handshake_init_send(netplay); + netplay->status = RARCH_NETPLAY_CONNECTION_INIT; + +#if 0 /* Establish the connection */ if (netplay_handshake(netplay)) { @@ -201,6 +212,7 @@ static bool netplay_net_pre_frame(netplay_t *netplay) /* FIXME: Get in a state to accept another client */ } +#endif } } @@ -351,9 +363,13 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) { if (!netplay_is_server(netplay)) { +#if 0 if (!netplay_handshake(netplay)) return false; netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; +#endif + netplay_handshake_init_send(netplay); + netplay->status = RARCH_NETPLAY_CONNECTION_INIT; } return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 19491d9b1f..d3fbfe9344 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -39,7 +39,7 @@ #define RARCH_DEFAULT_PORT 55435 #define RARCH_DEFAULT_NICK "Anonymous" -#define NETPLAY_PROTOCOL_VERSION 3 +#define NETPLAY_PROTOCOL_VERSION 4 #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) @@ -269,7 +269,11 @@ bool netplay_get_nickname(netplay_t *netplay, int fd); bool netplay_send_nickname(netplay_t *netplay, int fd); -bool netplay_handshake(netplay_t *netplay); +/* Various netplay initialization modes: */ +bool netplay_handshake_init_send(netplay_t *netplay); +bool netplay_handshake_init(netplay_t *netplay, bool *had_input); +bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input); +bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input); uint32_t netplay_impl_magic(void); From 71e5cb756ae03f8a5c5a13dd77147ca813ae8cca Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 15:25:37 -0500 Subject: [PATCH 09/89] Support for delay_frames=0 in nonblocking mode. --- network/netplay/netplay_common.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index cbc1f1cedb..6c9294271f 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -333,14 +333,6 @@ static void netplay_handshake_ready(netplay_t *netplay) { netplay->force_send_savestate = true; } - else - { - /* FIXME: Still correct? */ - /* Because the first frame isn't serialized, we're actually at - * frame 1 */ - netplay->self_ptr = NEXT_PTR(netplay->self_ptr); - netplay->self_frame_count = 1; - } } else { @@ -351,6 +343,10 @@ static void netplay_handshake_ready(netplay_t *netplay) runloop_msg_queue_push(msg, 1, 180, false); } + /* Unstall if we were waiting for this */ + if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION) + netplay->stall = 0; + netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; } From ce7686104dff5e3e0448c4ce5d4610d4306cb7a4 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 15:30:16 -0500 Subject: [PATCH 10/89] Removing dead code --- network/netplay/netplay_common.c | 237 ------------------------------- network/netplay/netplay_net.c | 48 ------- 2 files changed, 285 deletions(-) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 6c9294271f..f01a0cf897 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -479,243 +479,6 @@ bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input) return true; } -#if 0 -bool netplay_handshake(netplay_t *netplay) -{ - size_t i; - uint32_t local_pmagic, remote_pmagic; - unsigned sram_size, remote_sram_size; - retro_ctx_memory_info_t mem_info; - char msg[512]; - uint32_t *content_crc_ptr = NULL; - void *sram = NULL; - uint32_t header[5] = {0}; - bool is_server = netplay->is_server; - int compression = 0; - - msg[0] = '\0'; - - mem_info.id = RETRO_MEMORY_SAVE_RAM; - - core_get_memory(&mem_info); - content_get_crc(&content_crc_ptr); - - local_pmagic = netplay_platform_magic(); - - header[0] = htonl(*content_crc_ptr); - header[1] = htonl(netplay_impl_magic()); - header[2] = htonl(mem_info.size); - header[3] = htonl(local_pmagic); - header[4] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - - if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false)) - return false; - - if (!socket_receive_all_blocking(netplay->fd, header, sizeof(header))) - { - strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT), sizeof(msg)); - goto error; - } - - if (*content_crc_ptr != ntohl(header[0])) - { - strlcpy(msg, msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER), sizeof(msg)); - goto error; - } - - if (netplay_impl_magic() != ntohl(header[1])) - { - strlcpy(msg, "Implementations differ. Make sure you're using exact same " - "libretro implementations and RetroArch version.", sizeof(msg)); - goto error; - } - - /* Some cores only report the correct sram size late, so we can't actually - * error out if the sram size seems wrong. */ - sram_size = mem_info.size; - remote_sram_size = ntohl(header[2]); - if (sram_size != 0 && remote_sram_size != 0 && sram_size != remote_sram_size) - { - RARCH_WARN("Content SRAM sizes do not correspond.\n"); - } - - /* We only care about platform magic if our core is quirky */ - remote_pmagic = ntohl(header[3]); - if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) && - netplay_endian_mismatch(local_pmagic, remote_pmagic)) - { - RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n"); - strlcpy(msg, "This core does not support inter-architecture netplay " - "between these systems.", sizeof(msg)); - goto error; - } - if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) && - (local_pmagic != remote_pmagic)) - { - RARCH_ERR("Platform mismatch with a platform-sensitive core.\n"); - strlcpy(msg, "This core does not support inter-architecture netplay.", - sizeof(msg)); - goto error; - } - - /* Clear any existing compression */ - if (netplay->compression_stream) - netplay->compression_backend->stream_free(netplay->compression_stream); - if (netplay->decompression_stream) - netplay->decompression_backend->stream_free(netplay->decompression_stream); - - /* Check what compression is supported */ - compression = ntohl(header[4]); - compression &= NETPLAY_COMPRESSION_SUPPORTED; - if (compression & NETPLAY_COMPRESSION_ZLIB) - { - netplay->compression_backend = trans_stream_get_zlib_deflate_backend(); - if (!netplay->compression_backend) - netplay->compression_backend = trans_stream_get_pipe_backend(); - } - else - { - netplay->compression_backend = trans_stream_get_pipe_backend(); - } - netplay->decompression_backend = netplay->compression_backend->reverse; - - /* Allocate our compression stream */ - netplay->compression_stream = netplay->compression_backend->stream_new(); - netplay->decompression_stream = netplay->decompression_backend->stream_new(); - if (!netplay->compression_stream || !netplay->decompression_stream) - { - RARCH_ERR("Failed to allocate compression transcoder!\n"); - return false; - } - - /* Client sends nickname first, server replies with nickname */ - if (!is_server) - { - if (!netplay_send_nickname(netplay, netplay->fd)) - { - strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_HOST), - sizeof(msg)); - goto error; - } - } - - if (!netplay_get_nickname(netplay, netplay->fd)) - { - if (is_server) - strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT), - sizeof(msg)); - else - strlcpy(msg, - msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST), - sizeof(msg)); - goto error; - } - - if (is_server) - { - if (!netplay_send_nickname(netplay, netplay->fd)) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT)); - return false; - } - } - - /* Server sends SRAM, client receives */ - if (is_server) - { - /* Send SRAM data to the client */ - sram = mem_info.data; - - if (!socket_send_all_blocking(netplay->fd, sram, sram_size, false)) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_SEND_SRAM_DATA_TO_CLIENT)); - return false; - } - - } - else - { - /* Get SRAM data from User 1. */ - if (sram_size != 0 && sram_size == remote_sram_size) - { - sram = mem_info.data; - - if (!socket_receive_all_blocking(netplay->fd, sram, sram_size)) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); - return false; - } - - } - else if (remote_sram_size != 0) - { - /* We can't load this, but we still need to get rid of the data */ - uint32_t quickbuf; - while (remote_sram_size > 0) - { - if (!socket_receive_all_blocking(netplay->fd, &quickbuf, (remote_sram_size > sizeof(uint32_t)) ? sizeof(uint32_t) : remote_sram_size)) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); - return false; - } - if (remote_sram_size > sizeof(uint32_t)) - remote_sram_size -= sizeof(uint32_t); - else - remote_sram_size = 0; - } - - } - - } - - /* Reset our frame count so it's consistent with the server */ - netplay->self_frame_count = netplay->other_frame_count = 0; - netplay->read_frame_count = 1; - for (i = 0; i < netplay->buffer_size; i++) - { - netplay->buffer[i].used = false; - if (i == netplay->self_ptr) - { - netplay_delta_frame_ready(netplay, &netplay->buffer[i], 0); - netplay->buffer[i].have_remote = true; - netplay->other_ptr = i; - netplay->read_ptr = NEXT_PTR(i); - } - else - { - netplay->buffer[i].used = false; - } - } - - if (is_server) - { - netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick); - } - else - { - snprintf(msg, sizeof(msg), "%s: \"%s\"", - msg_hash_to_str(MSG_CONNECTED_TO), - netplay->other_nick); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - } - - return true; - -error: - if (msg[0]) - { - RARCH_ERR("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - } - return false; -} -#endif - bool netplay_is_server(netplay_t* netplay) { if (!netplay) diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 4acce6323e..8fdd4a1b4e 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -170,49 +170,6 @@ static bool netplay_net_pre_frame(netplay_t *netplay) netplay_handshake_init_send(netplay); netplay->status = RARCH_NETPLAY_CONNECTION_INIT; -#if 0 - /* Establish the connection */ - if (netplay_handshake(netplay)) - { - netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; - - /* FIXME: Not the best place for this, needs to happen after initial - * connection in get_info */ - if (!socket_nonblock(netplay->fd)) - { - free(netplay); - return NULL; - } - - /* Send them the savestate */ - if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) - { - netplay->force_send_savestate = true; - } - else - { - /* Because the first frame isn't serialized, we're actually at - * frame 1 */ - netplay->self_ptr = NEXT_PTR(netplay->self_ptr); - netplay->self_frame_count = 1; - } - - /* And expect the current frame from the other side */ - netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; - netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; - - /* Unstall if we were waiting for this */ - if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION) - netplay->stall = 0; - - } - else - { - socket_close(netplay->fd); - /* FIXME: Get in a state to accept another client */ - - } -#endif } } @@ -363,11 +320,6 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) { if (!netplay_is_server(netplay)) { -#if 0 - if (!netplay_handshake(netplay)) - return false; - netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; -#endif netplay_handshake_init_send(netplay); netplay->status = RARCH_NETPLAY_CONNECTION_INIT; } From 9054b51f0ed743cd39013ab898585d8413f4496b Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 16:17:13 -0500 Subject: [PATCH 11/89] Finally, bringing back the first frame (in nonblocking mode) --- network/netplay/netplay.c | 71 ++++++++++++++------------------ network/netplay/netplay_common.c | 13 +++--- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 33fcadbe93..64680ce2f8 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -46,15 +46,6 @@ #define HAVE_INET6 1 #endif -enum -{ - CMD_OPT_ALLOWED_IN_SPECTATE_MODE = 0x1, - CMD_OPT_REQUIRE_ACK = 0x2, - CMD_OPT_HOST_ONLY = 0x4, - CMD_OPT_CLIENT_ONLY = 0x8, - CMD_OPT_REQUIRE_SYNC = 0x10 -}; - /* Only used before init_netplay */ static bool netplay_enabled = false; static bool netplay_is_client = false; @@ -338,6 +329,25 @@ static bool netplay_can_poll(netplay_t *netplay) return netplay->can_poll; } +/* Send the current input state, either immediately after receiving it or after + * finishing the initial handshake */ +static void send_input(netplay_t *netplay) +{ + if (!netplay->spectate.enabled && /* Spectate sends in its own way */ + netplay->status == RARCH_NETPLAY_CONNECTION_PLAYING) + { + netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, + netplay->input_packet_buffer, + sizeof(netplay->input_packet_buffer)) || + !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, + false)) + { + hangup(netplay); + } + } +} + /** * get_self_input_state: * @netplay : pointer to netplay object @@ -409,19 +419,7 @@ static bool get_self_input_state(netplay_t *netplay) netplay->input_packet_buffer[4] = htonl(state[1]); netplay->input_packet_buffer[5] = htonl(state[2]); - if (!netplay->spectate.enabled && /* Spectate sends in its own way */ - netplay->status == RARCH_NETPLAY_CONNECTION_PLAYING) - { - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, - netplay->input_packet_buffer, - sizeof(netplay->input_packet_buffer)) || - !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, - false)) - { - hangup(netplay); - return false; - } - } + send_input(netplay); memcpy(ptr->self_state, state, sizeof(state)); ptr->have_local = true; @@ -490,9 +488,17 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) case RARCH_NETPLAY_CONNECTION_INIT: return netplay_handshake_init(netplay, had_input); case RARCH_NETPLAY_CONNECTION_PRE_NICK: - return netplay_handshake_pre_nick(netplay, had_input); + { + bool ret = netplay_handshake_pre_nick(netplay, had_input); + send_input(netplay); + return ret; + } case RARCH_NETPLAY_CONNECTION_PRE_SRAM: - return netplay_handshake_pre_sram(netplay, had_input); + { + bool ret = netplay_handshake_pre_sram(netplay, had_input); + send_input(netplay); + return ret; + } default: break; } @@ -1430,7 +1436,6 @@ error: * @cmd : command to send * @data : data to send as argument * @sz : size of data - * @flags : flags of CMD_OPT_* * @command_str : name of action * @success_msg : message to display upon success * @@ -1438,29 +1443,14 @@ error: */ bool netplay_command(netplay_t* netplay, enum netplay_cmd cmd, void* data, size_t sz, - uint32_t flags, const char* command_str, const char* success_msg) { char m[256]; const char* msg = NULL; - bool allowed_spectate = !!(flags & CMD_OPT_ALLOWED_IN_SPECTATE_MODE); - bool host_only = !!(flags & CMD_OPT_HOST_ONLY); retro_assert(netplay); - if (netplay->spectate.enabled && !allowed_spectate) - { - msg = "Cannot %s in spectate mode."; - goto error; - } - - if (host_only && netplay->port == 0) - { - msg = "Cannot %s as a client."; - goto error; - } - if (!netplay_send_raw_cmd(netplay, cmd, data, sz)) goto error; @@ -1491,7 +1481,6 @@ static void netplay_flip_users(netplay_t *netplay) bool command = netplay_command( netplay, NETPLAY_CMD_FLIP_PLAYERS, &flip_frame_net, sizeof flip_frame_net, - CMD_OPT_HOST_ONLY | CMD_OPT_REQUIRE_SYNC, "flip users", "Successfully flipped users.\n"); if(command) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index f01a0cf897..603001f070 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -306,17 +306,16 @@ static void netplay_handshake_ready(netplay_t *netplay) char msg[512]; /* Reset our frame count so it's consistent between server and client */ - netplay->self_frame_count = netplay->other_frame_count = 0; - netplay->read_frame_count = 1; + netplay->self_frame_count = netplay->other_frame_count = netplay->read_frame_count = 0; for (i = 0; i < netplay->buffer_size; i++) { - netplay->buffer[i].used = false; if (i == netplay->self_ptr) { - netplay_delta_frame_ready(netplay, &netplay->buffer[i], 0); - netplay->buffer[i].have_remote = true; - netplay->other_ptr = i; - netplay->read_ptr = NEXT_PTR(i); + struct delta_frame *ptr = &netplay->buffer[i]; + if (!ptr->used) + netplay_delta_frame_ready(netplay, ptr, 0); + ptr->frame = 0; + netplay->other_ptr = netplay->read_ptr = i; } else { From b3092f6fde0d144bfa7aa62575276173285529d4 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 17:01:19 -0500 Subject: [PATCH 12/89] Moving things that shouldn't have been public into netplay_private.h --- network/netplay/netplay.h | 165 ------------------------ network/netplay/netplay_private.h | 203 +++++++++++++++++++++++++++--- 2 files changed, 184 insertions(+), 184 deletions(-) diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 7e4d453f2f..24abe4e615 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -46,89 +46,6 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_DISCONNECT }; -enum netplay_cmd -{ - /* Basic commands */ - - /* Acknowlegement response */ - NETPLAY_CMD_ACK = 0x0000, - - /* Failed acknowlegement response */ - NETPLAY_CMD_NAK = 0x0001, - - /* Gracefully disconnects from host */ - NETPLAY_CMD_DISCONNECT = 0x0002, - - /* Input data */ - NETPLAY_CMD_INPUT = 0x0003, - - /* Initialization commands */ - - /* Inform the other side of our nick (must be first command) */ - NETPLAY_CMD_NICK = 0x0020, - - /* Send SRAM data (must be second command from server) */ - NETPLAY_CMD_SRAM = 0x0021, - - /* Join spectator mode */ - NETPLAY_CMD_SPECTATE = 0x0022, - - /* Join play mode */ - NETPLAY_CMD_PLAY = 0x0023, - - /* Loading and synchronization */ - - /* Send the CRC hash of a frame's state */ - NETPLAY_CMD_CRC = 0x0040, - - /* Request a savestate */ - NETPLAY_CMD_REQUEST_SAVESTATE = 0x0041, - - /* Send a savestate for the client to load */ - NETPLAY_CMD_LOAD_SAVESTATE = 0x0042, - - /* Pauses the game, takes no arguments */ - NETPLAY_CMD_PAUSE = 0x0043, - - /* Resumes the game, takes no arguments */ - NETPLAY_CMD_RESUME = 0x0044, - - /* Sends over cheats enabled on client (unsupported) */ - NETPLAY_CMD_CHEATS = 0x0045, - - /* Misc. commands */ - - /* Swap inputs between player 1 and player 2 */ - NETPLAY_CMD_FLIP_PLAYERS = 0x0060, - - /* Sends multiple config requests over, - * See enum netplay_cmd_cfg */ - NETPLAY_CMD_CFG = 0x0061, - - /* CMD_CFG streamlines sending multiple - configurations. This acknowledges - each one individually */ - NETPLAY_CMD_CFG_ACK = 0x0062 -}; - -/* These are the configurations sent by NETPLAY_CMD_CFG. */ -enum netplay_cmd_cfg -{ - /* Nickname */ - NETPLAY_CFG_NICK = 0x0001, - - /* input.netplay_client_swap_input */ - NETPLAY_CFG_SWAP_INPUT = 0x0002, - - /* netplay.delay_frames */ - NETPLAY_CFG_DELAY_FRAMES = 0x0004, - - /* For more than 2 players */ - NETPLAY_CFG_PLAYER_SLOT = 0x0008 -}; - -void input_poll_net(void); - int16_t input_state_net(unsigned port, unsigned device, unsigned idx, unsigned id); @@ -139,88 +56,6 @@ void audio_sample_net(int16_t left, int16_t right); size_t audio_sample_batch_net(const int16_t *data, size_t frames); -/** - * netplay_new: - * @direct_host : Netplay host discovered from scanning. - * @server : IP address of server. - * @port : Port of server. - * @delay_frames : Amount of delay frames. - * @check_frames : Frequency with which to check CRCs. - * @cb : Libretro callbacks. - * @spectate : If true, enable spectator mode. - * @nat_traversal : If true, attempt NAT traversal. - * @nick : Nickname of user. - * @quirks : Netplay quirks. - * - * Creates a new netplay handle. A NULL host means we're - * hosting (user 1). - * - * Returns: new netplay handle. - **/ -netplay_t *netplay_new(void *direct_host, const char *server, - uint16_t port, unsigned delay_frames, unsigned check_frames, - const struct retro_callbacks *cb, bool spectate, bool nat_traversal, - const char *nick, uint64_t quirks); - -/** - * netplay_free: - * @netplay : pointer to netplay object - * - * Frees netplay handle. - **/ -void netplay_free(netplay_t *handle); - -/** - * 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 clear to emulate the frame, false (0) - * if we're stalled or paused - **/ -bool netplay_pre_frame(netplay_t *handle); - -/** - * netplay_post_frame: - * @netplay : pointer to netplay object - * - * Post-frame for Netplay. - * We check if we have new input and replay from recorded input. - * Call this after running retro_run(). - **/ -void netplay_post_frame(netplay_t *handle); - -/** - * netplay_frontend_paused - * @netplay : pointer to netplay object - * @paused : true if frontend is paused - * - * Inform Netplay of the frontend's pause state (paused or otherwise) - **/ -void netplay_frontend_paused(netplay_t *netplay, bool paused); - -/** - * netplay_load_savestate - * @netplay : pointer to netplay object - * @serial_info : the savestate being loaded, NULL means "load it yourself" - * @save : whether to save the provided serial_info into the frame buffer - * - * Inform Netplay of a savestate load and send it to the other side - **/ -void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save); - -/** - * netplay_disconnect - * @netplay : pointer to netplay object - * - * Disconnect netplay. - * - * Returns: true (1) if successful. At present, cannot fail. - **/ -bool netplay_disconnect(netplay_t *netplay); - /** * init_netplay * @is_spectate : true if running in spectate mode diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index d3fbfe9344..ac5bbf8779 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -78,6 +78,103 @@ #define NETPLAY_COMPRESSION_SUPPORTED 0 #endif +enum netplay_cmd +{ + /* Basic commands */ + + /* Acknowlegement response */ + NETPLAY_CMD_ACK = 0x0000, + + /* Failed acknowlegement response */ + NETPLAY_CMD_NAK = 0x0001, + + /* Gracefully disconnects from host */ + NETPLAY_CMD_DISCONNECT = 0x0002, + + /* Input data */ + NETPLAY_CMD_INPUT = 0x0003, + + /* Initialization commands */ + + /* Inform the other side of our nick (must be first command) */ + NETPLAY_CMD_NICK = 0x0020, + + /* Send SRAM data (must be second command from server) */ + NETPLAY_CMD_SRAM = 0x0021, + + /* Join spectator mode */ + NETPLAY_CMD_SPECTATE = 0x0022, + + /* Join play mode */ + NETPLAY_CMD_PLAY = 0x0023, + + /* Loading and synchronization */ + + /* Send the CRC hash of a frame's state */ + NETPLAY_CMD_CRC = 0x0040, + + /* Request a savestate */ + NETPLAY_CMD_REQUEST_SAVESTATE = 0x0041, + + /* Send a savestate for the client to load */ + NETPLAY_CMD_LOAD_SAVESTATE = 0x0042, + + /* Pauses the game, takes no arguments */ + NETPLAY_CMD_PAUSE = 0x0043, + + /* Resumes the game, takes no arguments */ + NETPLAY_CMD_RESUME = 0x0044, + + /* Sends over cheats enabled on client (unsupported) */ + NETPLAY_CMD_CHEATS = 0x0045, + + /* Misc. commands */ + + /* Swap inputs between player 1 and player 2 */ + NETPLAY_CMD_FLIP_PLAYERS = 0x0060, + + /* Sends multiple config requests over, + * See enum netplay_cmd_cfg */ + NETPLAY_CMD_CFG = 0x0061, + + /* CMD_CFG streamlines sending multiple + configurations. This acknowledges + each one individually */ + NETPLAY_CMD_CFG_ACK = 0x0062 +}; + +/* These are the configurations sent by NETPLAY_CMD_CFG. */ +enum netplay_cmd_cfg +{ + /* Nickname */ + NETPLAY_CFG_NICK = 0x0001, + + /* input.netplay_client_swap_input */ + NETPLAY_CFG_SWAP_INPUT = 0x0002, + + /* netplay.delay_frames */ + NETPLAY_CFG_DELAY_FRAMES = 0x0004, + + /* For more than 2 players */ + NETPLAY_CFG_PLAYER_SLOT = 0x0008 +}; + +enum rarch_netplay_connection_status +{ + RARCH_NETPLAY_CONNECTION_NONE = 0, + RARCH_NETPLAY_CONNECTION_INIT, /* Waiting for header */ + RARCH_NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ + RARCH_NETPLAY_CONNECTION_PRE_SRAM, /* Waiting for SRAM */ + RARCH_NETPLAY_CONNECTION_PLAYING /* Normal ready state */ +}; + +enum rarch_netplay_stall_reason +{ + RARCH_NETPLAY_STALL_NONE = 0, + RARCH_NETPLAY_STALL_RUNNING_FAST, + RARCH_NETPLAY_STALL_NO_CONNECTION +}; + struct delta_frame { bool used; /* a bit derpy, but this is how we know if the delta's been used at all */ @@ -117,22 +214,6 @@ struct netplay_callbacks { bool (*info_cb) (netplay_t *netplay, unsigned frames); }; -enum rarch_netplay_connection_status -{ - RARCH_NETPLAY_CONNECTION_NONE = 0, - RARCH_NETPLAY_CONNECTION_INIT, /* Waiting for header */ - RARCH_NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ - RARCH_NETPLAY_CONNECTION_PRE_SRAM, /* Waiting for SRAM */ - RARCH_NETPLAY_CONNECTION_PLAYING /* Normal ready state */ -}; - -enum rarch_netplay_stall_reasons -{ - RARCH_NETPLAY_STALL_NONE = 0, - RARCH_NETPLAY_STALL_RUNNING_FAST, - RARCH_NETPLAY_STALL_NO_CONNECTION -}; - struct netplay { char nick[32]; @@ -167,10 +248,10 @@ struct netplay size_t zbuffer_size; /* Pointer where we are now. */ - size_t self_ptr; + size_t self_ptr; /* Points to the last reliable state that self ever had. */ size_t other_ptr; - /* Pointer to where we are reading. + /* Pointer to where we are reading. * Generally, other_ptr <= read_ptr <= self_ptr. */ size_t read_ptr; /* A pointer used temporarily for replay. */ @@ -241,7 +322,7 @@ struct netplay /* And stalling */ uint32_t delay_frames; - int stall; + enum rarch_netplay_stall_reason stall; retro_time_t stall_time; /* Frequency with which to check CRCs */ @@ -250,6 +331,90 @@ struct netplay struct netplay_callbacks* net_cbs; }; +void input_poll_net(void); + +/** + * netplay_new: + * @direct_host : Netplay host discovered from scanning. + * @server : IP address of server. + * @port : Port of server. + * @delay_frames : Amount of delay frames. + * @check_frames : Frequency with which to check CRCs. + * @cb : Libretro callbacks. + * @spectate : If true, enable spectator mode. + * @nat_traversal : If true, attempt NAT traversal. + * @nick : Nickname of user. + * @quirks : Netplay quirks. + * + * Creates a new netplay handle. A NULL host means we're + * hosting (user 1). + * + * Returns: new netplay handle. + **/ +netplay_t *netplay_new(void *direct_host, const char *server, + uint16_t port, unsigned delay_frames, unsigned check_frames, + const struct retro_callbacks *cb, bool spectate, bool nat_traversal, + const char *nick, uint64_t quirks); + +/** + * netplay_free: + * @netplay : pointer to netplay object + * + * Frees netplay handle. + **/ +void netplay_free(netplay_t *handle); + +/** + * 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 clear to emulate the frame, false (0) + * if we're stalled or paused + **/ +bool netplay_pre_frame(netplay_t *handle); + +/** + * netplay_post_frame: + * @netplay : pointer to netplay object + * + * Post-frame for Netplay. + * We check if we have new input and replay from recorded input. + * Call this after running retro_run(). + **/ +void netplay_post_frame(netplay_t *handle); + +/** + * netplay_frontend_paused + * @netplay : pointer to netplay object + * @paused : true if frontend is paused + * + * Inform Netplay of the frontend's pause state (paused or otherwise) + **/ +void netplay_frontend_paused(netplay_t *netplay, bool paused); + +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded, NULL means "load it yourself" + * @save : whether to save the provided serial_info into the frame buffer + * + * Inform Netplay of a savestate load and send it to the other side + **/ +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save); + +/** + * netplay_disconnect + * @netplay : pointer to netplay object + * + * Disconnect netplay. + * + * Returns: true (1) if successful. At present, cannot fail. + **/ +bool netplay_disconnect(netplay_t *netplay); + struct netplay_callbacks* netplay_get_cbs_net(void); struct netplay_callbacks* netplay_get_cbs_spectate(void); From f89e54fcb70cc75117bf5e8c8258fb1999e1d068 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 17:22:50 -0500 Subject: [PATCH 13/89] Now that we don't have to start at frame 0, use the server frame count --- network/netplay/netplay.c | 4 +- network/netplay/netplay_common.c | 78 ++++++++++++++++++++++--------- network/netplay/netplay_private.h | 9 +++- 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 64680ce2f8..e0be90bae9 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -494,8 +494,10 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) return ret; } case RARCH_NETPLAY_CONNECTION_PRE_SRAM: + return netplay_handshake_pre_sram(netplay, had_input); + case RARCH_NETPLAY_CONNECTION_PRE_FRAME: { - bool ret = netplay_handshake_pre_sram(netplay, had_input); + bool ret = netplay_handshake_pre_frame(netplay, had_input); send_input(netplay); return ret; } diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 603001f070..83169dd6b3 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -305,24 +305,6 @@ static void netplay_handshake_ready(netplay_t *netplay) size_t i; char msg[512]; - /* Reset our frame count so it's consistent between server and client */ - netplay->self_frame_count = netplay->other_frame_count = netplay->read_frame_count = 0; - for (i = 0; i < netplay->buffer_size; i++) - { - if (i == netplay->self_ptr) - { - struct delta_frame *ptr = &netplay->buffer[i]; - if (!ptr->used) - netplay_delta_frame_ready(netplay, ptr, 0); - ptr->frame = 0; - netplay->other_ptr = netplay->read_ptr = i; - } - else - { - netplay->buffer[i].used = false; - } - } - if (netplay->is_server) { netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick); @@ -382,8 +364,8 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) if (netplay->is_server) { - /* If we're the server, now we send our SRAM */ - uint32_t cmd[2]; + /* If we're the server, now we send our SRAM and frame number */ + uint32_t cmd[3]; retro_ctx_memory_info_t mem_info; mem_info.id = RETRO_MEMORY_SAVE_RAM; @@ -393,10 +375,17 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) cmd[1] = htonl(mem_info.size); if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, - sizeof(cmd))) + 2*sizeof(uint32_t))) return false; if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, - mem_info.data, mem_info.size) || + mem_info.data, mem_info.size)) + return false; + + cmd[0] = htonl(NETPLAY_CMD_FRAME); + cmd[1] = htonl(sizeof(uint32_t)); + cmd[2] = htonl(netplay->self_frame_count); + if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, + sizeof(cmd)) || !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) return false; @@ -471,6 +460,51 @@ bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input) } + /* Finally, wait for the frame number */ + netplay->status = RARCH_NETPLAY_CONNECTION_PRE_FRAME; + *had_input = true; + netplay_recv_flush(&netplay->recv_packet_buffer); + return true; +} + +bool netplay_handshake_pre_frame(netplay_t *netplay, bool *had_input) +{ + uint32_t cmd[3]; + ssize_t recvd; + size_t i; + + RECV(cmd, sizeof(cmd)) + return false; + + /* Only expecting an frame command */ + if (ntohl(cmd[0]) != NETPLAY_CMD_FRAME || + ntohl(cmd[1]) != sizeof(uint32_t)) + { + /* FIXME: Correct error message */ + RARCH_ERR("%s\n", + msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); + return false; + } + + /* Reset our frame count so it's consistent between server and client */ + netplay->self_frame_count = netplay->other_frame_count = + netplay->read_frame_count = ntohl(cmd[2]); + for (i = 0; i < netplay->buffer_size; i++) + { + if (i == netplay->self_ptr) + { + struct delta_frame *ptr = &netplay->buffer[i]; + if (!ptr->used) + netplay_delta_frame_ready(netplay, ptr, 0); + ptr->frame = netplay->self_frame_count; + netplay->other_ptr = netplay->read_ptr = i; + } + else + { + netplay->buffer[i].used = false; + } + } + /* We're ready! */ netplay_handshake_ready(netplay); *had_input = true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index ac5bbf8779..b1012fb510 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -102,11 +102,14 @@ enum netplay_cmd /* Send SRAM data (must be second command from server) */ NETPLAY_CMD_SRAM = 0x0021, + /* Inform client of current frame number (must be third command from server) */ + NETPLAY_CMD_FRAME = 0x0022, + /* Join spectator mode */ - NETPLAY_CMD_SPECTATE = 0x0022, + NETPLAY_CMD_SPECTATE = 0x0023, /* Join play mode */ - NETPLAY_CMD_PLAY = 0x0023, + NETPLAY_CMD_PLAY = 0x0024, /* Loading and synchronization */ @@ -165,6 +168,7 @@ enum rarch_netplay_connection_status RARCH_NETPLAY_CONNECTION_INIT, /* Waiting for header */ RARCH_NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ RARCH_NETPLAY_CONNECTION_PRE_SRAM, /* Waiting for SRAM */ + RARCH_NETPLAY_CONNECTION_PRE_FRAME, /* Waiting for frame number */ RARCH_NETPLAY_CONNECTION_PLAYING /* Normal ready state */ }; @@ -439,6 +443,7 @@ bool netplay_handshake_init_send(netplay_t *netplay); bool netplay_handshake_init(netplay_t *netplay, bool *had_input); bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input); bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input); +bool netplay_handshake_pre_frame(netplay_t *netplay, bool *had_input); uint32_t netplay_impl_magic(void); From 80be19a7daf578ae78923481f4954776aaa0d4c4 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 18:53:57 -0500 Subject: [PATCH 14/89] Renames for readability, merging frame and sram into sync packet. --- network/netplay/netplay.c | 34 +++++---- network/netplay/netplay_common.c | 107 +++++++++++------------------ network/netplay/netplay_net.c | 10 +-- network/netplay/netplay_private.h | 38 +++++----- network/netplay/netplay_spectate.c | 2 +- 5 files changed, 85 insertions(+), 106 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index e0be90bae9..8e655eb630 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -272,7 +272,7 @@ static void hangup(netplay_t *netplay) { if (!netplay) return; - if (netplay->status == RARCH_NETPLAY_CONNECTION_NONE) + if (netplay->mode == NETPLAY_CONNECTION_NONE) return; RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); @@ -291,7 +291,7 @@ static void hangup(netplay_t *netplay) } } - netplay->status = RARCH_NETPLAY_CONNECTION_NONE; + netplay->mode = NETPLAY_CONNECTION_NONE; /* Reset things that will behave oddly if we get a new connection */ netplay->remote_paused = false; @@ -319,7 +319,7 @@ static bool netplay_should_skip(netplay_t *netplay) { if (!netplay) return false; - return netplay->is_replay && (netplay->status == RARCH_NETPLAY_CONNECTION_PLAYING); + return netplay->is_replay && (netplay->mode == NETPLAY_CONNECTION_PLAYING); } static bool netplay_can_poll(netplay_t *netplay) @@ -334,7 +334,7 @@ static bool netplay_can_poll(netplay_t *netplay) static void send_input(netplay_t *netplay) { if (!netplay->spectate.enabled && /* Spectate sends in its own way */ - netplay->status == RARCH_NETPLAY_CONNECTION_PLAYING) + netplay->mode == NETPLAY_CONNECTION_PLAYING) { netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, @@ -480,24 +480,22 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) ssize_t recvd; /* We don't handle the initial handshake here */ - switch (netplay->status) + switch (netplay->mode) { - case RARCH_NETPLAY_CONNECTION_NONE: + case NETPLAY_CONNECTION_NONE: /* Huh?! */ return false; - case RARCH_NETPLAY_CONNECTION_INIT: + case NETPLAY_CONNECTION_INIT: return netplay_handshake_init(netplay, had_input); - case RARCH_NETPLAY_CONNECTION_PRE_NICK: + case NETPLAY_CONNECTION_PRE_NICK: { bool ret = netplay_handshake_pre_nick(netplay, had_input); send_input(netplay); return ret; } - case RARCH_NETPLAY_CONNECTION_PRE_SRAM: - return netplay_handshake_pre_sram(netplay, had_input); - case RARCH_NETPLAY_CONNECTION_PRE_FRAME: + case NETPLAY_CONNECTION_PRE_SYNC: { - bool ret = netplay_handshake_pre_frame(netplay, had_input); + bool ret = netplay_handshake_pre_sync(netplay, had_input); send_input(netplay); return ret; } @@ -927,7 +925,7 @@ static bool netplay_poll(void) { int res; - if (netplay_data->status == RARCH_NETPLAY_CONNECTION_NONE) + if (netplay_data->mode == NETPLAY_CONNECTION_NONE) return false; netplay_data->can_poll = false; @@ -1029,7 +1027,7 @@ static bool netplay_is_alive(void) { if (!netplay_data) return false; - return (netplay_data->status == RARCH_NETPLAY_CONNECTION_PLAYING); + return (netplay_data->mode == NETPLAY_CONNECTION_PLAYING); } static bool netplay_flip_port(netplay_t *netplay, bool port) @@ -1591,7 +1589,7 @@ bool netplay_pre_frame(netplay_t *netplay) if (!netplay->net_cbs->pre_frame(netplay)) return false; - return ((netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING) || + return ((netplay->mode != NETPLAY_CONNECTION_PLAYING) || (!netplay->stall && !netplay->remote_paused)); } @@ -1625,7 +1623,7 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) return; netplay->local_paused = paused; - if (netplay->status != RARCH_NETPLAY_CONNECTION_NONE && + if (netplay->mode != NETPLAY_CONNECTION_NONE && !netplay->spectate.enabled) { netplay_send_raw_cmd(netplay, paused @@ -1653,7 +1651,7 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t tmp_serial_info; uint32_t rd, wn; - if (netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING) + if (netplay->mode != NETPLAY_CONNECTION_PLAYING) return; /* Record it in our own buffer */ @@ -1749,7 +1747,7 @@ void netplay_load_savestate(netplay_t *netplay, **/ bool netplay_disconnect(netplay_t *netplay) { - if (!netplay || (netplay->status == RARCH_NETPLAY_CONNECTION_NONE)) + if (!netplay || (netplay->mode == NETPLAY_CONNECTION_NONE)) return true; hangup(netplay); return true; diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 83169dd6b3..f9c7f05472 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -286,7 +286,7 @@ bool netplay_handshake_init(netplay_t *netplay, bool *had_input) return false; /* Move on to the next mode */ - netplay->status = RARCH_NETPLAY_CONNECTION_PRE_NICK; + netplay->mode = NETPLAY_CONNECTION_PRE_NICK; *had_input = true; netplay_recv_flush(&netplay->recv_packet_buffer); return true; @@ -328,7 +328,7 @@ static void netplay_handshake_ready(netplay_t *netplay) if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION) netplay->stall = 0; - netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; + netplay->mode = NETPLAY_CONNECTION_PLAYING; } bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) @@ -364,28 +364,22 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) if (netplay->is_server) { - /* If we're the server, now we send our SRAM and frame number */ + /* If we're the server, now we send sync info */ uint32_t cmd[3]; retro_ctx_memory_info_t mem_info; mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); - cmd[0] = htonl(NETPLAY_CMD_SRAM); - cmd[1] = htonl(mem_info.size); + cmd[0] = htonl(NETPLAY_CMD_SYNC); + cmd[1] = htonl(sizeof(uint32_t) + mem_info.size); + cmd[2] = htonl(netplay->self_frame_count); if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, - 2*sizeof(uint32_t))) + sizeof(uint32_t))) return false; if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, - mem_info.data, mem_info.size)) - return false; - - cmd[0] = htonl(NETPLAY_CMD_FRAME); - cmd[1] = htonl(sizeof(uint32_t)); - cmd[2] = htonl(netplay->self_frame_count); - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, - sizeof(cmd)) || + mem_info.data, mem_info.size) || !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) return false; @@ -396,8 +390,8 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) } else { - /* Client needs to wait for SRAM */ - netplay->status = RARCH_NETPLAY_CONNECTION_PRE_SRAM; + /* Client needs to wait for sync info */ + netplay->mode = NETPLAY_CONNECTION_PRE_SYNC; } @@ -406,29 +400,57 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) return true; } -bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input) +bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) { uint32_t cmd[2]; uint32_t local_sram_size, remote_sram_size; + uint32_t new_frame_count; + size_t i; ssize_t recvd; retro_ctx_memory_info_t mem_info; RECV(cmd, sizeof(cmd)) return false; - /* Only expecting an SRAM command */ - if (ntohl(cmd[0]) != NETPLAY_CMD_SRAM) + /* Only expecting a sync command */ + if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || + ntohl(cmd[1]) < sizeof(uint32_t)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); return false; } + /* Get the frame count */ + RECV(&new_frame_count, sizeof(new_frame_count)) + return false; + new_frame_count = ntohl(new_frame_count); + + /* Reset our frame buffer so it's consistent between server and client */ + netplay->self_frame_count = netplay->other_frame_count = + netplay->read_frame_count = new_frame_count; + for (i = 0; i < netplay->buffer_size; i++) + { + if (i == netplay->self_ptr) + { + struct delta_frame *ptr = &netplay->buffer[i]; + if (!ptr->used) + netplay_delta_frame_ready(netplay, ptr, 0); + ptr->frame = new_frame_count; + netplay->other_ptr = netplay->read_ptr = i; + } + else + { + netplay->buffer[i].used = false; + } + } + + /* Now check the SRAM */ mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); local_sram_size = mem_info.size; - remote_sram_size = ntohl(cmd[1]); + remote_sram_size = ntohl(cmd[1]) - sizeof(uint32_t); if (local_sram_size != 0 && local_sram_size == remote_sram_size) { @@ -460,51 +482,6 @@ bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input) } - /* Finally, wait for the frame number */ - netplay->status = RARCH_NETPLAY_CONNECTION_PRE_FRAME; - *had_input = true; - netplay_recv_flush(&netplay->recv_packet_buffer); - return true; -} - -bool netplay_handshake_pre_frame(netplay_t *netplay, bool *had_input) -{ - uint32_t cmd[3]; - ssize_t recvd; - size_t i; - - RECV(cmd, sizeof(cmd)) - return false; - - /* Only expecting an frame command */ - if (ntohl(cmd[0]) != NETPLAY_CMD_FRAME || - ntohl(cmd[1]) != sizeof(uint32_t)) - { - /* FIXME: Correct error message */ - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); - return false; - } - - /* Reset our frame count so it's consistent between server and client */ - netplay->self_frame_count = netplay->other_frame_count = - netplay->read_frame_count = ntohl(cmd[2]); - for (i = 0; i < netplay->buffer_size; i++) - { - if (i == netplay->self_ptr) - { - struct delta_frame *ptr = &netplay->buffer[i]; - if (!ptr->used) - netplay_delta_frame_ready(netplay, ptr, 0); - ptr->frame = netplay->self_frame_count; - netplay->other_ptr = netplay->read_ptr = i; - } - else - { - netplay->buffer[i].used = false; - } - } - /* We're ready! */ netplay_handshake_ready(netplay); *had_input = true; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 8fdd4a1b4e..3fafe87797 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -105,13 +105,13 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } /* If we can't transmit savestates, we must stall until the client is ready */ - if (netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING && + if (netplay->mode != NETPLAY_CONNECTION_PLAYING && netplay->self_frame_count > 0 && (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) netplay->stall = RARCH_NETPLAY_STALL_NO_CONNECTION; } - if (netplay->is_server && netplay->status == RARCH_NETPLAY_CONNECTION_NONE) + if (netplay->is_server && netplay->mode == NETPLAY_CONNECTION_NONE) { fd_set fds; struct timeval tmp_tv = {0}; @@ -168,7 +168,7 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } netplay_handshake_init_send(netplay); - netplay->status = RARCH_NETPLAY_CONNECTION_INIT; + netplay->mode = NETPLAY_CONNECTION_INIT; } } @@ -192,7 +192,7 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (netplay->status != RARCH_NETPLAY_CONNECTION_PLAYING) + if (netplay->mode != NETPLAY_CONNECTION_PLAYING) { netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; @@ -321,7 +321,7 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) if (!netplay_is_server(netplay)) { netplay_handshake_init_send(netplay); - netplay->status = RARCH_NETPLAY_CONNECTION_INIT; + netplay->mode = NETPLAY_CONNECTION_INIT; } return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index b1012fb510..1c3a60ab35 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -99,17 +99,17 @@ enum netplay_cmd /* Inform the other side of our nick (must be first command) */ NETPLAY_CMD_NICK = 0x0020, - /* Send SRAM data (must be second command from server) */ - NETPLAY_CMD_SRAM = 0x0021, - - /* Inform client of current frame number (must be third command from server) */ - NETPLAY_CMD_FRAME = 0x0022, + /* Initial synchronization info (frame, sram, player info) */ + NETPLAY_CMD_SYNC = 0x0021, /* Join spectator mode */ - NETPLAY_CMD_SPECTATE = 0x0023, + NETPLAY_CMD_SPECTATE = 0x0022, /* Join play mode */ - NETPLAY_CMD_PLAY = 0x0024, + NETPLAY_CMD_PLAY = 0x0023, + + /* Report player mode */ + NETPLAY_CMD_MODE = 0x0024, /* Loading and synchronization */ @@ -162,14 +162,19 @@ enum netplay_cmd_cfg NETPLAY_CFG_PLAYER_SLOT = 0x0008 }; -enum rarch_netplay_connection_status +enum rarch_netplay_connection_mode { - RARCH_NETPLAY_CONNECTION_NONE = 0, - RARCH_NETPLAY_CONNECTION_INIT, /* Waiting for header */ - RARCH_NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ - RARCH_NETPLAY_CONNECTION_PRE_SRAM, /* Waiting for SRAM */ - RARCH_NETPLAY_CONNECTION_PRE_FRAME, /* Waiting for frame number */ - RARCH_NETPLAY_CONNECTION_PLAYING /* Normal ready state */ + NETPLAY_CONNECTION_NONE = 0, + + /* Initialization: */ + NETPLAY_CONNECTION_INIT, /* Waiting for header */ + NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ + NETPLAY_CONNECTION_PRE_SYNC, /* Waiting for sync */ + + /* Ready: */ + NETPLAY_CONNECTION_CONNECTED, /* Modes above this are connected */ + NETPLAY_CONNECTION_SPECTATING, /* Spectator mode */ + NETPLAY_CONNECTION_PLAYING /* Normal ready state */ }; enum rarch_netplay_stall_reason @@ -225,7 +230,7 @@ struct netplay struct sockaddr_storage other_addr; /* Status of our connection */ - enum rarch_netplay_connection_status status; + enum rarch_netplay_connection_mode mode; struct retro_callbacks cbs; /* TCP connection for state sending, etc. Also used for commands */ @@ -442,8 +447,7 @@ bool netplay_send_nickname(netplay_t *netplay, int fd); bool netplay_handshake_init_send(netplay_t *netplay); bool netplay_handshake_init(netplay_t *netplay, bool *had_input); bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input); -bool netplay_handshake_pre_sram(netplay_t *netplay, bool *had_input); -bool netplay_handshake_pre_frame(netplay_t *netplay, bool *had_input); +bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input); uint32_t netplay_impl_magic(void); diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index d5670cbc7f..f53fc86f12 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -285,7 +285,7 @@ static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) return false; } - netplay->status = RARCH_NETPLAY_CONNECTION_PLAYING; + netplay->mode = NETPLAY_CONNECTION_PLAYING; return true; } From 170b590bdb4dc6151742058b762a1fa405af71bf Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 20:20:16 -0500 Subject: [PATCH 15/89] Client now starts one frame after finishing connection, for easier sync. --- network/netplay/netplay_common.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index f9c7f05472..2804610fe0 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -376,7 +376,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) cmd[2] = htonl(netplay->self_frame_count); if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, - sizeof(uint32_t))) + sizeof(cmd))) return false; if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, mem_info.data, mem_info.size) || @@ -384,6 +384,12 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) false)) return false; + /* They start one frame after us */ + netplay->other_frame_count = netplay->read_frame_count = + netplay->self_frame_count + 1; + netplay->other_ptr = netplay->read_ptr = + NEXT_PTR(netplay->self_ptr); + /* Now we're ready! */ netplay_handshake_ready(netplay); @@ -431,17 +437,17 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) netplay->read_frame_count = new_frame_count; for (i = 0; i < netplay->buffer_size; i++) { + struct delta_frame *ptr = &netplay->buffer[i]; + ptr->used = false; + if (i == netplay->self_ptr) { - struct delta_frame *ptr = &netplay->buffer[i]; - if (!ptr->used) - netplay_delta_frame_ready(netplay, ptr, 0); + /* Clear out any current data but still use this frame */ + netplay_delta_frame_ready(netplay, ptr, 0); ptr->frame = new_frame_count; + ptr->have_local = true; netplay->other_ptr = netplay->read_ptr = i; - } - else - { - netplay->buffer[i].used = false; + } } From 775584cdacb4a2f9a04534fcae1b642536075582 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 22:08:45 -0500 Subject: [PATCH 16/89] Adding more README info. --- network/netplay/README | 64 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index e5a5384163..b58de284c0 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -100,6 +100,11 @@ Description: Negative Acknowledgement. If received, the connection is terminated. Sent whenever a command is malformed or otherwise not understood. +Command: DISCONNECT +Payload: None +Description: + Gracefully disconnect. Not used. + Command: INPUT Payload: { @@ -107,24 +112,50 @@ Payload: joypad input: uint32 analog 1 input: uint32 analog 2 input: uint32 - OPTIONAL state CRC: uint32 } Description: Input state for each frame. Netplay must send an INPUT command for every frame in order to function at all. -Command: FLIP_PLAYERS +Command: NICK +Payload: + { + nickname: char[32] + } +Description: + Send nickname. Mandatory handshake command. + +Command: SYNC +Payload: + { + connection number: uint32 + frame number: uint32 + sram: variable + } +Description: + Initial state synchronization. Mandatory handshake command from server to + client only. Sent after receiving client's NICK. + +Command: SPECTATE +Payload: None +Description: + Request to enter spectate mode. + +Command: PLAY +Payload: None +Description: + Request to enter player mode. + +Command: MODE Payload: { frame number: uint32 + connection number: uint32 + player number: uint32 (MAX for spectator mode) } Description: - Flip players at the requested frame. - -Command: DISCONNECT -Payload: None -Description: - Gracefully disconnect. Not used. + Inform of a connection mode change (possibly the connection of the + receiving client). Only server-to-client. Command: CRC Payload: @@ -161,3 +192,20 @@ Payload: None Command: RESUME Payload: None Indicates that the core is no longer paused. + +Command: CHEATS +Unused + +Command: FLIP_PLAYERS +Payload: + { + frame number: uint32 + } +Description: + Flip players at the requested frame. + +Command: CFG +Unused + +Command: CFG_ACK +Unused From 3908e258953d5c1244b2f1c65127331083342b8f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 3 Dec 2016 23:08:31 -0500 Subject: [PATCH 17/89] Separating local mode from remote mode. --- network/netplay/netplay.c | 30 +++++++++++++++++++----------- network/netplay/netplay_common.c | 21 ++++++++++++++------- network/netplay/netplay_net.c | 10 +++++----- network/netplay/netplay_private.h | 5 ++++- network/netplay/netplay_spectate.c | 2 +- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 8e655eb630..f5a438e874 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -272,7 +272,7 @@ static void hangup(netplay_t *netplay) { if (!netplay) return; - if (netplay->mode == NETPLAY_CONNECTION_NONE) + if (netplay->remote_mode == NETPLAY_CONNECTION_NONE) return; RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); @@ -290,8 +290,12 @@ static void hangup(netplay_t *netplay) runloop_msg_queue_push("Failed to reinitialize Netplay.", 0, 480, false); } } + else + { + netplay->self_mode = NETPLAY_CONNECTION_NONE; + } - netplay->mode = NETPLAY_CONNECTION_NONE; + netplay->remote_mode = NETPLAY_CONNECTION_NONE; /* Reset things that will behave oddly if we get a new connection */ netplay->remote_paused = false; @@ -319,7 +323,7 @@ static bool netplay_should_skip(netplay_t *netplay) { if (!netplay) return false; - return netplay->is_replay && (netplay->mode == NETPLAY_CONNECTION_PLAYING); + return netplay->is_replay && (netplay->self_mode == NETPLAY_CONNECTION_PLAYING); } static bool netplay_can_poll(netplay_t *netplay) @@ -334,7 +338,7 @@ static bool netplay_can_poll(netplay_t *netplay) static void send_input(netplay_t *netplay) { if (!netplay->spectate.enabled && /* Spectate sends in its own way */ - netplay->mode == NETPLAY_CONNECTION_PLAYING) + netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, @@ -480,7 +484,7 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) ssize_t recvd; /* We don't handle the initial handshake here */ - switch (netplay->mode) + switch (netplay->self_mode) { case NETPLAY_CONNECTION_NONE: /* Huh?! */ @@ -925,7 +929,7 @@ static bool netplay_poll(void) { int res; - if (netplay_data->mode == NETPLAY_CONNECTION_NONE) + if (netplay_data->remote_mode == NETPLAY_CONNECTION_NONE) return false; netplay_data->can_poll = false; @@ -1027,7 +1031,7 @@ static bool netplay_is_alive(void) { if (!netplay_data) return false; - return (netplay_data->mode == NETPLAY_CONNECTION_PLAYING); + return (netplay_data->remote_mode == NETPLAY_CONNECTION_PLAYING); } static bool netplay_flip_port(netplay_t *netplay, bool port) @@ -1393,6 +1397,10 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->delay_frames = delay_frames; netplay->check_frames = check_frames; netplay->quirks = quirks; + netplay->remote_mode = NETPLAY_CONNECTION_NONE; + netplay->self_mode = netplay->is_server ? + NETPLAY_CONNECTION_PLAYING : + NETPLAY_CONNECTION_NONE; strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); @@ -1589,7 +1597,7 @@ bool netplay_pre_frame(netplay_t *netplay) if (!netplay->net_cbs->pre_frame(netplay)) return false; - return ((netplay->mode != NETPLAY_CONNECTION_PLAYING) || + return ((netplay->remote_mode != NETPLAY_CONNECTION_PLAYING) || (!netplay->stall && !netplay->remote_paused)); } @@ -1623,7 +1631,7 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) return; netplay->local_paused = paused; - if (netplay->mode != NETPLAY_CONNECTION_NONE && + if (netplay->remote_mode != NETPLAY_CONNECTION_NONE && !netplay->spectate.enabled) { netplay_send_raw_cmd(netplay, paused @@ -1651,7 +1659,7 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t tmp_serial_info; uint32_t rd, wn; - if (netplay->mode != NETPLAY_CONNECTION_PLAYING) + if (netplay->remote_mode != NETPLAY_CONNECTION_PLAYING) return; /* Record it in our own buffer */ @@ -1747,7 +1755,7 @@ void netplay_load_savestate(netplay_t *netplay, **/ bool netplay_disconnect(netplay_t *netplay) { - if (!netplay || (netplay->mode == NETPLAY_CONNECTION_NONE)) + if (!netplay || (netplay->remote_mode == NETPLAY_CONNECTION_NONE)) return true; hangup(netplay); return true; diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 2804610fe0..f745ab6f2b 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -286,7 +286,7 @@ bool netplay_handshake_init(netplay_t *netplay, bool *had_input) return false; /* Move on to the next mode */ - netplay->mode = NETPLAY_CONNECTION_PRE_NICK; + netplay->self_mode = NETPLAY_CONNECTION_PRE_NICK; *had_input = true; netplay_recv_flush(&netplay->recv_packet_buffer); return true; @@ -328,7 +328,7 @@ static void netplay_handshake_ready(netplay_t *netplay) if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION) netplay->stall = 0; - netplay->mode = NETPLAY_CONNECTION_PLAYING; + netplay->remote_mode = NETPLAY_CONNECTION_PLAYING; } bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) @@ -365,7 +365,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) if (netplay->is_server) { /* If we're the server, now we send sync info */ - uint32_t cmd[3]; + uint32_t cmd[4]; retro_ctx_memory_info_t mem_info; mem_info.id = RETRO_MEMORY_SAVE_RAM; @@ -374,6 +374,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) cmd[0] = htonl(NETPLAY_CMD_SYNC); cmd[1] = htonl(sizeof(uint32_t) + mem_info.size); cmd[2] = htonl(netplay->self_frame_count); + cmd[3] = htonl(1); if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, sizeof(cmd))) @@ -397,7 +398,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) else { /* Client needs to wait for sync info */ - netplay->mode = NETPLAY_CONNECTION_PRE_SYNC; + netplay->self_mode = NETPLAY_CONNECTION_PRE_SYNC; } @@ -410,7 +411,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) { uint32_t cmd[2]; uint32_t local_sram_size, remote_sram_size; - uint32_t new_frame_count; + uint32_t new_frame_count, self_connection_num; size_t i; ssize_t recvd; retro_ctx_memory_info_t mem_info; @@ -420,7 +421,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) /* Only expecting a sync command */ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || - ntohl(cmd[1]) < sizeof(uint32_t)) + ntohl(cmd[1]) < 2*sizeof(uint32_t)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); @@ -432,6 +433,11 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) return false; new_frame_count = ntohl(new_frame_count); + /* And the connection number */ + RECV(&self_connection_num, sizeof(self_connection_num)) + return false; + netplay->self_connection_num = ntohl(self_connection_num); + /* Reset our frame buffer so it's consistent between server and client */ netplay->self_frame_count = netplay->other_frame_count = netplay->read_frame_count = new_frame_count; @@ -456,7 +462,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) core_get_memory(&mem_info); local_sram_size = mem_info.size; - remote_sram_size = ntohl(cmd[1]) - sizeof(uint32_t); + remote_sram_size = ntohl(cmd[1]) - 2*sizeof(uint32_t); if (local_sram_size != 0 && local_sram_size == remote_sram_size) { @@ -489,6 +495,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) } /* We're ready! */ + netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay_handshake_ready(netplay); *had_input = true; netplay_recv_flush(&netplay->recv_packet_buffer); diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 3fafe87797..66505f5ccf 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -105,13 +105,13 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } /* If we can't transmit savestates, we must stall until the client is ready */ - if (netplay->mode != NETPLAY_CONNECTION_PLAYING && + if (netplay->remote_mode != NETPLAY_CONNECTION_PLAYING && netplay->self_frame_count > 0 && (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) netplay->stall = RARCH_NETPLAY_STALL_NO_CONNECTION; } - if (netplay->is_server && netplay->mode == NETPLAY_CONNECTION_NONE) + if (netplay->is_server && netplay->remote_mode == NETPLAY_CONNECTION_NONE) { fd_set fds; struct timeval tmp_tv = {0}; @@ -168,7 +168,7 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } netplay_handshake_init_send(netplay); - netplay->mode = NETPLAY_CONNECTION_INIT; + netplay->remote_mode = NETPLAY_CONNECTION_INIT; } } @@ -192,7 +192,7 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (netplay->mode != NETPLAY_CONNECTION_PLAYING) + if (netplay->remote_mode != NETPLAY_CONNECTION_PLAYING) { netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; @@ -321,7 +321,7 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) if (!netplay_is_server(netplay)) { netplay_handshake_init_send(netplay); - netplay->mode = NETPLAY_CONNECTION_INIT; + netplay->remote_mode = netplay->self_mode = NETPLAY_CONNECTION_INIT; } return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 1c3a60ab35..288f853d8c 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -229,8 +229,11 @@ struct netplay char other_nick[32]; struct sockaddr_storage other_addr; + /* Our connection number */ + uint32_t self_connection_num; + /* Status of our connection */ - enum rarch_netplay_connection_mode mode; + enum rarch_netplay_connection_mode remote_mode, self_mode; struct retro_callbacks cbs; /* TCP connection for state sending, etc. Also used for commands */ diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index f53fc86f12..4b7c452acf 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -285,7 +285,7 @@ static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) return false; } - netplay->mode = NETPLAY_CONNECTION_PLAYING; + netplay->remote_mode = netplay->self_mode = NETPLAY_CONNECTION_PLAYING; return true; } From 1e1abf6951f5f8c4fd02ee67a99d56e0cc8c7c40 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 5 Dec 2016 00:04:01 -0500 Subject: [PATCH 18/89] First step of multiple connections. Still only one connection actually works. --- network/netplay/netplay.c | 345 ++++++++++++++++++----------- network/netplay/netplay_common.c | 39 ++-- network/netplay/netplay_net.c | 96 +++++--- network/netplay/netplay_private.h | 35 ++- network/netplay/netplay_spectate.c | 14 +- 5 files changed, 344 insertions(+), 185 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index f5a438e874..62d8721485 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -212,7 +212,10 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, if (fd >= 0) { ret = true; - netplay->fd = fd; + if (direct_host || server) + netplay->connections[0].fd = fd; + else + netplay->listen_fd = fd; break; } @@ -268,34 +271,37 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve * * Disconnects an active Netplay connection due to an error **/ -static void hangup(netplay_t *netplay) +static void hangup(netplay_t *netplay, struct netplay_connection *connection) { if (!netplay) return; - if (netplay->remote_mode == NETPLAY_CONNECTION_NONE) + if (!connection->active) return; RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); runloop_msg_queue_push("Netplay has disconnected. Will continue without connection.", 0, 480, false); - socket_close(netplay->fd); - netplay->fd = -1; + socket_close(connection->fd); + connection->active = false; - if (netplay->is_server && !netplay->spectate.enabled) + if (!netplay->is_server) + netplay->self_mode = NETPLAY_CONNECTION_NONE; + + /* Check if we have any more players */ + if (netplay->have_player_connections && connection->mode == NETPLAY_CONNECTION_PLAYING) { - /* In server mode, make the socket listen for a new connection */ - if (!init_socket(netplay, NULL, NULL, netplay->tcp_port)) + size_t i; + netplay->have_player_connections = false; + for (i = 0; i < netplay->connections_size; i++) { - RARCH_WARN("Failed to reinitialize Netplay.\n"); - runloop_msg_queue_push("Failed to reinitialize Netplay.", 0, 480, false); + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->mode == NETPLAY_CONNECTION_PLAYING) + { + netplay->have_player_connections = true; + break; + } } } - else - { - netplay->self_mode = NETPLAY_CONNECTION_NONE; - } - - netplay->remote_mode = NETPLAY_CONNECTION_NONE; /* Reset things that will behave oddly if we get a new connection */ netplay->remote_paused = false; @@ -335,19 +341,20 @@ static bool netplay_can_poll(netplay_t *netplay) /* Send the current input state, either immediately after receiving it or after * finishing the initial handshake */ -static void send_input(netplay_t *netplay) +static void send_input(netplay_t *netplay, struct netplay_connection *connection) { if (!netplay->spectate.enabled && /* Spectate sends in its own way */ - netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + netplay->self_mode == NETPLAY_CONNECTION_PLAYING && + connection->mode >= NETPLAY_CONNECTION_CONNECTED) { netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, netplay->input_packet_buffer, sizeof(netplay->input_packet_buffer)) || - !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, + !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, false)) { - hangup(netplay); + hangup(netplay, connection); } } } @@ -364,6 +371,7 @@ static bool get_self_input_state(netplay_t *netplay) { uint32_t state[WORDS_PER_FRAME - 1] = {0, 0, 0}; struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; + size_t i; if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) return false; @@ -376,7 +384,6 @@ static bool get_self_input_state(netplay_t *netplay) if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) { - unsigned i; settings_t *settings = config_get_ptr(); /* First frame we always give zero input since relying on @@ -423,60 +430,76 @@ static bool get_self_input_state(netplay_t *netplay) netplay->input_packet_buffer[4] = htonl(state[1]); netplay->input_packet_buffer[5] = htonl(state[2]); - send_input(netplay); + for (i = 0; i < netplay->connections_size; i++) + { + if (netplay->connections[i].active) + send_input(netplay, &netplay->connections[i]); + } memcpy(ptr->self_state, state, sizeof(state)); ptr->have_local = true; return true; } -static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, - const void *data, size_t size) +static 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(&netplay->send_packet_buffer, netplay->fd, cmdbuf, + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, cmdbuf, sizeof(cmdbuf))) return false; if (size > 0) - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, data, size)) + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, data, size)) return false; return true; } -static bool netplay_cmd_nak(netplay_t *netplay) +static bool netplay_cmd_nak(netplay_t *netplay, + struct netplay_connection *connection) { - netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_NAK, NULL, 0); return false; } bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) { uint32_t payload[2]; + bool success = true; + size_t i; payload[0] = htonl(delta->frame); payload[1] = htonl(delta->crc); - return netplay_send_raw_cmd(netplay, NETPLAY_CMD_CRC, payload, sizeof(payload)); + 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; } 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_CMD_REQUEST_SAVESTATE, NULL, 0); + return netplay_send_raw_cmd(netplay, &netplay->connections[0], + NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); } -static ssize_t netplay_recva(netplay_t *netplay, void *buf, size_t len) -{ - return netplay_recv(&netplay->recv_packet_buffer, netplay->fd, buf, len, false); -} - -static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) +static bool netplay_get_cmd(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) { uint32_t cmd; uint32_t flip_frame; @@ -484,23 +507,23 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) ssize_t recvd; /* We don't handle the initial handshake here */ - switch (netplay->self_mode) + switch (connection->mode) { case NETPLAY_CONNECTION_NONE: /* Huh?! */ return false; case NETPLAY_CONNECTION_INIT: - return netplay_handshake_init(netplay, had_input); + return netplay_handshake_init(netplay, connection, had_input); case NETPLAY_CONNECTION_PRE_NICK: { - bool ret = netplay_handshake_pre_nick(netplay, had_input); - send_input(netplay); + bool ret = netplay_handshake_pre_nick(netplay, connection, had_input); + send_input(netplay, connection); return ret; } case NETPLAY_CONNECTION_PRE_SYNC: { - bool ret = netplay_handshake_pre_sync(netplay, had_input); - send_input(netplay); + bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); + send_input(netplay, connection); return ret; } default: @@ -510,7 +533,8 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) /* FIXME: This depends on delta_frame_ready */ #define RECV(buf, sz) \ - recvd = netplay_recva(netplay, (buf), (sz)); \ + recvd = netplay_recv(&netplay->recv_packet_buffer, connection->fd, (buf), \ + (sz), false); \ if (recvd >= 0 && recvd < (sz)) goto shrt; \ else if (recvd < 0) @@ -544,13 +568,13 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) { RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } RECV(buffer, sizeof(buffer)) { RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } for (i = 0; i < WORDS_PER_FRAME; i++) @@ -564,7 +588,7 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) else if (buffer[0] > netplay->read_frame_count) { /* Out of order = out of luck */ - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } /* The data's good! */ @@ -580,13 +604,13 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) if (cmd_size != sizeof(uint32_t)) { RARCH_ERR("CMD_FLIP_PLAYERS received an unexpected command size.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } RECV(&flip_frame, sizeof(flip_frame)) { RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } flip_frame = ntohl(flip_frame); @@ -594,7 +618,7 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) if (flip_frame < netplay->read_frame_count) { RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } netplay->flip ^= true; @@ -614,10 +638,10 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) case NETPLAY_CMD_SPECTATE: RARCH_ERR("NETPLAY_CMD_SPECTATE unimplemented.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); case NETPLAY_CMD_DISCONNECT: - hangup(netplay); + hangup(netplay, connection); return true; case NETPLAY_CMD_CRC: @@ -629,13 +653,13 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) if (cmd_size != sizeof(buffer)) { RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } RECV(buffer, sizeof(buffer)) { RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } buffer[0] = ntohl(buffer[0]); @@ -726,39 +750,39 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) if (cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t)) { RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } RECV(&frame, sizeof(frame)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } frame = ntohl(frame); if (frame != netplay->read_frame_count) { RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } RECV(&isize, sizeof(isize)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } 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); + return netplay_cmd_nak(netplay, connection); } RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } /* And decompress it */ @@ -799,10 +823,11 @@ static bool netplay_get_cmd(netplay_t *netplay, bool *had_input) default: RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); - return netplay_cmd_nak(netplay); + return netplay_cmd_nak(netplay, connection); } netplay_recv_flush(&netplay->recv_packet_buffer); + netplay->timeout_cnt = 0; if (had_input) *had_input = true; return true; @@ -815,10 +840,22 @@ shrt: #undef RECV } +/* FIXME: This is going to be very screwy for delay_frames = 0 */ static int poll_input(netplay_t *netplay, bool block) { - bool had_input = false; - int max_fd = netplay->fd + 1; + 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; do { @@ -832,8 +869,12 @@ static int poll_input(netplay_t *netplay, bool block) &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) { - if (!netplay_get_cmd(netplay, &had_input)) - return -1; + 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)) + hangup(netplay, connection); + } } if (block) @@ -850,7 +891,12 @@ static int poll_input(netplay_t *netplay, bool block) tv.tv_usec = RETRY_MS * 1000; FD_ZERO(&fds); - FD_SET(netplay->fd, &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; @@ -929,17 +975,10 @@ static bool netplay_poll(void) { int res; - if (netplay_data->remote_mode == NETPLAY_CONNECTION_NONE) - return false; - netplay_data->can_poll = false; get_self_input_state(netplay_data); - /* No network side in spectate mode */ - if (netplay_is_server(netplay_data) && netplay_data->spectate.enabled) - return true; - /* Read Netplay input, block if we're configured to stall for input every * frame */ if (netplay_data->delay_frames == 0 && @@ -949,7 +988,10 @@ static bool netplay_poll(void) res = poll_input(netplay_data, false); if (res == -1) { - hangup(netplay_data); + /* Catastrophe! */ + size_t i; + for (i = 0; i < netplay_data->connections_size; i++) + hangup(netplay_data, &netplay_data->connections[i]); return false; } @@ -965,6 +1007,10 @@ static bool netplay_poll(void) netplay_data->stall = RARCH_NETPLAY_STALL_NONE; break; + case RARCH_NETPLAY_STALL_NO_CONNECTION: + /* We certainly haven't fixed this */ + break; + default: /* not stalling */ if (netplay_data->read_frame_count + netplay_data->delay_frames <= netplay_data->self_frame_count) @@ -984,8 +1030,10 @@ static bool netplay_poll(void) netplay_data->stall_time = now; else if (now - netplay_data->stall_time >= MAX_STALL_TIME_USEC) { - /* Stalled out! */ - hangup(netplay_data); + /* Stalled out! (FIXME: Shouldn't be so nuclear) */ + size_t i; + for (i = 0; i < netplay_data->connections_size; i++) + hangup(netplay_data, &netplay_data->connections[i]); return false; } } @@ -1031,7 +1079,7 @@ static bool netplay_is_alive(void) { if (!netplay_data) return false; - return (netplay_data->remote_mode == NETPLAY_CONNECTION_PLAYING); + return netplay_data->have_player_connections; } static bool netplay_flip_port(netplay_t *netplay, bool port) @@ -1387,7 +1435,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, if (!netplay) return NULL; - netplay->fd = -1; + netplay->listen_fd = -1; netplay->tcp_port = port; netplay->cbs = *cb; netplay->port = server ? 0 : 1; @@ -1397,11 +1445,22 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->delay_frames = delay_frames; netplay->check_frames = check_frames; netplay->quirks = quirks; - netplay->remote_mode = NETPLAY_CONNECTION_NONE; netplay->self_mode = netplay->is_server ? NETPLAY_CONNECTION_PLAYING : 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, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); if (!netplay_init_buffers(netplay, delay_frames)) @@ -1424,15 +1483,27 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, if(!netplay_info_cb(netplay, delay_frames)) goto error; - /* FIXME: Our initial connection should also be nonblocking */ - if (!socket_nonblock(netplay->fd)) - goto error; + /* 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->fd >= 0) - socket_close(netplay->fd); + 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; @@ -1449,17 +1520,16 @@ error: * * Sends a single netplay command and waits for response. */ -bool netplay_command(netplay_t* netplay, enum netplay_cmd cmd, - void* data, size_t sz, - const char* command_str, - const char* success_msg) +bool netplay_command(netplay_t* netplay, struct netplay_connection *connection, + enum netplay_cmd cmd, void* data, size_t sz, const char* command_str, + const char* success_msg) { char m[256]; const char* msg = NULL; retro_assert(netplay); - if (!netplay_send_raw_cmd(netplay, cmd, data, sz)) + if (!netplay_send_raw_cmd(netplay, connection, cmd, data, sz)) goto error; runloop_msg_queue_push(success_msg, 1, 180, false); @@ -1486,16 +1556,21 @@ static void netplay_flip_users(netplay_t *netplay) * already sent this frame's data */ uint32_t flip_frame = netplay->self_frame_count + 1; uint32_t flip_frame_net = htonl(flip_frame); - bool command = netplay_command( - netplay, NETPLAY_CMD_FLIP_PLAYERS, - &flip_frame_net, sizeof flip_frame_net, - "flip users", "Successfully flipped users.\n"); + size_t i; - if(command) + for (i = 0; i < netplay->connections_size; i++) { - netplay->flip ^= true; - netplay->flip_frame = flip_frame; + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + { + netplay_command(netplay, connection, NETPLAY_CMD_FLIP_PLAYERS, + &flip_frame_net, sizeof flip_frame_net, "flip users", + "Successfully flipped users.\n"); + } } + + netplay->flip ^= true; + netplay->flip_frame = flip_frame; } /** @@ -1506,10 +1581,17 @@ static void netplay_flip_users(netplay_t *netplay) **/ void netplay_free(netplay_t *netplay) { - unsigned i; + size_t i; - if (netplay->fd >= 0) - socket_close(netplay->fd); + 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); + } if (netplay->spectate.enabled) { @@ -1520,6 +1602,9 @@ void netplay_free(netplay_t *netplay) free(netplay->spectate.input); } + if (netplay->connections && netplay->connections != &netplay->one_connection) + free(netplay->connections); + if (netplay->nat_traversal) natt_free(&netplay->nat_traversal_state); @@ -1597,8 +1682,8 @@ bool netplay_pre_frame(netplay_t *netplay) if (!netplay->net_cbs->pre_frame(netplay)) return false; - return ((netplay->remote_mode != NETPLAY_CONNECTION_PLAYING) || - (!netplay->stall && !netplay->remote_paused)); + return (!netplay->have_player_connections || + (!netplay->stall && !netplay->remote_paused)); } /** @@ -1613,8 +1698,11 @@ void netplay_post_frame(netplay_t *netplay) { retro_assert(netplay && netplay->net_cbs->post_frame); netplay->net_cbs->post_frame(netplay); - if (!netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) - hangup(netplay); + /* FIXME: Per-connection send buffer */ + if (netplay->connections_size > 0 && + netplay->connections[0].active && + !netplay_send_flush(&netplay->send_packet_buffer, netplay->connections[0].fd, false)) + hangup(netplay, &netplay->connections[0]); } /** @@ -1626,19 +1714,24 @@ void netplay_post_frame(netplay_t *netplay) **/ void netplay_frontend_paused(netplay_t *netplay, bool paused) { + size_t i; + /* Nothing to do if we already knew this */ if (netplay->local_paused == paused) return; netplay->local_paused = paused; - if (netplay->remote_mode != NETPLAY_CONNECTION_NONE && - !netplay->spectate.enabled) + for (i = 0; i < netplay->connections_size; i++) { - netplay_send_raw_cmd(netplay, paused - ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + { + netplay_send_raw_cmd(netplay, connection, + paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); - /* We're not going to be polled, so we need to flush this command now */ - netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, true); + /* We're not going to be polled, so we need to flush this command now */ + netplay_send_flush(&netplay->send_packet_buffer, connection->fd, true); + } } } @@ -1658,9 +1751,7 @@ void netplay_load_savestate(netplay_t *netplay, uint32_t header[4]; retro_ctx_serialize_info_t tmp_serial_info; uint32_t rd, wn; - - if (netplay->remote_mode != NETPLAY_CONNECTION_PLAYING) - return; + size_t i; /* Record it in our own buffer */ if (save || !serial_info) @@ -1720,28 +1811,28 @@ void netplay_load_savestate(netplay_t *netplay, if (!netplay->compression_backend->trans(netplay->compression_stream, true, &rd, &wn, NULL)) { - hangup(netplay); + /* Catastrophe! */ + for (i = 0; i < netplay->connections_size; i++) + hangup(netplay, &netplay->connections[i]); return; } - /* And send it to the peer */ + /* And send it to the peers */ header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); header[1] = htonl(wn + 2*sizeof(uint32_t)); header[2] = htonl(netplay->self_frame_count); header[3] = htonl(serial_info->size); - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, header, - sizeof(header))) + for (i = 0; i < netplay->connections_size; i++) { - hangup(netplay); - return; - } + struct netplay_connection *connection = &netplay->connections[i]; + if (!connection->active) continue; - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, - netplay->zbuffer, wn)) - { - hangup(netplay); - return; + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, header, + sizeof(header)) || + !netplay_send(&netplay->send_packet_buffer, connection->fd, + netplay->zbuffer, wn)) + hangup(netplay, connection); } } @@ -1755,9 +1846,11 @@ void netplay_load_savestate(netplay_t *netplay, **/ bool netplay_disconnect(netplay_t *netplay) { - if (!netplay || (netplay->remote_mode == NETPLAY_CONNECTION_NONE)) + size_t i; + if (!netplay) return true; - hangup(netplay); + for (i = 0; i < netplay->connections_size; i++) + hangup(netplay, &netplay->connections[i]); return true; } diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index f745ab6f2b..0a2406a037 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -158,7 +158,7 @@ static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb) return (pma & ebit) != (pmb & ebit); } -bool netplay_handshake_init_send(netplay_t *netplay) +bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection) { uint32_t *content_crc_ptr = NULL; uint32_t header[4] = {0}; @@ -170,9 +170,9 @@ bool netplay_handshake_init_send(netplay_t *netplay) header[2] = htonl(netplay_platform_magic()); header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, header, + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, header, sizeof(header)) || - !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) + !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, false)) return false; return true; @@ -185,7 +185,7 @@ struct nick_buf_s }; #define RECV(buf, sz) \ - recvd = netplay_recv(&netplay->recv_packet_buffer, netplay->fd, (buf), (sz), false); \ + recvd = netplay_recv(&netplay->recv_packet_buffer, connection->fd, (buf), (sz), false); \ if (recvd >= 0 && recvd < (sz)) \ { \ netplay_recv_reset(&netplay->recv_packet_buffer); \ @@ -193,7 +193,7 @@ struct nick_buf_s } \ else if (recvd < 0) -bool netplay_handshake_init(netplay_t *netplay, bool *had_input) +bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { uint32_t header[4] = {0}; ssize_t recvd; @@ -280,13 +280,13 @@ bool netplay_handshake_init(netplay_t *netplay, bool *had_input) 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(&netplay->send_packet_buffer, netplay->fd, &nick_buf, + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, &nick_buf, sizeof(nick_buf)) || - !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, false)) + !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, false)) return false; /* Move on to the next mode */ - netplay->self_mode = NETPLAY_CONNECTION_PRE_NICK; + connection->mode = NETPLAY_CONNECTION_PRE_NICK; *had_input = true; netplay_recv_flush(&netplay->recv_packet_buffer); return true; @@ -300,7 +300,7 @@ error: return false; } -static void netplay_handshake_ready(netplay_t *netplay) +static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connection *connection) { size_t i; char msg[512]; @@ -328,10 +328,11 @@ static void netplay_handshake_ready(netplay_t *netplay) if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION) netplay->stall = 0; - netplay->remote_mode = NETPLAY_CONNECTION_PLAYING; + connection->mode = NETPLAY_CONNECTION_PLAYING; + netplay->have_player_connections = true; } -bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) +bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { struct nick_buf_s nick_buf; ssize_t recvd; @@ -372,16 +373,16 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) core_get_memory(&mem_info); cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(sizeof(uint32_t) + mem_info.size); + cmd[1] = htonl(2*sizeof(uint32_t) + mem_info.size); cmd[2] = htonl(netplay->self_frame_count); cmd[3] = htonl(1); - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, cmd, + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, cmd, sizeof(cmd))) return false; - if (!netplay_send(&netplay->send_packet_buffer, netplay->fd, + if (!netplay_send(&netplay->send_packet_buffer, connection->fd, mem_info.data, mem_info.size) || - !netplay_send_flush(&netplay->send_packet_buffer, netplay->fd, + !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, false)) return false; @@ -392,13 +393,13 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) NEXT_PTR(netplay->self_ptr); /* Now we're ready! */ - netplay_handshake_ready(netplay); + netplay_handshake_ready(netplay, connection); } else { /* Client needs to wait for sync info */ - netplay->self_mode = NETPLAY_CONNECTION_PRE_SYNC; + connection->mode = NETPLAY_CONNECTION_PRE_SYNC; } @@ -407,7 +408,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input) return true; } -bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) +bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { uint32_t cmd[2]; uint32_t local_sram_size, remote_sram_size; @@ -496,7 +497,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input) /* We're ready! */ netplay->self_mode = NETPLAY_CONNECTION_PLAYING; - netplay_handshake_ready(netplay); + netplay_handshake_ready(netplay, connection); *had_input = true; netplay_recv_flush(&netplay->recv_packet_buffer); return true; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 66505f5ccf..180dfa00a5 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -105,41 +105,49 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } /* If we can't transmit savestates, we must stall until the client is ready */ - if (netplay->remote_mode != NETPLAY_CONNECTION_PLAYING && - netplay->self_frame_count > 0 && - (netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) + if (netplay->self_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 = RARCH_NETPLAY_STALL_NO_CONNECTION; } - if (netplay->is_server && netplay->remote_mode == NETPLAY_CONNECTION_NONE) + 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->fd, &fds); - if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) > 0 && - FD_ISSET(netplay->fd, &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->fd, (struct sockaddr*)&their_addr, &addr_size); + new_fd = accept(netplay->listen_fd, (struct sockaddr*)&their_addr, &addr_size); if (new_fd < 0) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED)); - return true; + goto process; } - socket_close(netplay->fd); - netplay->fd = new_fd; + /* Set the socket nonblocking */ + if (!socket_nonblock(new_fd)) + { + /* Catastrophe! */ + socket_close(new_fd); + goto process; + } #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) { int flag = 1; - if (setsockopt(netplay->fd, IPPROTO_TCP, TCP_NODELAY, + if (setsockopt(new_fd, IPPROTO_TCP, TCP_NODELAY, #ifdef _WIN32 (const char*) #else @@ -153,26 +161,63 @@ static bool netplay_net_pre_frame(netplay_t *netplay) #if defined(F_SETFD) && defined(FD_CLOEXEC) /* Don't let any inherited processes keep open our port */ - if (fcntl(netplay->fd, F_SETFD, FD_CLOEXEC) < 0) + 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 + /* Allocate a connection */ + for (connection_num = 0; connection_num < netplay->connections_size; connection_num++) + if (!netplay->connections[connection_num].active) break; + if (connection_num == netplay->connections_size) + { + if (connection_num == 0) + { + netplay->connections = malloc(sizeof(struct netplay_connection)); + if (netplay->connections == NULL) + { + 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 = + realloc(netplay->connections, + new_connections_size*sizeof(struct netplay_connection)); + if (new_connections == NULL) + { + 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; + + /* FIXME: Should be per connection */ netplay_clear_socket_buffer(&netplay->send_packet_buffer); netplay_clear_socket_buffer(&netplay->recv_packet_buffer); - /* Set the socket nonblocking */ - if (!socket_nonblock(netplay->fd)) - { - /* Catastrophe! */ - return false; - } - - netplay_handshake_init_send(netplay); - netplay->remote_mode = NETPLAY_CONNECTION_INIT; + netplay_handshake_init_send(netplay, connection); } } +process: netplay->can_poll = true; input_poll_net(); @@ -192,7 +237,7 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (netplay->remote_mode != NETPLAY_CONNECTION_PLAYING) + if (!netplay->have_player_connections) { netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; @@ -320,8 +365,9 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) { if (!netplay_is_server(netplay)) { - netplay_handshake_init_send(netplay); - netplay->remote_mode = netplay->self_mode = NETPLAY_CONNECTION_INIT; + netplay_handshake_init_send(netplay, netplay->connections); + netplay->connections[0].active = true; + netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_INIT; } return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 288f853d8c..56e66554cc 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -223,21 +223,40 @@ struct netplay_callbacks { bool (*info_cb) (netplay_t *netplay, unsigned frames); }; +/* Each connection gets a connection struct */ +struct netplay_connection +{ + bool active; + int fd; + enum rarch_netplay_connection_mode mode; + int player; +}; + struct netplay { char nick[32]; char other_nick[32]; struct sockaddr_storage other_addr; + /* TCP connection for listening (server only) */ + int listen_fd; + /* Our connection number */ uint32_t self_connection_num; - /* Status of our connection */ - enum rarch_netplay_connection_mode remote_mode, self_mode; + /* Our mode and status */ + enum rarch_netplay_connection_mode self_mode; + + /* All of our connections */ + struct netplay_connection *connections; + size_t connections_size; + struct netplay_connection one_connection; /* Client only */ + + /* True if any of our connections are players (i.e., we actually need to do + * netplay) */ + bool have_player_connections; struct retro_callbacks cbs; - /* TCP connection for state sending, etc. Also used for commands */ - int fd; /* TCP port (if serving) */ uint16_t tcp_port; /* NAT traversal info (if NAT traversal is used and serving) */ @@ -447,10 +466,10 @@ bool netplay_get_nickname(netplay_t *netplay, int fd); bool netplay_send_nickname(netplay_t *netplay, int fd); /* Various netplay initialization modes: */ -bool netplay_handshake_init_send(netplay_t *netplay); -bool netplay_handshake_init(netplay_t *netplay, bool *had_input); -bool netplay_handshake_pre_nick(netplay_t *netplay, bool *had_input); -bool netplay_handshake_pre_sync(netplay_t *netplay, bool *had_input); +bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection); +bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); +bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); +bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); uint32_t netplay_impl_magic(void); diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index 4b7c452acf..78c41094f8 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -65,15 +65,15 @@ static bool netplay_spectate_pre_frame(netplay_t *netplay) /* Check for connections */ FD_ZERO(&fds); - FD_SET(netplay->fd, &fds); - if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + FD_SET(netplay->listen_fd, &fds); + if (socket_select(netplay->listen_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) return true; - if (!FD_ISSET(netplay->fd, &fds)) + if (!FD_ISSET(netplay->listen_fd, &fds)) return true; addr_size = sizeof(their_addr); - new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size); + new_fd = accept(netplay->listen_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)); @@ -278,14 +278,14 @@ static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) } else { - if (!netplay_send_nickname(netplay, netplay->fd)) + if (!netplay_send_nickname(netplay, netplay->connections[0].fd)) return false; - if (!netplay_get_nickname(netplay, netplay->fd)) + if (!netplay_get_nickname(netplay, netplay->connections[0].fd)) return false; } - netplay->remote_mode = netplay->self_mode = NETPLAY_CONNECTION_PLAYING; + netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_PLAYING; return true; } From b334f04bd55ee6477a273eccda90027112b2d82d Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 13:32:04 -0500 Subject: [PATCH 19/89] Removing RARCH_ from Netplay stall reasons. --- network/netplay/netplay.c | 8 ++++---- network/netplay/netplay_common.c | 2 +- network/netplay/netplay_net.c | 4 ++-- network/netplay/netplay_private.h | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 62d8721485..18010c038f 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1002,12 +1002,12 @@ static bool netplay_poll(void) /* Consider stalling */ switch (netplay_data->stall) { - case RARCH_NETPLAY_STALL_RUNNING_FAST: + case NETPLAY_STALL_RUNNING_FAST: if (netplay_data->read_frame_count >= netplay_data->self_frame_count) - netplay_data->stall = RARCH_NETPLAY_STALL_NONE; + netplay_data->stall = NETPLAY_STALL_NONE; break; - case RARCH_NETPLAY_STALL_NO_CONNECTION: + case NETPLAY_STALL_NO_CONNECTION: /* We certainly haven't fixed this */ break; @@ -1015,7 +1015,7 @@ static bool netplay_poll(void) if (netplay_data->read_frame_count + netplay_data->delay_frames <= netplay_data->self_frame_count) { - netplay_data->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; + netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; netplay_data->stall_time = cpu_features_get_time_usec(); } } diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 0a2406a037..86ab59ab66 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -325,7 +325,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio } /* Unstall if we were waiting for this */ - if (netplay->stall == RARCH_NETPLAY_STALL_NO_CONNECTION) + if (netplay->stall == NETPLAY_STALL_NO_CONNECTION) netplay->stall = 0; connection->mode = NETPLAY_CONNECTION_PLAYING; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 180dfa00a5..40746cf931 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -109,7 +109,7 @@ static bool netplay_net_pre_frame(netplay_t *netplay) (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 = RARCH_NETPLAY_STALL_NO_CONNECTION; + netplay->stall = NETPLAY_STALL_NO_CONNECTION; } if (netplay->is_server) @@ -221,7 +221,7 @@ process: netplay->can_poll = true; input_poll_net(); - return (netplay->stall != RARCH_NETPLAY_STALL_NO_CONNECTION); + return (netplay->stall != NETPLAY_STALL_NO_CONNECTION); } /** diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 56e66554cc..9a3d05ca6c 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -179,9 +179,9 @@ enum rarch_netplay_connection_mode enum rarch_netplay_stall_reason { - RARCH_NETPLAY_STALL_NONE = 0, - RARCH_NETPLAY_STALL_RUNNING_FAST, - RARCH_NETPLAY_STALL_NO_CONNECTION + NETPLAY_STALL_NONE = 0, + NETPLAY_STALL_RUNNING_FAST, + NETPLAY_STALL_NO_CONNECTION }; struct delta_frame From 189cc6e5d66108dbc637e9bb04d91dc340fa6a73 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 14:14:54 -0500 Subject: [PATCH 20/89] Moving socket buffers to per-connection (currently breaks delay_frames=0) --- network/netplay/netplay.c | 128 +++++++++++++++++------------- network/netplay/netplay_common.c | 24 +++--- network/netplay/netplay_net.c | 15 +++- network/netplay/netplay_private.h | 26 +++++- 4 files changed, 120 insertions(+), 73 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 18010c038f..7a262c75a7 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -213,9 +213,14 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, { ret = true; if (direct_host || server) + { + netplay->connections[0].active = true; netplay->connections[0].fd = fd; + } else + { netplay->listen_fd = fd; + } break; } @@ -257,9 +262,6 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve if (!init_tcp_socket(netplay, direct_host, server, port, netplay->spectate.enabled)) return false; - netplay_clear_socket_buffer(&netplay->send_packet_buffer); - netplay_clear_socket_buffer(&netplay->recv_packet_buffer); - if (netplay->is_server && netplay->nat_traversal) init_nat_traversal(netplay); @@ -283,6 +285,8 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) 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; @@ -303,7 +307,7 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) } } - /* Reset things that will behave oddly if we get a new connection */ + /* Reset things that will behave oddly if we get a new connection (FIXME) */ netplay->remote_paused = false; netplay->flip = false; netplay->flip_frame = 0; @@ -348,10 +352,10 @@ static void send_input(netplay_t *netplay, struct netplay_connection *connection connection->mode >= NETPLAY_CONNECTION_CONNECTED) { netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, netplay->input_packet_buffer, sizeof(netplay->input_packet_buffer)) || - !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) { hangup(netplay, connection); @@ -450,12 +454,12 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, cmdbuf[0] = htonl(cmd); cmdbuf[1] = htonl(size); - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, cmdbuf, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmdbuf, sizeof(cmdbuf))) return false; if (size > 0) - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, data, size)) + if (!netplay_send(&connection->send_packet_buffer, connection->fd, data, size)) return false; return true; @@ -533,7 +537,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* FIXME: This depends on delta_frame_ready */ #define RECV(buf, sz) \ - recvd = netplay_recv(&netplay->recv_packet_buffer, connection->fd, (buf), \ + recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \ (sz), false); \ if (recvd >= 0 && recvd < (sz)) goto shrt; \ else if (recvd < 0) @@ -826,7 +830,7 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } - netplay_recv_flush(&netplay->recv_packet_buffer); + netplay_recv_flush(&connection->recv_packet_buffer); netplay->timeout_cnt = 0; if (had_input) *had_input = true; @@ -834,7 +838,7 @@ static bool netplay_get_cmd(netplay_t *netplay, shrt: /* No more data, reset and try again */ - netplay_recv_reset(&netplay->recv_packet_buffer); + netplay_recv_reset(&connection->recv_packet_buffer); return true; #undef RECV @@ -1271,6 +1275,42 @@ static void announce_nat_traversal(netplay_t *netplay) } #endif +static bool netplay_init_socket_buffers(netplay_t *netplay) +{ + /* Make our packet buffer big enough for a save state and 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->delay_frames * WORDS_PER_FRAME + (netplay->delay_frames+1)*3; + 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; +} + bool netplay_try_init_serialization(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; @@ -1293,7 +1333,7 @@ bool netplay_try_init_serialization(netplay_t *netplay) /* Once initialized, we no longer exhibit this quirk */ netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION); - return true; + return netplay_init_socket_buffers(netplay); } bool netplay_wait_and_init_serialization(netplay_t *netplay) @@ -1320,28 +1360,6 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay) return false; } -static bool netplay_init_socket_buffers(netplay_t *netplay) -{ - /* Make our packet buffer big enough for a save state and frames-many frames - * of input data, plus the headers for each of them */ - size_t packet_buffer_size = netplay->zbuffer_size + - netplay->delay_frames * WORDS_PER_FRAME + (netplay->delay_frames+1)*3; - - if (netplay->send_packet_buffer.data) - { - netplay_deinit_socket_buffer(&netplay->send_packet_buffer); - netplay_deinit_socket_buffer(&netplay->recv_packet_buffer); - netplay->send_packet_buffer.data = netplay->recv_packet_buffer.data = NULL; - } - - if (!netplay_init_socket_buffer(&netplay->send_packet_buffer, packet_buffer_size)) - return false; - if (!netplay_init_socket_buffer(&netplay->recv_packet_buffer, packet_buffer_size)) - return false; - - return true; -} - bool netplay_init_serialization(netplay_t *netplay) { unsigned i; @@ -1377,7 +1395,7 @@ bool netplay_init_serialization(netplay_t *netplay) return false; } - return netplay_init_socket_buffers(netplay); + return true; } static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) @@ -1399,13 +1417,10 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) if (!netplay->buffer) return false; - if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)) + if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_INITIALIZATION))) netplay_init_serialization(netplay); - if (!netplay->send_packet_buffer.data) - netplay_init_socket_buffers(netplay); - - return true; + return netplay_init_socket_buffers(netplay); } /** @@ -1463,12 +1478,6 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); - if (!netplay_init_buffers(netplay, delay_frames)) - { - free(netplay); - return NULL; - } - if(spectate) netplay->net_cbs = netplay_get_cbs_spectate(); else @@ -1480,6 +1489,17 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, return NULL; } + if (!netplay_init_buffers(netplay, delay_frames)) + { + free(netplay); + return NULL; + } + + if (!netplay->is_server) + { + fprintf(stderr, "CONNECTION 0 %d\n", netplay->connections[0].active); + } + if(!netplay_info_cb(netplay, delay_frames)) goto error; @@ -1590,7 +1610,11 @@ void netplay_free(netplay_t *netplay) { 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->spectate.enabled) @@ -1617,9 +1641,6 @@ void netplay_free(netplay_t *netplay) free(netplay->buffer); } - netplay_deinit_socket_buffer(&netplay->send_packet_buffer); - netplay_deinit_socket_buffer(&netplay->recv_packet_buffer); - if (netplay->zbuffer) free(netplay->zbuffer); @@ -1701,7 +1722,8 @@ void netplay_post_frame(netplay_t *netplay) /* FIXME: Per-connection send buffer */ if (netplay->connections_size > 0 && netplay->connections[0].active && - !netplay_send_flush(&netplay->send_packet_buffer, netplay->connections[0].fd, false)) + !netplay_send_flush(&netplay->connections[0].send_packet_buffer, + netplay->connections[0].fd, false)) hangup(netplay, &netplay->connections[0]); } @@ -1730,7 +1752,7 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); /* We're not going to be polled, so we need to flush this command now */ - netplay_send_flush(&netplay->send_packet_buffer, connection->fd, true); + netplay_send_flush(&connection->send_packet_buffer, connection->fd, true); } } } @@ -1828,9 +1850,9 @@ void netplay_load_savestate(netplay_t *netplay, struct netplay_connection *connection = &netplay->connections[i]; if (!connection->active) continue; - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, header, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, sizeof(header)) || - !netplay_send(&netplay->send_packet_buffer, connection->fd, + !netplay_send(&connection->send_packet_buffer, connection->fd, netplay->zbuffer, wn)) hangup(netplay, connection); } diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 86ab59ab66..b56c3b99d9 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -170,9 +170,9 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection * header[2] = htonl(netplay_platform_magic()); header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, header, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, sizeof(header)) || - !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, false)) + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) return false; return true; @@ -185,10 +185,10 @@ struct nick_buf_s }; #define RECV(buf, sz) \ - recvd = netplay_recv(&netplay->recv_packet_buffer, connection->fd, (buf), (sz), false); \ + recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \ if (recvd >= 0 && recvd < (sz)) \ { \ - netplay_recv_reset(&netplay->recv_packet_buffer); \ + netplay_recv_reset(&connection->recv_packet_buffer); \ return true; \ } \ else if (recvd < 0) @@ -280,15 +280,15 @@ bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *conne 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(&netplay->send_packet_buffer, connection->fd, &nick_buf, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, &nick_buf, sizeof(nick_buf)) || - !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, false)) + !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(&netplay->recv_packet_buffer); + netplay_recv_flush(&connection->recv_packet_buffer); return true; error: @@ -377,12 +377,12 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c cmd[2] = htonl(netplay->self_frame_count); cmd[3] = htonl(1); - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, cmd, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, sizeof(cmd))) return false; - if (!netplay_send(&netplay->send_packet_buffer, connection->fd, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, mem_info.data, mem_info.size) || - !netplay_send_flush(&netplay->send_packet_buffer, connection->fd, + !netplay_send_flush(&connection->send_packet_buffer, connection->fd, false)) return false; @@ -404,7 +404,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c } *had_input = true; - netplay_recv_flush(&netplay->recv_packet_buffer); + netplay_recv_flush(&connection->recv_packet_buffer); return true; } @@ -499,7 +499,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay_handshake_ready(netplay, connection); *had_input = true; - netplay_recv_flush(&netplay->recv_packet_buffer); + netplay_recv_flush(&connection->recv_packet_buffer); return true; } diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 40746cf931..0c9d858950 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -208,9 +208,17 @@ static bool netplay_net_pre_frame(netplay_t *netplay) connection->fd = new_fd; connection->mode = NETPLAY_CONNECTION_INIT; - /* FIXME: Should be per connection */ - netplay_clear_socket_buffer(&netplay->send_packet_buffer); - netplay_clear_socket_buffer(&netplay->recv_packet_buffer); + 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); @@ -366,7 +374,6 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) if (!netplay_is_server(netplay)) { netplay_handshake_init_send(netplay, netplay->connections); - netplay->connections[0].active = true; netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_INIT; } diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 9a3d05ca6c..841fd9b5b3 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -226,16 +226,31 @@ struct netplay_callbacks { /* Each connection gets a connection struct */ struct netplay_connection { + /* Is this connection buffer in use? */ bool active; + + /* fd associated with this connection */ int fd; + + /* Buffers for sending and receiving data */ + struct socket_buffer send_packet_buffer, recv_packet_buffer; + + /* Mode of the connection */ enum rarch_netplay_connection_mode mode; + + /* Player # of connected player, or -1 if not a player */ int player; }; struct netplay { + /* Our nickname */ char nick[32]; + + /* Nickname of peer */ char other_nick[32]; + + /* Address of peer */ struct sockaddr_storage other_addr; /* TCP connection for listening (server only) */ @@ -257,11 +272,14 @@ struct netplay bool have_player_connections; struct retro_callbacks cbs; - /* TCP port (if serving) */ + + /* TCP port (only set if serving) */ uint16_t tcp_port; + /* NAT traversal info (if NAT traversal is used and serving) */ bool nat_traversal; struct natt_status nat_traversal_state; + /* Which port is governed by netplay (other user)? */ unsigned port; @@ -278,6 +296,9 @@ struct netplay uint8_t *zbuffer; size_t zbuffer_size; + /* The size of our packet buffers */ + size_t packet_buffer_size; + /* Pointer where we are now. */ size_t self_ptr; /* Points to the last reliable state that self ever had. */ @@ -313,9 +334,6 @@ struct netplay /* A buffer for outgoing input packets. */ uint32_t input_packet_buffer[2 + WORDS_PER_FRAME]; - /* And buffers for sending and receiving our actual data */ - struct socket_buffer send_packet_buffer, recv_packet_buffer; - /* All of our frame counts */ uint32_t self_frame_count; uint32_t read_frame_count; From d1d29143b23cc9f45975f9e6d560ce95f0d1d248 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 22:02:31 -0500 Subject: [PATCH 21/89] Move nickname field to connections. Spectate mode officially broken. --- network/netplay/netplay_common.c | 55 +++--------------------------- network/netplay/netplay_private.h | 6 ++-- network/netplay/netplay_spectate.c | 4 +++ 3 files changed, 12 insertions(+), 53 deletions(-) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index b56c3b99d9..85b9361fba 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -29,51 +29,6 @@ #include "../../runloop.h" #include "../../version.h" -bool netplay_get_nickname(netplay_t *netplay, int fd) -{ - uint8_t nick_size; - - if (!socket_receive_all_blocking(fd, &nick_size, sizeof(nick_size))) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_SIZE_FROM_HOST)); - return false; - } - - if (nick_size >= sizeof(netplay->other_nick)) - { - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_INVALID_NICKNAME_SIZE)); - return false; - } - - if (!socket_receive_all_blocking(fd, netplay->other_nick, nick_size)) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME)); - return false; - } - - return true; -} -bool netplay_send_nickname(netplay_t *netplay, int fd) -{ - uint8_t nick_size = strlen(netplay->nick); - - if (!socket_send_all_blocking(fd, &nick_size, sizeof(nick_size), false)) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_SIZE)); - return false; - } - - if (!socket_send_all_blocking(fd, netplay->nick, nick_size, false)) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME)); - return false; - } - - return true; -} - /** * netplay_impl_magic: * @@ -307,7 +262,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio if (netplay->is_server) { - netplay_log_connection(&netplay->other_addr, 0, netplay->other_nick); + netplay_log_connection(&netplay->other_addr, 0, connection->nick); /* Send them the savestate */ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) @@ -319,7 +274,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio { snprintf(msg, sizeof(msg), "%s: \"%s\"", msg_hash_to_str(MSG_CONNECTED_TO), - netplay->other_nick); + connection->nick); RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false); } @@ -359,9 +314,9 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c return false; } - strlcpy(netplay->other_nick, nick_buf.nick, - (sizeof(netplay->other_nick) < sizeof(nick_buf.nick)) ? - sizeof(netplay->other_nick) : sizeof(nick_buf.nick)); + strlcpy(connection->nick, nick_buf.nick, + (sizeof(connection->nick) < sizeof(nick_buf.nick)) ? + sizeof(connection->nick) : sizeof(nick_buf.nick)); if (netplay->is_server) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 841fd9b5b3..bc8e147da4 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -232,6 +232,9 @@ struct netplay_connection /* fd associated with this connection */ int fd; + /* Nickname of peer */ + char nick[32]; + /* Buffers for sending and receiving data */ struct socket_buffer send_packet_buffer, recv_packet_buffer; @@ -247,9 +250,6 @@ struct netplay /* Our nickname */ char nick[32]; - /* Nickname of peer */ - char other_nick[32]; - /* Address of peer */ struct sockaddr_storage other_addr; diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index 78c41094f8..dd043af35a 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -97,6 +97,7 @@ static bool netplay_spectate_pre_frame(netplay_t *netplay) return true; } +#if 0 if (!netplay_get_nickname(netplay, new_fd)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT)); @@ -110,6 +111,7 @@ static bool netplay_spectate_pre_frame(netplay_t *netplay) socket_close(new_fd); return true; } +#endif /* Wait until it's safe to serialize */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) @@ -278,11 +280,13 @@ static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) } else { +#if 0 if (!netplay_send_nickname(netplay, netplay->connections[0].fd)) return false; if (!netplay_get_nickname(netplay, netplay->connections[0].fd)) return false; +#endif } netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_PLAYING; From ea722b49c82a53505a1de1b843f004201ba7fa90 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 22:29:02 -0500 Subject: [PATCH 22/89] Move other_addr from netplay to connection. --- network/netplay/netplay.c | 6 ++++-- network/netplay/netplay_common.c | 2 +- network/netplay/netplay_private.h | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 7a262c75a7..85abe08dfa 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -202,12 +202,13 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, while (tmp_info) { + struct sockaddr_storage sad; int fd = init_tcp_connection( tmp_info, direct_host || server, netplay->spectate.enabled, - (struct sockaddr*)&netplay->other_addr, - sizeof(netplay->other_addr)); + (struct sockaddr*)&sad, + sizeof(sad)); if (fd >= 0) { @@ -216,6 +217,7 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, { netplay->connections[0].active = true; netplay->connections[0].fd = fd; + netplay->connections[0].addr = sad; } else { diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 85b9361fba..786d9db047 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -262,7 +262,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio if (netplay->is_server) { - netplay_log_connection(&netplay->other_addr, 0, connection->nick); + netplay_log_connection(&connection->addr, 0, connection->nick); /* Send them the savestate */ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index bc8e147da4..43b2818085 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -232,6 +232,9 @@ struct netplay_connection /* fd associated with this connection */ int fd; + /* Address of peer */ + struct sockaddr_storage addr; + /* Nickname of peer */ char nick[32]; @@ -250,9 +253,6 @@ struct netplay /* Our nickname */ char nick[32]; - /* Address of peer */ - struct sockaddr_storage other_addr; - /* TCP connection for listening (server only) */ int listen_fd; From 4768970d52a87557627ca413ea113a61c8ef68b8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 22:37:50 -0500 Subject: [PATCH 23/89] Moving force_send_savestate from netplay to connection. --- network/netplay/netplay.c | 3 ++- network/netplay/netplay_common.c | 3 ++- network/netplay/netplay_net.c | 6 ++++-- network/netplay/netplay_private.h | 34 ++++++++++++++++++++----------- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 85abe08dfa..06adae17cf 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -717,7 +717,8 @@ static bool netplay_get_cmd(netplay_t *netplay, case NETPLAY_CMD_REQUEST_SAVESTATE: /* Delay until next frame so we don't send the savestate after the * input */ - netplay->force_send_savestate = true; + connection->force_send_savestate = true; + netplay->force_send_savestate_one = true; break; case NETPLAY_CMD_LOAD_SAVESTATE: diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 786d9db047..2d79c8d71d 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -267,7 +267,8 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio /* Send them the savestate */ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) { - netplay->force_send_savestate = true; + connection->force_send_savestate = true; + netplay->force_send_savestate_one = true; } } else diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 0c9d858950..157eda9924 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -88,12 +88,14 @@ static bool netplay_net_pre_frame(netplay_t *netplay) } else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info)) { - if (netplay->force_send_savestate && !netplay->stall) + if ((netplay->force_send_savestate_all || netplay->force_send_savestate_one) && !netplay->stall) { /* Send this along to the other side */ serial_info.data_const = netplay->buffer[netplay->self_ptr].state; netplay_load_savestate(netplay, &serial_info, false); - netplay->force_send_savestate = false; + netplay->force_send_savestate_all = + netplay->force_send_savestate_one = false; + /* FIXME: Shouldn't send to everyone! */ } } else diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 43b2818085..cd156e0fbe 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -246,6 +246,9 @@ struct netplay_connection /* Player # of connected player, or -1 if not a player */ int player; + + /* Force send a savestate, to this connection only */ + bool force_send_savestate; }; struct netplay @@ -299,16 +302,25 @@ struct netplay /* The size of our packet buffers */ size_t packet_buffer_size; - /* Pointer where we are now. */ + /* The current frame seen by the frontend */ size_t self_ptr; - /* Points to the last reliable state that self ever had. */ + uint32_t self_frame_count; + + /* The first frame at which some data might be unreliable */ size_t other_ptr; + uint32_t other_frame_count; + /* Pointer to where we are reading. - * Generally, other_ptr <= read_ptr <= self_ptr. */ + * Generally, other_ptr <= read_ptr <= self_ptr, but read_ptr can get ahead + * of self_ptr if the peer is running fast. */ size_t read_ptr; + uint32_t read_frame_count; + /* A pointer used temporarily for replay. */ size_t replay_ptr; + uint32_t replay_frame_count; + /* Size of savestates */ size_t state_size; /* Are we replaying old frames? */ @@ -324,9 +336,13 @@ struct netplay /* Quirks in the savestate implementation */ uint64_t quirks; - /* Force our state to be sent to the other side. Used when they request a - * savestate, to send at the next pre-frame. */ - bool force_send_savestate; + /* Force our state to be sent to all connections. Used when we explicitly + * load a state. */ + bool force_send_savestate_all; + + /* Set if there is at least one client which must be sent the state, usually + * because they've requested it or just connected. */ + bool force_send_savestate_one; /* Have we requested a savestate as a sync point? */ bool savestate_request_outstanding; @@ -334,12 +350,6 @@ struct netplay /* A buffer for outgoing input packets. */ uint32_t input_packet_buffer[2 + WORDS_PER_FRAME]; - /* All of our frame counts */ - uint32_t self_frame_count; - uint32_t read_frame_count; - uint32_t other_frame_count; - uint32_t replay_frame_count; - /* And socket info */ struct addrinfo *addr; struct sockaddr_storage their_addr; From 9b2270f5d4cc20fa1c9270d5e1cf64a7443d2dfc Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 23:04:39 -0500 Subject: [PATCH 24/89] Say goodbye to spectator mode (for now) --- Makefile.common | 1 - command.c | 5 +- configuration.c | 8 - configuration.h | 1 - menu/menu_displaylist.c | 4 - menu/menu_setting.c | 20 -- network/netplay/netplay.c | 34 +--- network/netplay/netplay.h | 3 +- network/netplay/netplay_common.c | 7 - network/netplay/netplay_private.h | 27 +-- network/netplay/netplay_spectate.c | 305 ----------------------------- retroarch.c | 8 - 12 files changed, 18 insertions(+), 405 deletions(-) delete mode 100644 network/netplay/netplay_spectate.c diff --git a/Makefile.common b/Makefile.common index c81cf37b91..5b20704103 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1123,7 +1123,6 @@ ifeq ($(HAVE_NETWORKING), 1) # Netplay DEFINES += -DHAVE_NETWORK_CMD OBJ += network/netplay/netplay_net.o \ - network/netplay/netplay_spectate.o \ network/netplay/netplay_common.o \ network/netplay/netplay_discovery.o \ network/netplay/netplay_buf.o \ diff --git a/command.c b/command.c index 6eefad0f02..8676b49c6d 100644 --- a/command.c +++ b/command.c @@ -1227,8 +1227,7 @@ static void command_event_load_auto_state(void) global_t *global = global_get_ptr(); #ifdef HAVE_NETWORKING - if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) - && !settings->netplay.is_spectate) + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) return; #endif @@ -2362,7 +2361,7 @@ bool command_event(enum event_command cmd, void *data) command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); #ifdef HAVE_NETWORKING if (!init_netplay( - settings->netplay.is_spectate, data, settings->netplay.server, + data, settings->netplay.server, settings->netplay.port)) return false; #endif diff --git a/configuration.c b/configuration.c index 0f6ee0a82c..b869a84f66 100644 --- a/configuration.c +++ b/configuration.c @@ -822,7 +822,6 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti SETTING_BOOL("network_remote_enable", &settings->network_remote_enable, false, false /* TODO */, false); #endif #ifdef HAVE_NETWORKING - SETTING_BOOL("netplay_spectator_mode_enable",&settings->netplay.is_spectate, false, false /* TODO */, false); SETTING_BOOL("netplay_nat_traversal", &settings->netplay.nat_traversal, true, true, false); #endif SETTING_BOOL("block_sram_overwrite", &settings->block_sram_overwrite, true, block_sram_overwrite, false); @@ -1807,13 +1806,6 @@ static bool config_load_file(const char *path, bool set_defaults, if (!rarch_ctl(RARCH_CTL_IS_FORCE_FULLSCREEN, NULL)) CONFIG_GET_BOOL_BASE(conf, settings, video.fullscreen, "video_fullscreen"); -#ifdef HAVE_NETWORKING - if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL)) - { - CONFIG_GET_BOOL_BASE(conf, settings, netplay.is_spectate, - "netplay_spectator_mode_enable"); - } -#endif #ifdef HAVE_NETWORKGAMEPAD for (i = 0; i < MAX_USERS; i++) { diff --git a/configuration.h b/configuration.h index d4cc94cf91..85093933a1 100644 --- a/configuration.h +++ b/configuration.h @@ -403,7 +403,6 @@ typedef struct settings unsigned port; unsigned delay_frames; unsigned check_frames; - bool is_spectate; bool swap_input; bool nat_traversal; } netplay; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 7492b4aa71..44f118ae6b 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4697,10 +4697,6 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, PARSE_ONLY_UINT, false) != -1) count++; - if (menu_displaylist_parse_settings_enum(menu, info, - MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, - PARSE_ONLY_BOOL, false) != -1) - count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, PARSE_ONLY_BOOL, false) != -1) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index fa64c2c968..789e76bdd3 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -1719,11 +1719,6 @@ void general_write_handler(void *data) case MENU_ENUM_LABEL_NETPLAY_MODE: #ifdef HAVE_NETWORKING retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL); -#endif - break; - case MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE: -#ifdef HAVE_NETWORKING - retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL); #endif break; case MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES: @@ -5610,21 +5605,6 @@ static bool setting_append_list( menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); - CONFIG_BOOL( - list, list_info, - &settings->netplay.is_spectate, - MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, - MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE, - false, - MENU_ENUM_LABEL_VALUE_OFF, - MENU_ENUM_LABEL_VALUE_ON, - &group_info, - &subgroup_info, - parent_group, - general_write_handler, - general_read_handler, - SD_FLAG_NONE); - CONFIG_BOOL( list, list_info, &settings->netplay.nat_traversal, diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 06adae17cf..88886cbcfe 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -61,7 +61,7 @@ static void announce_nat_traversal(netplay_t *netplay); #endif static int init_tcp_connection(const struct addrinfo *res, - bool server, bool spectate, + bool server, struct sockaddr *other_addr, socklen_t addr_size) { bool ret = true; @@ -114,7 +114,7 @@ static int init_tcp_connection(const struct addrinfo *res, } #endif if ( !socket_bind(fd, (void*)res) || - listen(fd, spectate ? MAX_SPECTATORS : 1) < 0) + listen(fd, 1024) < 0) { ret = false; goto end; @@ -132,7 +132,7 @@ end: } static bool init_tcp_socket(netplay_t *netplay, void *direct_host, - const char *server, uint16_t port, bool spectate) + const char *server, uint16_t port) { char port_buf[16]; bool ret = false; @@ -206,7 +206,6 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, int fd = init_tcp_connection( tmp_info, direct_host || server, - netplay->spectate.enabled, (struct sockaddr*)&sad, sizeof(sad)); @@ -261,7 +260,7 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve if (!network_init()) return false; - if (!init_tcp_socket(netplay, direct_host, server, port, netplay->spectate.enabled)) + if (!init_tcp_socket(netplay, direct_host, server, port)) return false; if (netplay->is_server && netplay->nat_traversal) @@ -349,8 +348,7 @@ static bool netplay_can_poll(netplay_t *netplay) * finishing the initial handshake */ static void send_input(netplay_t *netplay, struct netplay_connection *connection) { - if (!netplay->spectate.enabled && /* Spectate sends in its own way */ - netplay->self_mode == NETPLAY_CONNECTION_PLAYING && + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING && connection->mode >= NETPLAY_CONNECTION_CONNECTED) { netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); @@ -1434,7 +1432,6 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * @delay_frames : Amount of delay frames. * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. - * @spectate : If true, enable spectator mode. * @nat_traversal : If true, attempt NAT traversal. * @nick : Nickname of user. * @quirks : Netplay quirks required for this session. @@ -1446,7 +1443,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) **/ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, unsigned delay_frames, unsigned check_frames, - const struct retro_callbacks *cb, bool spectate, bool nat_traversal, + const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks) { netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay)); @@ -1457,7 +1454,6 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->tcp_port = port; netplay->cbs = *cb; netplay->port = server ? 0 : 1; - netplay->spectate.enabled = spectate; netplay->is_server = server == NULL; netplay->nat_traversal = netplay->is_server ? nat_traversal : false; netplay->delay_frames = delay_frames; @@ -1481,10 +1477,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); - if(spectate) - netplay->net_cbs = netplay_get_cbs_spectate(); - else - netplay->net_cbs = netplay_get_cbs_net(); + netplay->net_cbs = netplay_get_cbs_net(); if (!init_socket(netplay, direct_host, server, port)) { @@ -1620,15 +1613,6 @@ void netplay_free(netplay_t *netplay) } } - if (netplay->spectate.enabled) - { - for (i = 0; i < MAX_SPECTATORS; i++) - if (netplay->spectate.fds[i] >= 0) - socket_close(netplay->spectate.fds[i]); - - free(netplay->spectate.input); - } - if (netplay->connections && netplay->connections != &netplay->one_connection) free(netplay->connections); @@ -1897,7 +1881,7 @@ void deinit_netplay(void) * Returns: true (1) if successful, otherwise false (0). **/ -bool init_netplay(bool is_spectate, void *direct_host, const char *server, unsigned port) +bool init_netplay(void *direct_host, const char *server, unsigned port) { struct retro_callbacks cbs = {0}; settings_t *settings = config_get_ptr(); @@ -1953,7 +1937,7 @@ bool init_netplay(bool is_spectate, void *direct_host, const char *server, unsig netplay_is_client ? server : NULL, port ? port : RARCH_DEFAULT_PORT, settings->netplay.delay_frames, settings->netplay.check_frames, &cbs, - is_spectate, settings->netplay.nat_traversal, settings->username, + settings->netplay.nat_traversal, settings->username, quirks); if (netplay_data) diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 24abe4e615..ec84805c97 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -58,7 +58,6 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); /** * init_netplay - * @is_spectate : true if running in spectate mode * @server : server address to connect to (client only) * @port : TCP port to host on/connect to * @@ -68,7 +67,7 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); * * Returns: true (1) if successful, otherwise false (0). **/ -bool init_netplay(bool is_spectate, void *direct_host, const char *server, unsigned port); +bool init_netplay(void *direct_host, const char *server, unsigned port); void deinit_netplay(void); diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 2d79c8d71d..7f59c82265 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -466,13 +466,6 @@ bool netplay_is_server(netplay_t* netplay) return netplay->is_server; } -bool netplay_is_spectate(netplay_t* netplay) -{ - if (!netplay) - return false; - return netplay->spectate.enabled; -} - bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) { void *remember_state; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index cd156e0fbe..085223c811 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -35,7 +35,6 @@ #endif #define WORDS_PER_FRAME 4 /* Allows us to send 128 bits worth of state per frame. */ -#define MAX_SPECTATORS 16 #define RARCH_DEFAULT_PORT 55435 #define RARCH_DEFAULT_NICK "Anonymous" @@ -253,6 +252,9 @@ struct netplay_connection struct netplay { + /* Are we the server? */ + bool is_server; + /* Our nickname */ char nick[32]; @@ -350,24 +352,12 @@ struct netplay /* A buffer for outgoing input packets. */ uint32_t input_packet_buffer[2 + WORDS_PER_FRAME]; - /* And socket info */ + /* Our local socket info */ struct addrinfo *addr; - struct sockaddr_storage their_addr; - bool has_client_addr; + /* Counter for timeouts */ unsigned timeout_cnt; - /* Spectating. */ - struct { - bool enabled; - int fds[MAX_SPECTATORS]; - uint32_t frames[MAX_SPECTATORS]; - uint16_t *input; - size_t input_ptr; - size_t input_sz; - } spectate; - bool is_server; - /* User flipping * Flipping state. If frame >= flip_frame, we apply the flip. * If not, we apply the opposite, effectively creating a trigger point. */ @@ -400,7 +390,6 @@ void input_poll_net(void); * @delay_frames : Amount of delay frames. * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. - * @spectate : If true, enable spectator mode. * @nat_traversal : If true, attempt NAT traversal. * @nick : Nickname of user. * @quirks : Netplay quirks. @@ -412,7 +401,7 @@ void input_poll_net(void); **/ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, unsigned delay_frames, unsigned check_frames, - const struct retro_callbacks *cb, bool spectate, bool nat_traversal, + const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks); /** @@ -476,8 +465,6 @@ bool netplay_disconnect(netplay_t *netplay); struct netplay_callbacks* netplay_get_cbs_net(void); -struct netplay_callbacks* netplay_get_cbs_spectate(void); - /* Normally called at init time, unless the INITIALIZATION quirk is set */ bool netplay_init_serialization(netplay_t *netplay); @@ -503,8 +490,6 @@ uint32_t netplay_impl_magic(void); bool netplay_is_server(netplay_t* netplay); -bool netplay_is_spectate(netplay_t* netplay); - bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame); uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta); diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c deleted file mode 100644 index dd043af35a..0000000000 --- a/network/netplay/netplay_spectate.c +++ /dev/null @@ -1,305 +0,0 @@ -/* 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; - 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]; - struct timeval tmp_tv = {0}; - - 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->input_packet_buffer[2] = htonl(netplay->self_frame_count - netplay->spectate.frames[i]); - if (!socket_send_all_blocking(netplay->spectate.fds[i], netplay->input_packet_buffer, sizeof(netplay->input_packet_buffer), false)) - { - socket_close(netplay->spectate.fds[i]); - netplay->spectate.fds[i] = -1; - } - } - } - - /* Check for connections */ - FD_ZERO(&fds); - FD_SET(netplay->listen_fd, &fds); - if (socket_select(netplay->listen_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - return true; - - if (!FD_ISSET(netplay->listen_fd, &fds)) - return true; - - addr_size = sizeof(their_addr); - new_fd = accept(netplay->listen_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 0 - 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; - } -#endif - - /* 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->input_packet_buffer[2] = htonl(0); - if (!socket_send_all_blocking(new_fd, netplay->input_packet_buffer, sizeof(netplay->input_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->delay_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 0 - if (!netplay_send_nickname(netplay, netplay->connections[0].fd)) - return false; - - if (!netplay_get_nickname(netplay, netplay->connections[0].fd)) - return false; -#endif - } - - netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_PLAYING; - - 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; -} diff --git a/retroarch.c b/retroarch.c index af54841baa..380c5dbdde 100644 --- a/retroarch.c +++ b/retroarch.c @@ -342,7 +342,6 @@ static void retroarch_print_help(const char *arg0) puts(" -F, --frames=NUMBER Delay frames when using netplay."); puts(" --check-frames=NUMBER\n" " Check frames when using netplay."); - puts(" --spectate Connect to netplay server as spectator."); #if defined(HAVE_NETWORK_CMD) puts(" --command Sends a command over UDP to an already " "running program process."); @@ -430,7 +429,6 @@ static void retroarch_parse_input(int argc, char *argv[]) { "frames", 1, NULL, 'F' }, { "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES }, { "port", 1, NULL, RA_OPT_PORT }, - { "spectate", 0, NULL, RA_OPT_SPECTATE }, #if defined(HAVE_NETWORK_CMD) { "command", 1, NULL, RA_OPT_COMMAND }, #endif @@ -725,12 +723,6 @@ static void retroarch_parse_input(int argc, char *argv[]) settings->netplay.port = strtoul(optarg, NULL, 0); break; - case RA_OPT_SPECTATE: - retroarch_override_setting_set( - RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL); - settings->netplay.is_spectate = true; - break; - #if defined(HAVE_NETWORK_CMD) case RA_OPT_COMMAND: if (command_network_send((const char*)optarg)) From b5cd18707772ddeebca7a1d1271703c6434377d6 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 9 Dec 2016 23:11:18 -0500 Subject: [PATCH 25/89] Since there's now only one mode, removing netplay_callbacks entirely. --- network/netplay/netplay.c | 22 ++++++--------------- network/netplay/netplay_net.c | 33 ++++++------------------------- network/netplay/netplay_private.h | 12 +++-------- 3 files changed, 15 insertions(+), 52 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 88886cbcfe..537fde9737 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -315,11 +315,6 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) netplay->stall = 0; } -static bool netplay_info_cb(netplay_t* netplay, unsigned delay_frames) -{ - return netplay->net_cbs->info_cb(netplay, delay_frames); -} - /** * netplay_should_skip: * @netplay : pointer to netplay object @@ -1477,8 +1472,6 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); - netplay->net_cbs = netplay_get_cbs_net(); - if (!init_socket(netplay, direct_host, server, port)) { free(netplay); @@ -1493,12 +1486,10 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, if (!netplay->is_server) { - fprintf(stderr, "CONNECTION 0 %d\n", netplay->connections[0].active); + netplay_handshake_init_send(netplay, &netplay->connections[0]); + netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_INIT; } - if(!netplay_info_cb(netplay, delay_frames)) - goto error; - /* FIXME: Not really the right place to do this, socket initialization needs * to be fixed in general */ if (netplay->is_server) @@ -1652,7 +1643,7 @@ void netplay_free(netplay_t *netplay) **/ bool netplay_pre_frame(netplay_t *netplay) { - retro_assert(netplay && netplay->net_cbs->pre_frame); + retro_assert(netplay); /* FIXME: This is an ugly way to learn we're not paused anymore */ if (netplay->local_paused) @@ -1687,7 +1678,7 @@ bool netplay_pre_frame(netplay_t *netplay) } } - if (!netplay->net_cbs->pre_frame(netplay)) + if (!netplay_sync_pre_frame(netplay)) return false; return (!netplay->have_player_connections || @@ -1704,9 +1695,8 @@ bool netplay_pre_frame(netplay_t *netplay) **/ void netplay_post_frame(netplay_t *netplay) { - retro_assert(netplay && netplay->net_cbs->post_frame); - netplay->net_cbs->post_frame(netplay); - /* FIXME: Per-connection send buffer */ + retro_assert(netplay); + netplay_sync_post_frame(netplay); if (netplay->connections_size > 0 && netplay->connections[0].active && !netplay_send_flush(&netplay->connections[0].send_packet_buffer, diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 157eda9924..db06e6311d 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -66,12 +66,12 @@ static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *de } /** - * netplay_net_pre_frame: + * netplay_sync_pre_frame: * @netplay : pointer to netplay object * - * Pre-frame for Netplay (normal version). + * Pre-frame for Netplay synchronization. **/ -static bool netplay_net_pre_frame(netplay_t *netplay) +bool netplay_sync_pre_frame(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; @@ -235,13 +235,13 @@ process: } /** - * netplay_net_post_frame: + * netplay_sync_post_frame: * @netplay : pointer to netplay object * - * Post-frame for Netplay (normal version). + * Post-frame for Netplay synchronization. * We check if we have new input and replay from recorded input. **/ -static void netplay_net_post_frame(netplay_t *netplay) +void netplay_sync_post_frame(netplay_t *netplay) { netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; @@ -370,24 +370,3 @@ static void netplay_net_post_frame(netplay_t *netplay) core_unserialize(&serial_info); } } - -static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) -{ - if (!netplay_is_server(netplay)) - { - netplay_handshake_init_send(netplay, netplay->connections); - netplay->connections[0].mode = netplay->self_mode = NETPLAY_CONNECTION_INIT; - } - - return true; -} - -struct netplay_callbacks* netplay_get_cbs_net(void) -{ - static struct netplay_callbacks cbs = { - &netplay_net_pre_frame, - &netplay_net_post_frame, - &netplay_net_info_cb - }; - return &cbs; -} diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 085223c811..dce6a995f7 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -216,12 +216,6 @@ struct socket_buffer size_t read; }; -struct netplay_callbacks { - bool (*pre_frame) (netplay_t *netplay); - void (*post_frame)(netplay_t *netplay); - bool (*info_cb) (netplay_t *netplay, unsigned frames); -}; - /* Each connection gets a connection struct */ struct netplay_connection { @@ -376,8 +370,6 @@ struct netplay /* Frequency with which to check CRCs */ uint32_t check_frames; - - struct netplay_callbacks* net_cbs; }; void input_poll_net(void); @@ -463,7 +455,9 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *seri **/ bool netplay_disconnect(netplay_t *netplay); -struct netplay_callbacks* netplay_get_cbs_net(void); +bool netplay_sync_pre_frame(netplay_t *netplay); + +void netplay_sync_post_frame(netplay_t *netplay); /* Normally called at init time, unless the INITIALIZATION quirk is set */ bool netplay_init_serialization(netplay_t *netplay); From 53c46530aa1b77c808d30559a49f40f62ef4189a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 10 Dec 2016 20:36:57 -0500 Subject: [PATCH 26/89] Very first tidbits of true multiplayer support (minus actual multiple players so far) --- network/netplay/README | 29 +- network/netplay/netplay.c | 522 +++++++++++++++++++++++++----- network/netplay/netplay_common.c | 41 +-- network/netplay/netplay_net.c | 35 +- network/netplay/netplay_private.h | 61 ++-- 5 files changed, 539 insertions(+), 149 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index b58de284c0..bfe155525a 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -109,13 +109,18 @@ Command: INPUT Payload: { frame number: uint32 + is server data: 1 bit + player: 31 bits joypad input: uint32 analog 1 input: uint32 analog 2 input: uint32 } Description: Input state for each frame. Netplay must send an INPUT command for every - frame in order to function at all. + frame in order to function at all. Client's player value is ignored. Server + indicates which frames are its own input data because INPUT is a + synchronization point: No synchronization events from the given frame may + arrive after the server's input for the frame. Command: NICK Payload: @@ -128,7 +133,6 @@ Description: Command: SYNC Payload: { - connection number: uint32 frame number: uint32 sram: variable } @@ -139,23 +143,32 @@ Description: Command: SPECTATE Payload: None Description: - Request to enter spectate mode. + Request to enter spectate mode. The client should immediately consider + itself to be in spectator mode and send no further input. Command: PLAY Payload: None Description: - Request to enter player mode. + Request to enter player mode. The client must wait for a MODE command + before sending input. Command: MODE Payload: { frame number: uint32 - connection number: uint32 - player number: uint32 (MAX for spectator mode) + reserved: 14 bits + playing: 1 bit + you: 1 bit + player number: uint16 } Description: - Inform of a connection mode change (possibly the connection of the - receiving client). Only server-to-client. + Inform of a connection mode change (possibly of the receiving client). Only + server-to-client. Frame number is the first frame in which player data is + expected, or the first frame in which player data is not expected. In the + case of new players the frame number must be later than the last frame of + the server's own input that has been sent, and in the case of leaving + players the frame number must be later than the last frame of the relevant + player's input that has been transmitted. Command: CRC Payload: diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 537fde9737..f841a1b924 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -60,6 +60,10 @@ static bool in_netplay = false; static void announce_nat_traversal(netplay_t *netplay); #endif +static void netplay_send_raw_cmd_all(netplay_t *netplay, + struct netplay_connection *except, uint32_t cmd, const void *data, + size_t size); + static int init_tcp_connection(const struct addrinfo *res, bool server, struct sockaddr *other_addr, socklen_t addr_size) @@ -292,19 +296,18 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) if (!netplay->is_server) netplay->self_mode = NETPLAY_CONNECTION_NONE; - /* Check if we have any more players */ - if (netplay->have_player_connections && connection->mode == NETPLAY_CONNECTION_PLAYING) + /* Remove this player */ + if (connection->mode == NETPLAY_CONNECTION_PLAYING) { - size_t i; - netplay->have_player_connections = false; - for (i = 0; i < netplay->connections_size; i++) + netplay->connected_players &= ~(1<player); + + /* FIXME: Duplication */ + if (netplay->is_server) { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection->active && connection->mode == NETPLAY_CONNECTION_PLAYING) - { - netplay->have_player_connections = true; - break; - } + uint32_t payload[2]; + payload[0] = htonl(netplay->foo_read_frame_count[connection->player]); + payload[1] = htonl(connection->player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); } } @@ -329,7 +332,7 @@ static bool netplay_should_skip(netplay_t *netplay) { if (!netplay) return false; - return netplay->is_replay && (netplay->self_mode == NETPLAY_CONNECTION_PLAYING); + return netplay->is_replay && (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); } static bool netplay_can_poll(netplay_t *netplay) @@ -339,6 +342,44 @@ static bool netplay_can_poll(netplay_t *netplay) return netplay->can_poll; } +/* Update the global unread_ptr and unread_frame_count to correspond to the + * earliest unread frame count of any connected player */ +static void update_unread_ptr(netplay_t *netplay) +{ + if (!netplay->connected_players) + { + /* 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 player; + + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1<foo_read_frame_count[player] < new_unread_frame_count) + { + new_unread_ptr = netplay->foo_read_ptr[player]; + new_unread_frame_count = netplay->foo_read_frame_count[player]; + } + } + + 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; + } + + netplay->unread_ptr = new_unread_ptr; + netplay->unread_frame_count = new_unread_frame_count; + } +} + /* Send the current input state, either immediately after receiving it or after * finishing the initial handshake */ static void send_input(netplay_t *netplay, struct netplay_connection *connection) @@ -358,6 +399,36 @@ static void send_input(netplay_t *netplay, struct netplay_connection *connection } } +/* Send a specified input frame */ +static void send_input_frame(netplay_t *netplay, uint32_t frame, uint32_t *state) +{ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + uint32_t buffer[2 + WORDS_PER_FRAME]; + size_t i; + + buffer[0] = htonl(NETPLAY_CMD_INPUT); + buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); + buffer[2] = htonl(netplay->self_frame_count); + buffer[3] = htonl(netplay->self_player | + (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0)); + buffer[4] = htonl(state[0]); + buffer[5] = htonl(state[1]); + buffer[6] = htonl(state[2]); + + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + { + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + buffer, sizeof(buffer))) + hangup(netplay, connection); + } + } + } +} + /** * get_self_input_state: * @netplay : pointer to netplay object @@ -390,27 +461,36 @@ static bool get_self_input_state(netplay_t *netplay) retro_input_state_t cb = netplay->cbs.state_cb; for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) { - int16_t tmp = cb(settings->netplay.swap_input ? - 0 : !netplay->port, + int16_t tmp = cb(0, RETRO_DEVICE_JOYPAD, 0, i); state[0] |= tmp ? 1 << i : 0; } for (i = 0; i < 2; i++) { - int16_t tmp_x = cb(settings->netplay.swap_input ? - 0 : !netplay->port, + int16_t tmp_x = cb(0, RETRO_DEVICE_ANALOG, i, 0); - int16_t tmp_y = cb(settings->netplay.swap_input ? - 0 : !netplay->port, + int16_t tmp_y = cb(0, RETRO_DEVICE_ANALOG, i, 1); state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); } } + memcpy(ptr->self_state, state, sizeof(state)); + ptr->have_local = true; + + /* If we're playing, copy it in as real input */ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + memcpy(ptr->remote_input_state[netplay->self_player], state, + sizeof(state)); + ptr->have_remote[netplay->self_player] = true; + } + /* Here we construct the payload format: * frame { * uint32_t frame_number + * uint32_t player * uint32_t RETRO_DEVICE_JOYPAD state (top 16 bits zero) * uint32_t ANALOG state[0] * uint32_t ANALOG state[1] @@ -425,9 +505,11 @@ static bool get_self_input_state(netplay_t *netplay) netplay->input_packet_buffer[0] = htonl(NETPLAY_CMD_INPUT); netplay->input_packet_buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); - netplay->input_packet_buffer[3] = htonl(state[0]); - netplay->input_packet_buffer[4] = htonl(state[1]); - netplay->input_packet_buffer[5] = htonl(state[2]); + netplay->input_packet_buffer[3] = htonl(netplay->self_player | + (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0)); + netplay->input_packet_buffer[4] = htonl(state[0]); + netplay->input_packet_buffer[5] = htonl(state[1]); + netplay->input_packet_buffer[6] = htonl(state[2]); for (i = 0; i < netplay->connections_size; i++) { @@ -435,8 +517,6 @@ static bool get_self_input_state(netplay_t *netplay) send_input(netplay, &netplay->connections[i]); } - memcpy(ptr->self_state, state, sizeof(state)); - ptr->have_local = true; return true; } @@ -460,6 +540,24 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, return true; } +static 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)) + hangup(netplay, connection); + } + } +} + static bool netplay_cmd_nak(netplay_t *netplay, struct netplay_connection *connection) { @@ -497,6 +595,27 @@ bool netplay_cmd_request_savestate(netplay_t *netplay) NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); } +bool netplay_cmd_mode(netplay_t *netplay, + struct netplay_connection *connection, + enum rarch_netplay_connection_mode mode) +{ + uint32_t cmd; + switch (mode) + { + case NETPLAY_CONNECTION_SPECTATING: + cmd = NETPLAY_CMD_SPECTATE; + break; + + case NETPLAY_CONNECTION_PLAYING: + cmd = NETPLAY_CMD_PLAY; + break; + + default: + return false; + } + return netplay_send_raw_cmd(netplay, connection, cmd, NULL, 0); +} + static bool netplay_get_cmd(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { @@ -562,7 +681,9 @@ static bool netplay_get_cmd(netplay_t *netplay, case NETPLAY_CMD_INPUT: { uint32_t buffer[WORDS_PER_FRAME]; + uint32_t player; unsigned i; + struct delta_frame *dframe; if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) { @@ -579,23 +700,51 @@ static bool netplay_get_cmd(netplay_t *netplay, for (i = 0; i < WORDS_PER_FRAME; i++) buffer[i] = ntohl(buffer[i]); - if (buffer[0] < netplay->read_frame_count) + if (netplay->is_server) + { + /* Ignore the claimed player #, must be this client */ + if (connection->mode != NETPLAY_CONNECTION_PLAYING) + return netplay_cmd_nak(netplay, connection); + player = connection->player; + } + else + { + player = buffer[1] & ~NETPLAY_CMD_INPUT_BIT_SERVER; + } + + if (player >= MAX_USERS || !(netplay->connected_players & (1<foo_read_frame_count[player]) { /* We already had this, so ignore the new transmission */ break; } - else if (buffer[0] > netplay->read_frame_count) + else if (buffer[0] > netplay->foo_read_frame_count[player]) { /* Out of order = out of luck */ return netplay_cmd_nak(netplay, connection); } /* The data's good! */ - netplay->buffer[netplay->read_ptr].have_remote = true; - memcpy(netplay->buffer[netplay->read_ptr].real_input_state, - buffer + 1, sizeof(buffer) - sizeof(uint32_t)); - netplay->read_ptr = NEXT_PTR(netplay->read_ptr); - netplay->read_frame_count++; + dframe = &netplay->buffer[netplay->foo_read_ptr[player]]; + if (!netplay_delta_frame_ready(netplay, dframe, netplay->foo_read_frame_count[player])) + { + /* FIXME: Catastrophe! */ + return netplay_cmd_nak(netplay, connection); + } + dframe->have_remote[player] = true; + memcpy(dframe->remote_input_state[player], buffer + 2, + WORDS_PER_INPUT*sizeof(uint32_t)); + netplay->foo_read_ptr[player] = NEXT_PTR(netplay->foo_read_ptr[player]); + netplay->foo_read_frame_count[player]++; + + /* If this was server data, advance our server pointer too */ + if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER)) + { + netplay->server_ptr = netplay->foo_read_ptr[player]; + netplay->server_frame_count = netplay->foo_read_frame_count[player]; + } break; } @@ -612,9 +761,12 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } + if (netplay->is_server) + return netplay_cmd_nak(netplay, connection); + flip_frame = ntohl(flip_frame); - if (flip_frame < netplay->read_frame_count) + if (flip_frame < netplay->server_frame_count) { RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n"); return netplay_cmd_nak(netplay, connection); @@ -636,8 +788,183 @@ static bool netplay_get_cmd(netplay_t *netplay, break; case NETPLAY_CMD_SPECTATE: - RARCH_ERR("NETPLAY_CMD_SPECTATE unimplemented.\n"); - return netplay_cmd_nak(netplay, connection); + { + uint32_t payload[2]; + + if (!netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + if (connection->mode == NETPLAY_CONNECTION_PLAYING) + { + /* The frame we haven't received is their end frame */ + payload[0] = htonl(netplay->foo_read_frame_count[connection->player]); + + /* Mark them as not playing anymore */ + connection->mode = NETPLAY_CONNECTION_SPECTATING; + + /* Tell everyone */ + payload[1] = htonl(connection->player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + } + else + { + payload[0] = htonl(0); + } + + /* Tell the player even if they were confused */ + payload[1] = htonl(NETPLAY_CMD_MODE_BIT_YOU | connection->player); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + break; + } + + case NETPLAY_CMD_PLAY: + { + uint32_t payload[2]; + uint32_t player = 1; /* FIXME */ + payload[0] = htonl(netplay->self_frame_count + 1); + + if (!netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + if (connection->mode != NETPLAY_CONNECTION_PLAYING) + { + /* Mark them as playing */ + connection->mode = NETPLAY_CONNECTION_PLAYING; + connection->player = player; + netplay->connected_players |= 1<player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + } + + /* Tell the player even if they were confused */ + payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | + NETPLAY_CMD_MODE_BIT_YOU | connection->player); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + /* And expect their data */ + netplay->foo_read_ptr[player] = NEXT_PTR(netplay->self_ptr); + netplay->foo_read_frame_count[player] = netplay->self_frame_count + 1; + break; + } + + case NETPLAY_CMD_MODE: + { + uint32_t payload[2]; + uint32_t frame, mode, player; + size_t ptr; + struct delta_frame *dframe; +#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 (cmd_size != sizeof(payload) || + netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + RECV(payload, sizeof(payload)) + { + RARCH_ERR("NETPLAY_CMD_MODE failed to receive payload.\n"); + return netplay_cmd_nak(netplay, connection); + } + + if (netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + frame = ntohl(payload[0]); + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + + /* We're changing past input, so must replay it */ + if (frame < netplay->self_frame_count) + netplay->force_rewind = true; + + mode = ntohl(payload[1]); + player = mode & 0xFFFF; + if (player > MAX_USERS) + return netplay_cmd_nak(netplay, connection); + + if (mode & NETPLAY_CMD_MODE_BIT_YOU) + { + /* A change to me! */ + if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) + { + /* Hooray, I get to play now! */ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + return netplay_cmd_nak(netplay, connection); + + netplay->self_mode = NETPLAY_CONNECTION_PLAYING; + netplay->self_player = player; + + /* Fix up current frame info */ + if (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) + { + memcpy(dframe->remote_input_state[player], dframe->self_state, sizeof(dframe->self_state)); + dframe->have_remote[player] = true; + send_input_frame(netplay, frame, dframe->self_state); + if (dframe->frame == netplay->self_frame_count) break; + NEXT(); + } + + } + else + { + /* It wants future frames, make sure we don't capture or send intermediate ones */ + START(netplay->self_ptr); + while (dframe->used && dframe->frame < frame) + { + memset(dframe->self_state, 0, sizeof(dframe->self_state)); + memset(dframe->remote_input_state[player], 0, sizeof(dframe->self_state)); + dframe->have_local = true; + NEXT(); + } + + } + + } + else /* YOU && !PLAYING */ + { + /* I'm no longer playing, but I should already know this */ + if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING) + return netplay_cmd_nak(netplay, connection); + + } + + } + else /* !YOU */ + { + /* Somebody else is joining or parting */ + if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) + { + netplay->connected_players |= (1<foo_read_ptr[player] = netplay->server_ptr; + netplay->foo_read_frame_count[player] = netplay->server_frame_count; + } + else + { + netplay->connected_players &= ~(1<mode != NETPLAY_CONNECTION_PLAYING) + return netplay_cmd_nak(netplay, connection); + /* There is a subtlty in whether the load comes before or after the * current frame: * @@ -760,7 +1091,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } frame = ntohl(frame); - if (frame != netplay->read_frame_count) + if (frame != netplay->foo_read_frame_count[connection->player]) { RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); return netplay_cmd_nak(netplay, connection); @@ -789,7 +1120,8 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->decompression_backend->set_in(netplay->decompression_stream, netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); netplay->decompression_backend->set_out(netplay->decompression_stream, - (uint8_t*)netplay->buffer[netplay->read_ptr].state, netplay->state_size); + (uint8_t*)netplay->buffer[netplay->foo_read_ptr[connection->player]].state, + netplay->state_size); netplay->decompression_backend->trans(netplay->decompression_stream, true, &rd, &wn, NULL); @@ -801,14 +1133,14 @@ static bool netplay_get_cmd(netplay_t *netplay, * 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->self_ptr = PREV_PTR(netplay->read_ptr); + netplay->self_ptr = PREV_PTR(netplay->foo_read_ptr[connection->player]); netplay->self_frame_count = frame - 1; } /* And force rewind to it */ netplay->force_rewind = true; netplay->savestate_request_outstanding = false; - netplay->other_ptr = netplay->read_ptr; + netplay->other_ptr = netplay->foo_read_ptr[connection->player]; netplay->other_frame_count = frame; break; } @@ -865,9 +1197,10 @@ static int poll_input(netplay_t *netplay, bool block) /* If we're not ready for input, wait until we are. * Could fill the TCP buffer, stalling the other side. */ + /* FIXME: This won't work with uneven input, need to somehow stall */ if (netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->read_ptr], - netplay->read_frame_count)) + &netplay->buffer[netplay->unread_ptr], + netplay->unread_frame_count)) { for (i = 0; i < netplay->connections_size; i++) { @@ -879,8 +1212,10 @@ static int poll_input(netplay_t *netplay, bool block) if (block) { + update_unread_ptr(netplay); + /* If we were blocked for input, pass if we have this frame's input */ - if (netplay->read_frame_count > netplay->self_frame_count) + if (netplay->unread_frame_count > netplay->self_frame_count) break; /* If we're supposed to block but we didn't have enough input, wait for it */ @@ -924,7 +1259,7 @@ static int poll_input(netplay_t *netplay, bool block) */ void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim) { - size_t prev = PREV_PTR(netplay->read_ptr); + size_t prev = PREV_PTR(netplay->unread_ptr); struct delta_frame *pframe = &netplay->buffer[prev], *simframe = &netplay->buffer[sim_ptr]; if (resim) @@ -948,15 +1283,15 @@ void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim) (1U<simulated_input_state[0] & keep; - sim_state |= pframe->real_input_state[0] & ~keep; - simframe->simulated_input_state[0] = sim_state; + uint32_t sim_state = simframe->simulated_input_state[0][0] & keep; + sim_state |= pframe->remote_input_state[0][0] & ~keep; + simframe->simulated_input_state[0][0] = sim_state; } else { memcpy(simframe->simulated_input_state, - pframe->real_input_state, - sizeof(pframe->real_input_state)); + pframe->remote_input_state, + sizeof(pframe->remote_input_state)); } } @@ -982,7 +1317,7 @@ static bool netplay_poll(void) /* Read Netplay input, block if we're configured to stall for input every * frame */ if (netplay_data->delay_frames == 0 && - netplay_data->read_frame_count <= netplay_data->self_frame_count) + netplay_data->unread_frame_count <= netplay_data->self_frame_count) res = poll_input(netplay_data, true); else res = poll_input(netplay_data, false); @@ -1003,7 +1338,8 @@ static bool netplay_poll(void) switch (netplay_data->stall) { case NETPLAY_STALL_RUNNING_FAST: - if (netplay_data->read_frame_count >= netplay_data->self_frame_count) + update_unread_ptr(netplay_data); + if (netplay_data->unread_frame_count >= netplay_data->self_frame_count) netplay_data->stall = NETPLAY_STALL_NONE; break; @@ -1012,7 +1348,8 @@ static bool netplay_poll(void) break; default: /* not stalling */ - if (netplay_data->read_frame_count + netplay_data->delay_frames + update_unread_ptr(netplay_data); + if (netplay_data->unread_frame_count + netplay_data->delay_frames <= netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; @@ -1079,42 +1416,50 @@ static bool netplay_is_alive(void) { if (!netplay_data) return false; - return netplay_data->have_player_connections; + return !!netplay_data->connected_players; } -static bool netplay_flip_port(netplay_t *netplay, bool port) +static bool netplay_flip_port(netplay_t *netplay) { size_t frame = netplay->self_frame_count; if (netplay->flip_frame == 0) - return port; + return false; if (netplay->is_replay) frame = netplay->replay_frame_count; - return port ^ netplay->flip ^ (frame < netplay->flip_frame); + return netplay->flip ^ (frame < netplay->flip_frame); } static int16_t netplay_input_state(netplay_t *netplay, - bool port, unsigned device, + unsigned port, unsigned device, unsigned idx, unsigned id) { size_t ptr = netplay->is_replay ? netplay->replay_ptr : netplay->self_ptr; - const uint32_t *curr_input_state = netplay->buffer[ptr].self_state; + const uint32_t *curr_input_state = NULL; - if (netplay->port == (netplay_flip_port(netplay, port) ? 1 : 0)) + if (port <= 1) { - if (netplay->buffer[ptr].have_remote) - { - netplay->buffer[ptr].used_real = true; - curr_input_state = netplay->buffer[ptr].real_input_state; - } - else - { - curr_input_state = netplay->buffer[ptr].simulated_input_state; - } + /* Possibly flip the port */ + if (netplay_flip_port(netplay)) + port ^= 1; + } + else if (port >= MAX_USERS) + { + return 0; + } + + if (netplay->buffer[ptr].have_remote[port]) + { + netplay->buffer[ptr].used_real[port] = true; + curr_input_state = netplay->buffer[ptr].remote_input_state[port]; + } + else + { + curr_input_state = netplay->buffer[ptr].simulated_input_state[port]; } switch (device) @@ -1137,13 +1482,7 @@ int16_t input_state_net(unsigned port, unsigned device, unsigned idx, unsigned id) { if (netplay_is_alive()) - { - /* Only two players for now. */ - if (port > 1) - return 0; - return netplay_input_state(netplay_data, port, device, idx, id); - } return netplay_data->cbs.state_cb(port, device, idx, id); } @@ -1448,7 +1787,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->listen_fd = -1; netplay->tcp_port = port; netplay->cbs = *cb; - netplay->port = server ? 0 : 1; + netplay->connected_players = 0; netplay->is_server = server == NULL; netplay->nat_traversal = netplay->is_server ? nat_traversal : false; netplay->delay_frames = delay_frames; @@ -1681,7 +2020,7 @@ bool netplay_pre_frame(netplay_t *netplay) if (!netplay_sync_pre_frame(netplay)) return false; - return (!netplay->have_player_connections || + return (!netplay->connected_players || (!netplay->stall && !netplay->remote_paused)); } @@ -1695,13 +2034,19 @@ bool netplay_pre_frame(netplay_t *netplay) **/ void netplay_post_frame(netplay_t *netplay) { + size_t i; retro_assert(netplay); + update_unread_ptr(netplay); netplay_sync_post_frame(netplay); - if (netplay->connections_size > 0 && - netplay->connections[0].active && - !netplay_send_flush(&netplay->connections[0].send_packet_buffer, - netplay->connections[0].fd, false)) - hangup(netplay, &netplay->connections[0]); + + 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)) + hangup(netplay, &netplay->connections[0]); + } } /** @@ -1785,10 +2130,25 @@ void netplay_load_savestate(netplay_t *netplay, /* We need to ignore any intervening data from the other side, * and never rewind past this */ - if (netplay->read_frame_count < netplay->self_frame_count) + update_unread_ptr(netplay); + if (netplay->unread_frame_count < netplay->self_frame_count) { - netplay->read_ptr = netplay->self_ptr; - netplay->read_frame_count = netplay->self_frame_count; + uint32_t player; + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1<foo_read_frame_count[player] < netplay->self_frame_count) + { + netplay->foo_read_ptr[player] = netplay->self_ptr; + netplay->foo_read_frame_count[player] = netplay->self_frame_count; + } + } + if (netplay->server_frame_count < netplay->self_frame_count) + { + netplay->server_ptr = netplay->self_ptr; + netplay->server_frame_count = netplay->self_frame_count; + } + update_unread_ptr(netplay); } if (netplay->other_frame_count < netplay->self_frame_count) { diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 7f59c82265..45330622b9 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -283,9 +283,6 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio /* Unstall if we were waiting for this */ if (netplay->stall == NETPLAY_STALL_NO_CONNECTION) netplay->stall = 0; - - connection->mode = NETPLAY_CONNECTION_PLAYING; - netplay->have_player_connections = true; } bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) @@ -322,16 +319,15 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c if (netplay->is_server) { /* If we're the server, now we send sync info */ - uint32_t cmd[4]; + uint32_t cmd[3]; retro_ctx_memory_info_t mem_info; mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(2*sizeof(uint32_t) + mem_info.size); + cmd[1] = htonl(sizeof(uint32_t) + mem_info.size); cmd[2] = htonl(netplay->self_frame_count); - cmd[3] = htonl(1); if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, sizeof(cmd))) @@ -342,13 +338,8 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c false)) return false; - /* They start one frame after us */ - netplay->other_frame_count = netplay->read_frame_count = - netplay->self_frame_count + 1; - netplay->other_ptr = netplay->read_ptr = - NEXT_PTR(netplay->self_ptr); - /* Now we're ready! */ + connection->mode = NETPLAY_CONNECTION_SPECTATING; netplay_handshake_ready(netplay, connection); } @@ -368,7 +359,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c { uint32_t cmd[2]; uint32_t local_sram_size, remote_sram_size; - uint32_t new_frame_count, self_connection_num; + uint32_t new_frame_count; size_t i; ssize_t recvd; retro_ctx_memory_info_t mem_info; @@ -378,7 +369,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c /* Only expecting a sync command */ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || - ntohl(cmd[1]) < 2*sizeof(uint32_t)) + ntohl(cmd[1]) < sizeof(uint32_t)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); @@ -390,14 +381,11 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c return false; new_frame_count = ntohl(new_frame_count); - /* And the connection number */ - RECV(&self_connection_num, sizeof(self_connection_num)) - return false; - netplay->self_connection_num = ntohl(self_connection_num); - /* Reset our frame buffer so it's consistent between server and client */ + /* FIXME: Assuming server is player 0 */ netplay->self_frame_count = netplay->other_frame_count = - netplay->read_frame_count = new_frame_count; + netplay->unread_frame_count = netplay->server_frame_count = + netplay->foo_read_frame_count[0] = new_frame_count; for (i = 0; i < netplay->buffer_size; i++) { struct delta_frame *ptr = &netplay->buffer[i]; @@ -409,7 +397,8 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c netplay_delta_frame_ready(netplay, ptr, 0); ptr->frame = new_frame_count; ptr->have_local = true; - netplay->other_ptr = netplay->read_ptr = i; + netplay->other_ptr = netplay->unread_ptr = netplay->server_ptr = + netplay->foo_read_ptr[0] = i; } } @@ -419,7 +408,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c core_get_memory(&mem_info); local_sram_size = mem_info.size; - remote_sram_size = ntohl(cmd[1]) - 2*sizeof(uint32_t); + remote_sram_size = ntohl(cmd[1]) - sizeof(uint32_t); if (local_sram_size != 0 && local_sram_size == remote_sram_size) { @@ -452,11 +441,15 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c } /* We're ready! */ - netplay->self_mode = NETPLAY_CONNECTION_PLAYING; + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + connection->mode = NETPLAY_CONNECTION_PLAYING; + netplay->connected_players = 1; netplay_handshake_ready(netplay, connection); *had_input = true; netplay_recv_flush(&connection->recv_packet_buffer); - return true; + + /* Ask to go to player mode */ + return netplay_cmd_mode(netplay, connection, NETPLAY_CONNECTION_PLAYING); } bool netplay_is_server(netplay_t* netplay) diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index db06e6311d..6784370581 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -247,10 +247,10 @@ void netplay_sync_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (!netplay->have_player_connections) + if (!netplay->connected_players) { - netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; - netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; + netplay->other_frame_count = netplay->self_frame_count; + netplay->other_ptr = netplay->self_ptr; return; } @@ -259,15 +259,20 @@ void netplay_sync_post_frame(netplay_t *netplay) { /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ - while (netplay->other_frame_count < netplay->read_frame_count && - netplay->other_frame_count < netplay->self_frame_count) + while (netplay->other_frame_count < netplay->unread_frame_count && + netplay->other_frame_count < netplay->self_frame_count) { struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; + size_t i; - if (memcmp(ptr->simulated_input_state, ptr->real_input_state, - sizeof(ptr->real_input_state)) != 0 - && !ptr->used_real) - break; + for (i = 0; i < MAX_USERS; i++) + { + if (memcmp(ptr->simulated_input_state[i], ptr->remote_input_state[i], + sizeof(ptr->remote_input_state[i])) != 0 + && !ptr->used_real[i]) + break; + } + if (i != MAX_USERS) break; netplay_handle_frame_hash(netplay, ptr); netplay->other_ptr = NEXT_PTR(netplay->other_ptr); netplay->other_frame_count++; @@ -277,7 +282,7 @@ void netplay_sync_post_frame(netplay_t *netplay) /* Now replay the real input if we've gotten ahead of it */ if (netplay->force_rewind || - (netplay->other_frame_count < netplay->read_frame_count && + (netplay->other_frame_count < netplay->unread_frame_count && netplay->other_frame_count < netplay->self_frame_count)) { retro_ctx_serialize_info_t serial_info; @@ -310,11 +315,11 @@ void netplay_sync_post_frame(netplay_t *netplay) /* Remember the current state */ memset(serial_info.data, 0, serial_info.size); core_serialize(&serial_info); - if (netplay->replay_frame_count < netplay->read_frame_count) + if (netplay->replay_frame_count < netplay->unread_frame_count) netplay_handle_frame_hash(netplay, ptr); /* Simulate this frame's input */ - if (netplay->replay_frame_count >= netplay->read_frame_count) + if (netplay->replay_frame_count >= netplay->unread_frame_count) netplay_simulate_input(netplay, netplay->replay_ptr, true); autosave_lock(); @@ -340,10 +345,10 @@ void netplay_sync_post_frame(netplay_t *netplay) #endif } - if (netplay->read_frame_count < netplay->self_frame_count) + if (netplay->unread_frame_count < netplay->self_frame_count) { - netplay->other_ptr = netplay->read_ptr; - netplay->other_frame_count = netplay->read_frame_count; + netplay->other_ptr = netplay->unread_ptr; + netplay->other_frame_count = netplay->unread_frame_count; } else { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index dce6a995f7..0b9bfc97b7 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -34,12 +34,14 @@ #define HAVE_IPV6 #endif -#define WORDS_PER_FRAME 4 /* Allows us to send 128 bits worth of state per frame. */ -#define RARCH_DEFAULT_PORT 55435 -#define RARCH_DEFAULT_NICK "Anonymous" +#define WORDS_PER_INPUT 3 /* Buttons, left stick, right stick */ +#define WORDS_PER_FRAME (WORDS_PER_INPUT+2) /* + frameno, playerno */ #define NETPLAY_PROTOCOL_VERSION 4 +#define RARCH_DEFAULT_PORT 55435 +#define RARCH_DEFAULT_NICK "Anonymous" + #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) @@ -145,6 +147,10 @@ enum netplay_cmd NETPLAY_CMD_CFG_ACK = 0x0062 }; +#define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31) +#define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<17) +#define NETPLAY_CMD_MODE_BIT_YOU (1U<<16) + /* These are the configurations sent by NETPLAY_CMD_CFG. */ enum netplay_cmd_cfg { @@ -183,6 +189,8 @@ enum rarch_netplay_stall_reason NETPLAY_STALL_NO_CONNECTION }; +typedef uint32_t netplay_input_state_t[WORDS_PER_INPUT]; + struct delta_frame { bool used; /* a bit derpy, but this is how we know if the delta's been used at all */ @@ -194,18 +202,18 @@ struct delta_frame /* The CRC-32 of the serialized state if we've calculated it, else 0 */ uint32_t crc; - uint32_t real_input_state[WORDS_PER_FRAME - 1]; - uint32_t simulated_input_state[WORDS_PER_FRAME - 1]; - uint32_t self_state[WORDS_PER_FRAME - 1]; + netplay_input_state_t remote_input_state[MAX_USERS]; + netplay_input_state_t simulated_input_state[MAX_USERS]; + netplay_input_state_t self_state; /* Have we read local input? */ bool have_local; /* Have we read the real remote input? */ - bool have_remote; + bool have_remote[MAX_USERS]; /* Is the current state as of self_frame_count using the real remote data? */ - bool used_real; + bool used_real[MAX_USERS]; }; struct socket_buffer @@ -237,7 +245,7 @@ struct netplay_connection /* Mode of the connection */ enum rarch_netplay_connection_mode mode; - /* Player # of connected player, or -1 if not a player */ + /* Player # of connected player */ int player; /* Force send a savestate, to this connection only */ @@ -255,8 +263,8 @@ struct netplay /* TCP connection for listening (server only) */ int listen_fd; - /* Our connection number */ - uint32_t self_connection_num; + /* Our player number */ + uint32_t self_player; /* Our mode and status */ enum rarch_netplay_connection_mode self_mode; @@ -266,9 +274,9 @@ struct netplay size_t connections_size; struct netplay_connection one_connection; /* Client only */ - /* True if any of our connections are players (i.e., we actually need to do - * netplay) */ - bool have_player_connections; + /* Bitmap of players with controllers (whether local or remote) (low bit is + * player 1) */ + int connected_players; struct retro_callbacks cbs; @@ -279,9 +287,6 @@ struct netplay bool nat_traversal; struct natt_status nat_traversal_state; - /* Which port is governed by netplay (other user)? */ - unsigned port; - struct delta_frame *buffer; size_t buffer_size; @@ -306,11 +311,21 @@ struct netplay size_t other_ptr; uint32_t other_frame_count; - /* Pointer to where we are reading. - * Generally, other_ptr <= read_ptr <= self_ptr, but read_ptr can get ahead + /* Pointer to the first frame for which we're missing the data of at least + * one connected player excluding ourself. + * Generally, other_ptr <= unread_ptr <= self_ptr, but unread_ptr can get ahead * of self_ptr if the peer is running fast. */ - size_t read_ptr; - uint32_t read_frame_count; + size_t unread_ptr; + uint32_t unread_frame_count; + + /* Pointer to the next frame to read from each player */ + size_t foo_read_ptr[MAX_USERS]; + uint32_t foo_read_frame_count[MAX_USERS]; + + /* Pointer to the next frame to read from the server (as it might not be a + * player but still synchronizes) */ + size_t server_ptr; + uint32_t server_frame_count; /* A pointer used temporarily for replay. */ size_t replay_ptr; @@ -492,6 +507,10 @@ bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta); bool netplay_cmd_request_savestate(netplay_t *netplay); +bool netplay_cmd_mode(netplay_t *netplay, + struct netplay_connection *connection, + enum rarch_netplay_connection_mode mode); + /* DISCOVERY: */ bool netplay_lan_ad_server(netplay_t *netplay); From 2cc8c5c46735e5bda78c42246c71b8b9f6b6072f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 10 Dec 2016 20:38:59 -0500 Subject: [PATCH 27/89] Removing silly foo_ names used to help migration. --- network/netplay/netplay.c | 48 +++++++++++++++---------------- network/netplay/netplay_common.c | 4 +-- network/netplay/netplay_private.h | 4 +-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index f841a1b924..2cdaa58c73 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -305,7 +305,7 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) if (netplay->is_server) { uint32_t payload[2]; - payload[0] = htonl(netplay->foo_read_frame_count[connection->player]); + payload[0] = htonl(netplay->read_frame_count[connection->player]); payload[1] = htonl(connection->player); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); } @@ -362,10 +362,10 @@ static void update_unread_ptr(netplay_t *netplay) for (player = 0; player < MAX_USERS; player++) { if (!(netplay->connected_players & (1<foo_read_frame_count[player] < new_unread_frame_count) + if (netplay->read_frame_count[player] < new_unread_frame_count) { - new_unread_ptr = netplay->foo_read_ptr[player]; - new_unread_frame_count = netplay->foo_read_frame_count[player]; + new_unread_ptr = netplay->read_ptr[player]; + new_unread_frame_count = netplay->read_frame_count[player]; } } @@ -715,20 +715,20 @@ static bool netplay_get_cmd(netplay_t *netplay, if (player >= MAX_USERS || !(netplay->connected_players & (1<foo_read_frame_count[player]) + if (buffer[0] < netplay->read_frame_count[player]) { /* We already had this, so ignore the new transmission */ break; } - else if (buffer[0] > netplay->foo_read_frame_count[player]) + else if (buffer[0] > netplay->read_frame_count[player]) { /* Out of order = out of luck */ return netplay_cmd_nak(netplay, connection); } /* The data's good! */ - dframe = &netplay->buffer[netplay->foo_read_ptr[player]]; - if (!netplay_delta_frame_ready(netplay, dframe, netplay->foo_read_frame_count[player])) + dframe = &netplay->buffer[netplay->read_ptr[player]]; + if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player])) { /* FIXME: Catastrophe! */ return netplay_cmd_nak(netplay, connection); @@ -736,14 +736,14 @@ static bool netplay_get_cmd(netplay_t *netplay, dframe->have_remote[player] = true; memcpy(dframe->remote_input_state[player], buffer + 2, WORDS_PER_INPUT*sizeof(uint32_t)); - netplay->foo_read_ptr[player] = NEXT_PTR(netplay->foo_read_ptr[player]); - netplay->foo_read_frame_count[player]++; + netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); + netplay->read_frame_count[player]++; /* If this was server data, advance our server pointer too */ if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER)) { - netplay->server_ptr = netplay->foo_read_ptr[player]; - netplay->server_frame_count = netplay->foo_read_frame_count[player]; + netplay->server_ptr = netplay->read_ptr[player]; + netplay->server_frame_count = netplay->read_frame_count[player]; } break; } @@ -797,7 +797,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (connection->mode == NETPLAY_CONNECTION_PLAYING) { /* The frame we haven't received is their end frame */ - payload[0] = htonl(netplay->foo_read_frame_count[connection->player]); + payload[0] = htonl(netplay->read_frame_count[connection->player]); /* Mark them as not playing anymore */ connection->mode = NETPLAY_CONNECTION_SPECTATING; @@ -844,8 +844,8 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); /* And expect their data */ - netplay->foo_read_ptr[player] = NEXT_PTR(netplay->self_ptr); - netplay->foo_read_frame_count[player] = netplay->self_frame_count + 1; + netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr); + netplay->read_frame_count[player] = netplay->self_frame_count + 1; break; } @@ -950,8 +950,8 @@ static bool netplay_get_cmd(netplay_t *netplay, { netplay->connected_players |= (1<foo_read_ptr[player] = netplay->server_ptr; - netplay->foo_read_frame_count[player] = netplay->server_frame_count; + netplay->read_ptr[player] = netplay->server_ptr; + netplay->read_frame_count[player] = netplay->server_frame_count; } else { @@ -1091,7 +1091,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } frame = ntohl(frame); - if (frame != netplay->foo_read_frame_count[connection->player]) + if (frame != netplay->read_frame_count[connection->player]) { RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); return netplay_cmd_nak(netplay, connection); @@ -1120,7 +1120,7 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->decompression_backend->set_in(netplay->decompression_stream, netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); netplay->decompression_backend->set_out(netplay->decompression_stream, - (uint8_t*)netplay->buffer[netplay->foo_read_ptr[connection->player]].state, + (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state, netplay->state_size); netplay->decompression_backend->trans(netplay->decompression_stream, true, &rd, &wn, NULL); @@ -1133,14 +1133,14 @@ static bool netplay_get_cmd(netplay_t *netplay, * 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->self_ptr = PREV_PTR(netplay->foo_read_ptr[connection->player]); + netplay->self_ptr = PREV_PTR(netplay->read_ptr[connection->player]); netplay->self_frame_count = frame - 1; } /* And force rewind to it */ netplay->force_rewind = true; netplay->savestate_request_outstanding = false; - netplay->other_ptr = netplay->foo_read_ptr[connection->player]; + netplay->other_ptr = netplay->read_ptr[connection->player]; netplay->other_frame_count = frame; break; } @@ -2137,10 +2137,10 @@ void netplay_load_savestate(netplay_t *netplay, for (player = 0; player < MAX_USERS; player++) { if (!(netplay->connected_players & (1<foo_read_frame_count[player] < netplay->self_frame_count) + if (netplay->read_frame_count[player] < netplay->self_frame_count) { - netplay->foo_read_ptr[player] = netplay->self_ptr; - netplay->foo_read_frame_count[player] = netplay->self_frame_count; + netplay->read_ptr[player] = netplay->self_ptr; + netplay->read_frame_count[player] = netplay->self_frame_count; } } if (netplay->server_frame_count < netplay->self_frame_count) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 45330622b9..93a2dd3681 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -385,7 +385,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c /* FIXME: Assuming server is player 0 */ netplay->self_frame_count = netplay->other_frame_count = netplay->unread_frame_count = netplay->server_frame_count = - netplay->foo_read_frame_count[0] = new_frame_count; + netplay->read_frame_count[0] = new_frame_count; for (i = 0; i < netplay->buffer_size; i++) { struct delta_frame *ptr = &netplay->buffer[i]; @@ -398,7 +398,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c ptr->frame = new_frame_count; ptr->have_local = true; netplay->other_ptr = netplay->unread_ptr = netplay->server_ptr = - netplay->foo_read_ptr[0] = i; + netplay->read_ptr[0] = i; } } diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 0b9bfc97b7..be547df9e0 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -319,8 +319,8 @@ struct netplay uint32_t unread_frame_count; /* Pointer to the next frame to read from each player */ - size_t foo_read_ptr[MAX_USERS]; - uint32_t foo_read_frame_count[MAX_USERS]; + size_t read_ptr[MAX_USERS]; + uint32_t read_frame_count[MAX_USERS]; /* Pointer to the next frame to read from the server (as it might not be a * player but still synchronizes) */ From b51cf8be2bb4149c56b12898b5c682a53b1ebe66 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 11 Dec 2016 11:00:17 -0500 Subject: [PATCH 28/89] Real, actual (almost) >2-player support. --- network/netplay/netplay.c | 71 ++++++++++++++++++++------------ network/netplay/netplay_common.c | 2 +- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 2cdaa58c73..917298156b 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -400,31 +400,30 @@ static void send_input(netplay_t *netplay, struct netplay_connection *connection } /* Send a specified input frame */ -static void send_input_frame(netplay_t *netplay, uint32_t frame, uint32_t *state) +static void send_input_frame(netplay_t *netplay, + struct netplay_connection *except, uint32_t frame, uint32_t player, + uint32_t *state) { - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + uint32_t buffer[2 + WORDS_PER_FRAME]; + size_t i; + + buffer[0] = htonl(NETPLAY_CMD_INPUT); + buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); + buffer[2] = htonl(frame); + buffer[3] = htonl(player); + buffer[4] = htonl(state[0]); + buffer[5] = htonl(state[1]); + buffer[6] = htonl(state[2]); + + for (i = 0; i < netplay->connections_size; i++) { - uint32_t buffer[2 + WORDS_PER_FRAME]; - size_t i; - - buffer[0] = htonl(NETPLAY_CMD_INPUT); - buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); - buffer[2] = htonl(netplay->self_frame_count); - buffer[3] = htonl(netplay->self_player | - (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0)); - buffer[4] = htonl(state[0]); - buffer[5] = htonl(state[1]); - buffer[6] = htonl(state[2]); - - 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) { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) - { - if (!netplay_send(&connection->send_packet_buffer, connection->fd, + if (!netplay_send(&connection->send_packet_buffer, connection->fd, buffer, sizeof(buffer))) - hangup(netplay, connection); - } + hangup(netplay, connection); } } } @@ -439,8 +438,8 @@ static void send_input_frame(netplay_t *netplay, uint32_t frame, uint32_t *state **/ static bool get_self_input_state(netplay_t *netplay) { - uint32_t state[WORDS_PER_FRAME - 1] = {0, 0, 0}; - struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; + uint32_t state[WORDS_PER_INPUT] = {0, 0, 0}; + struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; size_t i; if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) @@ -733,12 +732,16 @@ static bool netplay_get_cmd(netplay_t *netplay, /* FIXME: Catastrophe! */ return netplay_cmd_nak(netplay, connection); } - dframe->have_remote[player] = true; memcpy(dframe->remote_input_state[player], buffer + 2, WORDS_PER_INPUT*sizeof(uint32_t)); + dframe->have_remote[player] = true; netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); netplay->read_frame_count[player]++; + if (netplay->is_server) + /* Forward it on */ + send_input_frame(netplay, connection, buffer[0], player, buffer + 2); + /* If this was server data, advance our server pointer too */ if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER)) { @@ -820,12 +823,26 @@ static bool netplay_get_cmd(netplay_t *netplay, case NETPLAY_CMD_PLAY: { uint32_t payload[2]; - uint32_t player = 1; /* FIXME */ + uint32_t player = 0; payload[0] = htonl(netplay->self_frame_count + 1); if (!netplay->is_server) return netplay_cmd_nak(netplay, connection); + /* Find an available player slot */ + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING && + netplay->self_player == player) && + !(netplay->connected_players & player)) + break; + } + if (player == MAX_USERS) + { + /* FIXME */ + return netplay_cmd_nak(netplay, connection); + } + if (connection->mode != NETPLAY_CONNECTION_PLAYING) { /* Mark them as playing */ @@ -913,7 +930,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { memcpy(dframe->remote_input_state[player], dframe->self_state, sizeof(dframe->self_state)); dframe->have_remote[player] = true; - send_input_frame(netplay, frame, dframe->self_state); + send_input_frame(netplay, NULL, dframe->frame, player, dframe->self_state); if (dframe->frame == netplay->self_frame_count) break; NEXT(); } @@ -2163,6 +2180,8 @@ void netplay_load_savestate(netplay_t *netplay, return; /* Compress it */ + if (!netplay->compression_backend) + return; netplay->compression_backend->set_in(netplay->compression_stream, (const uint8_t*)serial_info->data_const, serial_info->size); netplay->compression_backend->set_out(netplay->compression_stream, diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 93a2dd3681..ad54fa9551 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -386,6 +386,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c netplay->self_frame_count = netplay->other_frame_count = netplay->unread_frame_count = netplay->server_frame_count = netplay->read_frame_count[0] = new_frame_count; + netplay->connected_players = 1; for (i = 0; i < netplay->buffer_size; i++) { struct delta_frame *ptr = &netplay->buffer[i]; @@ -443,7 +444,6 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c /* We're ready! */ netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; connection->mode = NETPLAY_CONNECTION_PLAYING; - netplay->connected_players = 1; netplay_handshake_ready(netplay, connection); *had_input = true; netplay_recv_flush(&connection->recv_packet_buffer); From d373684bd5666332a41eed08a15aca228770175f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 11 Dec 2016 20:33:35 -0500 Subject: [PATCH 29/89] Off-by-one --- network/netplay/netplay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 917298156b..6cf6522def 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -906,7 +906,7 @@ static bool netplay_get_cmd(netplay_t *netplay, mode = ntohl(payload[1]); player = mode & 0xFFFF; - if (player > MAX_USERS) + if (player >= MAX_USERS) return netplay_cmd_nak(netplay, connection); if (mode & NETPLAY_CMD_MODE_BIT_YOU) From 479955a085e1c83dd7e31a2ddbb1669fed53fad6 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 11 Dec 2016 21:34:17 -0500 Subject: [PATCH 30/89] More 3-player support. The initial connection is wonky with delay_frames>0 --- network/netplay/README | 6 +++- network/netplay/netplay_common.c | 49 +++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index bfe155525a..8fa349d296 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -134,11 +134,15 @@ Command: SYNC Payload: { frame number: uint32 + connected players: uint32 + flip frame: uint32 sram: variable } Description: Initial state synchronization. Mandatory handshake command from server to - client only. Sent after receiving client's NICK. + client only. Sent after receiving client's NICK. Connected players is a + bitmap with the lowest bit being player 0. Flip frame is 0 if players + aren't flipped. Command: SPECTATE Payload: None diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index ad54fa9551..9f8fefbc1e 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -319,15 +319,24 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c if (netplay->is_server) { /* If we're the server, now we send sync info */ - uint32_t cmd[3]; + uint32_t cmd[5]; + uint32_t connected_players; retro_ctx_memory_info_t mem_info; mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(sizeof(uint32_t) + mem_info.size); + cmd[1] = htonl(3*sizeof(uint32_t) + mem_info.size); cmd[2] = htonl(netplay->self_frame_count); + connected_players = netplay->connected_players; + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + connected_players |= 1<self_player; + cmd[3] = htonl(connected_players); + if (netplay->flip) + cmd[4] = htonl(netplay->flip_frame); + else + cmd[4] = htonl(0); if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, sizeof(cmd))) @@ -358,8 +367,8 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { uint32_t cmd[2]; + uint32_t new_frame_count, connected_players, flip_frame; uint32_t local_sram_size, remote_sram_size; - uint32_t new_frame_count; size_t i; ssize_t recvd; retro_ctx_memory_info_t mem_info; @@ -369,7 +378,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c /* Only expecting a sync command */ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || - ntohl(cmd[1]) < sizeof(uint32_t)) + ntohl(cmd[1]) < 3*sizeof(uint32_t)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); @@ -381,12 +390,23 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c return false; new_frame_count = ntohl(new_frame_count); - /* Reset our frame buffer so it's consistent between server and client */ - /* FIXME: Assuming server is player 0 */ + /* Get the connected players */ + RECV(&connected_players, sizeof(connected_players)) + return false; + connected_players = ntohl(connected_players); + netplay->connected_players = connected_players; + + /* And the flip state */ + RECV(&flip_frame, sizeof(flip_frame)) + return false; + flip_frame = ntohl(flip_frame); + netplay->flip = !!flip_frame; + netplay->flip_frame = flip_frame; + + /* Set our frame counters as requested */ netplay->self_frame_count = netplay->other_frame_count = netplay->unread_frame_count = netplay->server_frame_count = - netplay->read_frame_count[0] = new_frame_count; - netplay->connected_players = 1; + new_frame_count; for (i = 0; i < netplay->buffer_size; i++) { struct delta_frame *ptr = &netplay->buffer[i]; @@ -398,18 +418,25 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c netplay_delta_frame_ready(netplay, ptr, 0); ptr->frame = new_frame_count; ptr->have_local = true; - netplay->other_ptr = netplay->unread_ptr = netplay->server_ptr = - netplay->read_ptr[0] = i; + netplay->other_ptr = netplay->unread_ptr = netplay->server_ptr = i; } } + for (i = 0; i < MAX_USERS; i++) + { + if (connected_players & (1<read_ptr[i] = netplay->self_ptr; + netplay->read_frame_count[i] = netplay->self_frame_count; + } + } /* Now check the SRAM */ mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); local_sram_size = mem_info.size; - remote_sram_size = ntohl(cmd[1]) - sizeof(uint32_t); + remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t); if (local_sram_size != 0 && local_sram_size == remote_sram_size) { From f6630ad85afdd1b10160611ff679c701cef347ca Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 09:50:39 -0500 Subject: [PATCH 31/89] More fixups for >2 player mode. Mostly was just sending packets that shouldn't have been sent. --- network/netplay/netplay.c | 164 +++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 63 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 6cf6522def..1fa0335264 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -380,29 +380,10 @@ static void update_unread_ptr(netplay_t *netplay) } } -/* Send the current input state, either immediately after receiving it or after - * finishing the initial handshake */ -static void send_input(netplay_t *netplay, struct netplay_connection *connection) -{ - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING && - connection->mode >= NETPLAY_CONNECTION_CONNECTED) - { - netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - netplay->input_packet_buffer, - sizeof(netplay->input_packet_buffer)) || - !netplay_send_flush(&connection->send_packet_buffer, connection->fd, - false)) - { - hangup(netplay, connection); - } - } -} - -/* Send a specified input frame */ -static void send_input_frame(netplay_t *netplay, - struct netplay_connection *except, uint32_t frame, uint32_t player, - uint32_t *state) +/* Send the specified input data */ +static bool send_input_frame(netplay_t *netplay, + struct netplay_connection *only, struct netplay_connection *except, + uint32_t frame, uint32_t player, uint32_t *state) { uint32_t buffer[2 + WORDS_PER_FRAME]; size_t i; @@ -415,17 +396,73 @@ static void send_input_frame(netplay_t *netplay, buffer[5] = htonl(state[1]); buffer[6] = htonl(state[2]); - for (i = 0; i < netplay->connections_size; i++) + if (only) { - struct netplay_connection *connection = &netplay->connections[i]; - if (connection == except) continue; - if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + if (only->mode == NETPLAY_CONNECTION_PLAYING && only->player == player) { - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - buffer, sizeof(buffer))) - hangup(netplay, connection); + hangup(netplay, only); + return false; + } + if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer))) + { + 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 || + connection->player != player)) + { + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + buffer, sizeof(buffer))) + hangup(netplay, connection); + } + } + } + + return true; +} + +/* Send the current input frame */ +static bool send_cur_input(netplay_t *netplay, struct netplay_connection *connection) +{ + struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr]; + uint32_t player; + + for (player = 0; player < MAX_USERS; player++) + { + if (connection->mode == NETPLAY_CONNECTION_PLAYING && + connection->player == player) + continue; + if ((netplay->connected_players & (1<have_remote[player]) + { + if (!send_input_frame(netplay, connection, NULL, + netplay->self_frame_count, player, + dframe->remote_input_state[player])) + return false; + } + } + else if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING && + netplay->self_player == player) + { + if (!send_input_frame(netplay, connection, NULL, + netplay->self_frame_count, + (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | player, + dframe->self_state)) + return false; + } + } + + return true; } /** @@ -486,34 +523,12 @@ static bool get_self_input_state(netplay_t *netplay) ptr->have_remote[netplay->self_player] = true; } - /* Here we construct the payload format: - * frame { - * uint32_t frame_number - * uint32_t player - * uint32_t RETRO_DEVICE_JOYPAD state (top 16 bits zero) - * uint32_t ANALOG state[0] - * uint32_t ANALOG state[1] - * } - * - * payload { - * cmd (CMD_INPUT) - * cmd_size (4 words) - * frame - * } - */ - netplay->input_packet_buffer[0] = htonl(NETPLAY_CMD_INPUT); - netplay->input_packet_buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); - netplay->input_packet_buffer[2] = htonl(netplay->self_frame_count); - netplay->input_packet_buffer[3] = htonl(netplay->self_player | - (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0)); - netplay->input_packet_buffer[4] = htonl(state[0]); - netplay->input_packet_buffer[5] = htonl(state[1]); - netplay->input_packet_buffer[6] = htonl(state[2]); - + /* And send this input to our peers */ for (i = 0; i < netplay->connections_size; i++) { - if (netplay->connections[i].active) - send_input(netplay, &netplay->connections[i]); + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) + send_cur_input(netplay, &netplay->connections[i]); } return true; @@ -634,13 +649,17 @@ static bool netplay_get_cmd(netplay_t *netplay, case NETPLAY_CONNECTION_PRE_NICK: { bool ret = netplay_handshake_pre_nick(netplay, connection, had_input); - send_input(netplay, connection); + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !send_cur_input(netplay, connection)) + return false; return ret; } case NETPLAY_CONNECTION_PRE_SYNC: { bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); - send_input(netplay, connection); + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !send_cur_input(netplay, connection)) + return false; return ret; } default: @@ -739,8 +758,12 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->read_frame_count[player]++; if (netplay->is_server) - /* Forward it on */ - send_input_frame(netplay, connection, buffer[0], player, buffer + 2); + { + /* Forward it on if it's past data*/ + if (dframe->frame <= netplay->self_frame_count) + send_input_frame(netplay, NULL, connection, buffer[0], + player, dframe->remote_input_state[player]); + } /* If this was server data, advance our server pointer too */ if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER)) @@ -834,7 +857,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING && netplay->self_player == player) && - !(netplay->connected_players & player)) + !(netplay->connected_players & (1<remote_input_state[player], dframe->self_state, sizeof(dframe->self_state)); dframe->have_remote[player] = true; - send_input_frame(netplay, NULL, dframe->frame, player, dframe->self_state); + send_input_frame(netplay, NULL, NULL, dframe->frame, player, dframe->self_state); if (dframe->frame == netplay->self_frame_count) break; NEXT(); } @@ -1063,6 +1086,7 @@ static bool netplay_get_cmd(netplay_t *netplay, uint32_t frame; uint32_t isize; uint32_t rd, wn; + uint32_t player; /* Make sure we're ready for it */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) @@ -1154,6 +1178,17 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->self_frame_count = frame - 1; } + /* Don't expect earlier data from other clients */ + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1< netplay->read_frame_count[player]) + { + netplay->read_ptr[player] = netplay->read_ptr[connection->player]; + netplay->read_frame_count[player] = frame; + } + } + /* And force rewind to it */ netplay->force_rewind = true; netplay->savestate_request_outstanding = false; @@ -1763,6 +1798,9 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * Read is allowed to drift as much as 'frames' frames ahead */ netplay->buffer_size = frames * 2 + 1; + /* FIXME: Really terrible temporary solution for multiplayer */ + netplay->buffer_size *= 10; + netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, sizeof(*netplay->buffer)); @@ -2204,7 +2242,7 @@ void netplay_load_savestate(netplay_t *netplay, for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; - if (!connection->active) continue; + if (!connection->active || connection->mode < NETPLAY_CONNECTION_CONNECTED) continue; if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, sizeof(header)) || From e7ce01ad3b6816ef98656110c6a50b8148e7f9f3 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 12:34:41 -0500 Subject: [PATCH 32/89] More renaming Now that remote_input_state isn't always remote, it should just be called real_input_state (and is). --- network/netplay/netplay.c | 32 +++++++++++++++---------------- network/netplay/netplay_net.c | 4 ++-- network/netplay/netplay_private.h | 10 ++++++---- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 1fa0335264..6e6b221bd8 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -443,11 +443,11 @@ static bool send_cur_input(netplay_t *netplay, struct netplay_connection *connec continue; if ((netplay->connected_players & (1<have_remote[player]) + if (dframe->have_real[player]) { if (!send_input_frame(netplay, connection, NULL, netplay->self_frame_count, player, - dframe->remote_input_state[player])) + dframe->real_input_state[player])) return false; } } @@ -518,9 +518,9 @@ static bool get_self_input_state(netplay_t *netplay) /* If we're playing, copy it in as real input */ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { - memcpy(ptr->remote_input_state[netplay->self_player], state, + memcpy(ptr->real_input_state[netplay->self_player], state, sizeof(state)); - ptr->have_remote[netplay->self_player] = true; + ptr->have_real[netplay->self_player] = true; } /* And send this input to our peers */ @@ -751,9 +751,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* FIXME: Catastrophe! */ return netplay_cmd_nak(netplay, connection); } - memcpy(dframe->remote_input_state[player], buffer + 2, + memcpy(dframe->real_input_state[player], buffer + 2, WORDS_PER_INPUT*sizeof(uint32_t)); - dframe->have_remote[player] = true; + dframe->have_real[player] = true; netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); netplay->read_frame_count[player]++; @@ -762,7 +762,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Forward it on if it's past data*/ if (dframe->frame <= netplay->self_frame_count) send_input_frame(netplay, NULL, connection, buffer[0], - player, dframe->remote_input_state[player]); + player, dframe->real_input_state[player]); } /* If this was server data, advance our server pointer too */ @@ -951,8 +951,8 @@ static bool netplay_get_cmd(netplay_t *netplay, START(netplay->server_ptr); while (dframe->used && dframe->frame <= netplay->self_frame_count) { - memcpy(dframe->remote_input_state[player], dframe->self_state, sizeof(dframe->self_state)); - dframe->have_remote[player] = true; + memcpy(dframe->real_input_state[player], dframe->self_state, sizeof(dframe->self_state)); + dframe->have_real[player] = true; send_input_frame(netplay, NULL, NULL, dframe->frame, player, dframe->self_state); if (dframe->frame == netplay->self_frame_count) break; NEXT(); @@ -966,7 +966,7 @@ static bool netplay_get_cmd(netplay_t *netplay, while (dframe->used && dframe->frame < frame) { memset(dframe->self_state, 0, sizeof(dframe->self_state)); - memset(dframe->remote_input_state[player], 0, sizeof(dframe->self_state)); + memset(dframe->real_input_state[player], 0, sizeof(dframe->self_state)); dframe->have_local = true; NEXT(); } @@ -1336,14 +1336,14 @@ void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim) (1U<simulated_input_state[0][0] & keep; - sim_state |= pframe->remote_input_state[0][0] & ~keep; + sim_state |= pframe->real_input_state[0][0] & ~keep; simframe->simulated_input_state[0][0] = sim_state; } else { memcpy(simframe->simulated_input_state, - pframe->remote_input_state, - sizeof(pframe->remote_input_state)); + pframe->real_input_state, + sizeof(pframe->real_input_state)); } } @@ -1383,7 +1383,7 @@ static bool netplay_poll(void) } /* Simulate the input if we don't have real input */ - if (!netplay_data->buffer[netplay_data->self_ptr].have_remote) + if (!netplay_data->buffer[netplay_data->self_ptr].have_real) netplay_simulate_input(netplay_data, netplay_data->self_ptr, false); /* Consider stalling */ @@ -1504,10 +1504,10 @@ static int16_t netplay_input_state(netplay_t *netplay, return 0; } - if (netplay->buffer[ptr].have_remote[port]) + if (netplay->buffer[ptr].have_real[port]) { netplay->buffer[ptr].used_real[port] = true; - curr_input_state = netplay->buffer[ptr].remote_input_state[port]; + curr_input_state = netplay->buffer[ptr].real_input_state[port]; } else { diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 6784370581..889a451f77 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -267,8 +267,8 @@ void netplay_sync_post_frame(netplay_t *netplay) for (i = 0; i < MAX_USERS; i++) { - if (memcmp(ptr->simulated_input_state[i], ptr->remote_input_state[i], - sizeof(ptr->remote_input_state[i])) != 0 + if (memcmp(ptr->simulated_input_state[i], ptr->real_input_state[i], + sizeof(ptr->real_input_state[i])) != 0 && !ptr->used_real[i]) break; } diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index be547df9e0..8e0dee2775 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -202,17 +202,19 @@ struct delta_frame /* The CRC-32 of the serialized state if we've calculated it, else 0 */ uint32_t crc; - netplay_input_state_t remote_input_state[MAX_USERS]; + /* The real, simulated and local input. If we're playing, self_state is + * mirrored to the appropriate real_input_state player. */ + netplay_input_state_t real_input_state[MAX_USERS]; netplay_input_state_t simulated_input_state[MAX_USERS]; netplay_input_state_t self_state; /* Have we read local input? */ bool have_local; - /* Have we read the real remote input? */ - bool have_remote[MAX_USERS]; + /* Have we read the real (remote) input? */ + bool have_real[MAX_USERS]; - /* Is the current state as of self_frame_count using the real remote data? */ + /* Is the current state as of self_frame_count using the real (remote) data? */ bool used_real[MAX_USERS]; }; From 90c15fc7a6742398aed8b63bf244bc3cff0bb481 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 12:42:22 -0500 Subject: [PATCH 33/89] Still need to flush input data. --- network/netplay/netplay.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 6e6b221bd8..c4a244c578 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -462,6 +462,10 @@ static bool send_cur_input(netplay_t *netplay, struct netplay_connection *connec } } + if (!netplay_send_flush(&connection->send_packet_buffer, connection->fd, + false)) + return false; + return true; } From 262d77546b092ef966dae2e966c6ad637f29bd9f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 17:22:35 -0500 Subject: [PATCH 34/89] Adding game/watch key Adding a key to toggle between playing and spectating. This key takes the place of the previous flip key, although player flipping does continue to work (and must be rebound if you still want it) --- command.c | 6 ++++++ command.h | 2 ++ config.def.h | 3 ++- input/input_config.c | 3 ++- input/input_defines.h | 1 + intl/msg_hash_us.c | 8 ++++++++ intl/msg_hash_us.h | 2 ++ msg_hash.h | 2 ++ network/netplay/netplay.c | 41 +++++++++++++++++++++++++++++++++++++-- network/netplay/netplay.h | 1 + runloop.c | 3 +++ 11 files changed, 68 insertions(+), 4 deletions(-) diff --git a/command.c b/command.c index 8676b49c6d..abff46c982 100644 --- a/command.c +++ b/command.c @@ -286,6 +286,7 @@ static const struct cmd_map map[] = { { "MUTE", RARCH_MUTE }, { "OSK", RARCH_OSK }, { "NETPLAY_FLIP", RARCH_NETPLAY_FLIP }, + { "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH }, { "SLOWMOTION", RARCH_SLOWMOTION }, { "VOLUME_UP", RARCH_VOLUME_UP }, { "VOLUME_DOWN", RARCH_VOLUME_DOWN }, @@ -2369,6 +2370,11 @@ bool command_event(enum event_command cmd, void *data) case CMD_EVENT_NETPLAY_FLIP_PLAYERS: #ifdef HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, NULL); +#endif + break; + case CMD_EVENT_NETPLAY_GAME_WATCH: +#ifdef HAVE_NETWORKING + netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL); #endif break; case CMD_EVENT_FULLSCREEN_TOGGLE: diff --git a/command.h b/command.h index 4c47cd5224..3131915d0a 100644 --- a/command.h +++ b/command.h @@ -173,6 +173,8 @@ enum event_command CMD_EVENT_NETPLAY_DEINIT, /* Flip netplay players. */ CMD_EVENT_NETPLAY_FLIP_PLAYERS, + /* Switch between netplay gaming and watching. */ + CMD_EVENT_NETPLAY_GAME_WATCH, /* Initializes BSV movie. */ CMD_EVENT_BSV_MOVIE_INIT, /* Deinitializes BSV movie. */ diff --git a/config.def.h b/config.def.h index cc7fd22b1f..d44acd7f50 100644 --- a/config.def.h +++ b/config.def.h @@ -987,7 +987,8 @@ static const struct retro_keybind retro_keybinds_1[] = { { true, RARCH_SCREENSHOT, MENU_ENUM_LABEL_VALUE_INPUT_META_SCREENSHOT, RETROK_F8, NO_BTN, 0, AXIS_NONE }, { true, RARCH_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE, RETROK_F9, NO_BTN, 0, AXIS_NONE }, { true, RARCH_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, RETROK_F12, NO_BTN, 0, AXIS_NONE }, - { true, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, RETROK_i, NO_BTN, 0, AXIS_NONE }, + { true, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, RETROK_UNKNOWN, NO_BTN, 0, AXIS_NONE }, + { true, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH, RETROK_i, NO_BTN, 0, AXIS_NONE }, { true, RARCH_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION, RETROK_e, NO_BTN, 0, AXIS_NONE }, { true, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, RETROK_UNKNOWN, NO_BTN, 0, AXIS_NONE }, { true, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, RETROK_KP_PLUS, NO_BTN, 0, AXIS_NONE }, diff --git a/input/input_config.c b/input/input_config.c index eb6861afcf..a978d0af36 100644 --- a/input/input_config.c +++ b/input/input_config.c @@ -125,7 +125,8 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = { DECLARE_META_BIND(2, screenshot, RARCH_SCREENSHOT, MENU_ENUM_LABEL_VALUE_INPUT_META_SCREENSHOT), DECLARE_META_BIND(2, audio_mute, RARCH_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE), DECLARE_META_BIND(2, osk_toggle, RARCH_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK), - DECLARE_META_BIND(2, netplay_flip_players, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP), + DECLARE_META_BIND(2, netplay_flip_players_1_2, RARCH_NETPLAY_FLIP, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP), + DECLARE_META_BIND(2, netplay_game_watch, RARCH_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH), DECLARE_META_BIND(2, slowmotion, RARCH_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION), DECLARE_META_BIND(2, enable_hotkey, RARCH_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY), DECLARE_META_BIND(2, volume_up, RARCH_VOLUME_UP, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP), diff --git a/input/input_defines.h b/input/input_defines.h index 9040d6df7b..c626512773 100644 --- a/input/input_defines.h +++ b/input/input_defines.h @@ -72,6 +72,7 @@ enum RARCH_MUTE, RARCH_OSK, RARCH_NETPLAY_FLIP, + RARCH_NETPLAY_GAME_WATCH, RARCH_SLOWMOTION, RARCH_ENABLE_HOTKEY, RARCH_VOLUME_UP, diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index e364b1723f..ace43f647f 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -91,6 +91,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) snprintf(s, len, "Netplay flip users."); break; + case RARCH_NETPLAY_GAME_WATCH: + snprintf(s, len, + "Netplay toggle play/spectate mode."); + break; case RARCH_SLOWMOTION: snprintf(s, len, "Hold for slowmotion."); @@ -1787,6 +1791,10 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) snprintf(s, len, "Netplay flip users."); break; + case MENU_ENUM_LABEL_NETPLAY_GAME_WATCH: + snprintf(s, len, + "Netplay toggle play/spectate mode."); + break; case MENU_ENUM_LABEL_CHEAT_INDEX_PLUS: snprintf(s, len, "Increment cheat index.\n"); diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index cb1271d5fd..5b84cfa9aa 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -714,6 +714,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE, "Audio mute toggle") MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, "Netplay flip users") +MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH, + "Netplay toggle play/spectate mode") MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, "On-screen keyboard toggle") MSG_HASH(MENU_ENUM_LABEL_VALUE_INPUT_META_OVERLAY_NEXT, diff --git a/msg_hash.h b/msg_hash.h index 0ca9835b8a..3f975fc885 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -530,6 +530,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_INPUT_META_MUTE, MENU_ENUM_LABEL_VALUE_INPUT_META_OSK, MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_FLIP, + MENU_ENUM_LABEL_VALUE_INPUT_META_NETPLAY_GAME_WATCH, MENU_ENUM_LABEL_VALUE_INPUT_META_SLOWMOTION, MENU_ENUM_LABEL_VALUE_INPUT_META_ENABLE_HOTKEY, MENU_ENUM_LABEL_VALUE_INPUT_META_VOLUME_UP, @@ -940,6 +941,7 @@ enum msg_hash_enums MENU_LABEL(UNDO_SAVE_STATE), MENU_LABEL(NETPLAY_FLIP_PLAYERS), + MENU_LABEL(NETPLAY_GAME_WATCH), MENU_LABEL(CHEAT_INDEX_MINUS), MENU_LABEL(CHEAT_INDEX_PLUS), MENU_LABEL(SHADER_NEXT), diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index c4a244c578..50a4d5eaec 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -831,6 +831,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Mark them as not playing anymore */ connection->mode = NETPLAY_CONNECTION_SPECTATING; + netplay->connected_players &= ~(1<player); /* Tell everyone */ payload[1] = htonl(connection->player); @@ -924,8 +925,6 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); frame = ntohl(payload[0]); - if (frame != netplay->server_frame_count) - return netplay_cmd_nak(netplay, connection); /* We're changing past input, so must replay it */ if (frame < netplay->self_frame_count) @@ -941,6 +940,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* A change to me! */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + /* Hooray, I get to play now! */ if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) return netplay_cmd_nak(netplay, connection); @@ -992,6 +994,9 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Somebody else is joining or parting */ if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) { + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + netplay->connected_players |= (1<read_ptr[player] = netplay->server_ptr; @@ -1978,6 +1983,35 @@ static void netplay_flip_users(netplay_t *netplay) netplay->flip_frame = flip_frame; } +/* Toggle between play mode and spectate mode */ +static void netplay_toggle_play_spectate(netplay_t *netplay) +{ + uint32_t cmd; + size_t i; + + if (netplay->is_server) + { + /* FIXME */ + return; + } + + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + /* Switch to spectator mode immediately */ + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + cmd = NETPLAY_CMD_SPECTATE; + } + else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) + { + /* Switch only after getting permission */ + cmd = NETPLAY_CMD_PLAY; + } + else return; + + netplay_send_raw_cmd_all(netplay, NULL, cmd, NULL, 0); +} + + /** * netplay_free: * @netplay : pointer to netplay object @@ -2426,6 +2460,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) netplay_flip_users(netplay_data); } break; + case RARCH_NETPLAY_CTL_GAME_WATCH: + netplay_toggle_play_spectate(netplay_data); + break; case RARCH_NETPLAY_CTL_PAUSE: netplay_frontend_paused(netplay_data, true); break; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index ec84805c97..61e63a24a5 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -33,6 +33,7 @@ enum rarch_netplay_ctl_state { RARCH_NETPLAY_CTL_NONE = 0, RARCH_NETPLAY_CTL_FLIP_PLAYERS, + RARCH_NETPLAY_CTL_GAME_WATCH, RARCH_NETPLAY_CTL_POST_FRAME, RARCH_NETPLAY_CTL_PRE_FRAME, RARCH_NETPLAY_CTL_ENABLE_SERVER, diff --git a/runloop.c b/runloop.c index 29e413d7fe..c05f34aa65 100644 --- a/runloop.c +++ b/runloop.c @@ -922,6 +922,9 @@ static enum runloop_state runloop_check_state( #ifdef HAVE_NETWORKING tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_FLIP); netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, &tmp); + tmp = runloop_cmd_triggered(trigger_input, RARCH_NETPLAY_GAME_WATCH); + if (tmp) + netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL); tmp = runloop_cmd_triggered(trigger_input, RARCH_FULLSCREEN_TOGGLE_KEY); #endif From 2130fd81a5c65c4b706f300eacf0a2e5cd4adb6c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 17:23:21 -0500 Subject: [PATCH 35/89] Fixed simulation for >2 players --- network/netplay/netplay.c | 82 +++++++++++++++++-------------- network/netplay/netplay_net.c | 5 +- network/netplay/netplay_private.h | 2 +- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 50a4d5eaec..65036aef38 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1318,45 +1318,56 @@ static int poll_input(netplay_t *netplay, bool block) * * "Simulate" input by assuming it hasn't changed since the last read input. */ -void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim) +void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) { - size_t prev = PREV_PTR(netplay->unread_ptr); - struct delta_frame *pframe = &netplay->buffer[prev], - *simframe = &netplay->buffer[sim_ptr]; - if (resim) + uint32_t player; + size_t prev; + struct delta_frame *simframe, *pframe; + + simframe = &netplay->buffer[sim_ptr]; + + for (player = 0; player < MAX_USERS; player++) { - /* 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 = (1U<simulated_input_state[0][0] & keep; - sim_state |= pframe->real_input_state[0][0] & ~keep; - simframe->simulated_input_state[0][0] = sim_state; - } - else - { - memcpy(simframe->simulated_input_state, - pframe->real_input_state, - sizeof(pframe->real_input_state)); + if (!(netplay->connected_players & (1<have_real[player]) continue; + + prev = PREV_PTR(netplay->read_ptr[player]); + pframe = &netplay->buffer[prev]; + + if (resim) + { + /* 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 = (1U<simulated_input_state[player][0] & keep; + sim_state |= pframe->real_input_state[player][0] & ~keep; + simframe->simulated_input_state[player][0] = sim_state; + } + else + { + memcpy(simframe->simulated_input_state[player], + pframe->real_input_state[player], + WORDS_PER_INPUT * sizeof(uint32_t)); + } } } - /** * netplay_poll: * @netplay : pointer to netplay object @@ -1392,8 +1403,7 @@ static bool netplay_poll(void) } /* Simulate the input if we don't have real input */ - if (!netplay_data->buffer[netplay_data->self_ptr].have_real) - netplay_simulate_input(netplay_data, netplay_data->self_ptr, false); + netplay_simulate_input(netplay_data, netplay_data->self_ptr, false); /* Consider stalling */ switch (netplay_data->stall) diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 889a451f77..72830f4ff2 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -318,9 +318,8 @@ void netplay_sync_post_frame(netplay_t *netplay) if (netplay->replay_frame_count < netplay->unread_frame_count) netplay_handle_frame_hash(netplay, ptr); - /* Simulate this frame's input */ - if (netplay->replay_frame_count >= netplay->unread_frame_count) - netplay_simulate_input(netplay, netplay->replay_ptr, true); + /* Re-simulate this frame's input */ + netplay_simulate_input(netplay, netplay->replay_ptr, true); autosave_lock(); core_run(); diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 8e0dee2775..3bcb53056d 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -482,7 +482,7 @@ bool netplay_init_serialization(netplay_t *netplay); /* Force serialization to be ready by fast-forwarding the core */ bool netplay_wait_and_init_serialization(netplay_t *netplay); -void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr, bool resim); +void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim); void netplay_log_connection(const struct sockaddr_storage *their_addr, unsigned slot, const char *nick); From 727743aa7bb222555c0c373859225022fbb40d9d Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 17:58:01 -0500 Subject: [PATCH 36/89] Fixing a couple FIXMEs/TODOs --- network/netplay/netplay.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 65036aef38..bc5a25e645 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -310,12 +310,6 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); } } - - /* Reset things that will behave oddly if we get a new connection (FIXME) */ - netplay->remote_paused = false; - netplay->flip = false; - netplay->flip_frame = 0; - netplay->stall = 0; } /** @@ -1256,19 +1250,23 @@ static int poll_input(netplay_t *netplay, bool block) netplay->timeout_cnt++; - /* If we're not ready for input, wait until we are. - * Could fill the TCP buffer, stalling the other side. */ - /* FIXME: This won't work with uneven input, need to somehow stall */ - if (netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->unread_ptr], - netplay->unread_frame_count)) + /* Make sure we're actually ready for data */ + update_unread_ptr(netplay); + if (!netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) + break; + if (!netplay->is_server && + !netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->server_ptr], + netplay->server_frame_count)) + break; + + /* Read input from each connection */ + for (i = 0; i < netplay->connections_size; i++) { - 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)) - hangup(netplay, connection); - } + struct netplay_connection *connection = &netplay->connections[i]; + if (connection->active && !netplay_get_cmd(netplay, connection, &had_input)) + hangup(netplay, connection); } if (block) @@ -1817,9 +1815,6 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * Read is allowed to drift as much as 'frames' frames ahead */ netplay->buffer_size = frames * 2 + 1; - /* FIXME: Really terrible temporary solution for multiplayer */ - netplay->buffer_size *= 10; - netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, sizeof(*netplay->buffer)); From 3d34c0222f439ffcfb8050407961b5606c944f1f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 18:39:45 -0500 Subject: [PATCH 37/89] Added simple (cheesy) announcements on join/leave --- network/netplay/netplay.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index bc5a25e645..18b6c1e644 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -635,6 +635,7 @@ static bool netplay_get_cmd(netplay_t *netplay, uint32_t flip_frame; uint32_t cmd_size; ssize_t recvd; + char msg[512]; /* We don't handle the initial handshake here */ switch (connection->mode) @@ -830,6 +831,12 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Tell everyone */ payload[1] = htonl(connection->player); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "Player %d has left", connection->player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); } else { @@ -875,6 +882,13 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Tell everyone */ payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | connection->player); netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } /* Tell the player even if they were confused */ @@ -894,6 +908,7 @@ static bool netplay_get_cmd(netplay_t *netplay, uint32_t frame, mode, player; size_t ptr; struct delta_frame *dframe; + #define START(which) \ do { \ ptr = which; \ @@ -973,6 +988,12 @@ static bool netplay_get_cmd(netplay_t *netplay, } + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } else /* YOU && !PLAYING */ { @@ -980,6 +1001,11 @@ static bool netplay_get_cmd(netplay_t *netplay, if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING) return netplay_cmd_nak(netplay, connection); + /* Announce it */ + strlcpy(msg, "You have left the game", sizeof(msg)); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } } @@ -995,10 +1021,23 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->read_ptr[player] = netplay->server_ptr; netplay->read_frame_count[player] = netplay->server_frame_count; + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } else { netplay->connected_players &= ~(1< Date: Mon, 12 Dec 2016 19:34:50 -0500 Subject: [PATCH 38/89] force_send_savestate is global again We cannot send a savestate to only one player, as sending a savestate is a synchronization event invalidating all prior input. --- network/netplay/netplay.c | 3 +-- network/netplay/netplay_common.c | 3 +-- network/netplay/netplay_net.c | 6 ++---- network/netplay/netplay_private.h | 12 ++---------- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 18b6c1e644..2240d71298 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1119,8 +1119,7 @@ static bool netplay_get_cmd(netplay_t *netplay, case NETPLAY_CMD_REQUEST_SAVESTATE: /* Delay until next frame so we don't send the savestate after the * input */ - connection->force_send_savestate = true; - netplay->force_send_savestate_one = true; + netplay->force_send_savestate = true; break; case NETPLAY_CMD_LOAD_SAVESTATE: diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 9f8fefbc1e..ca5c8d7f8b 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -267,8 +267,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio /* Send them the savestate */ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) { - connection->force_send_savestate = true; - netplay->force_send_savestate_one = true; + netplay->force_send_savestate = true; } } else diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 72830f4ff2..2af25f64c8 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -88,14 +88,12 @@ bool netplay_sync_pre_frame(netplay_t *netplay) } else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info)) { - if ((netplay->force_send_savestate_all || netplay->force_send_savestate_one) && !netplay->stall) + if (netplay->force_send_savestate && !netplay->stall) { /* Send this along to the other side */ serial_info.data_const = netplay->buffer[netplay->self_ptr].state; netplay_load_savestate(netplay, &serial_info, false); - netplay->force_send_savestate_all = - netplay->force_send_savestate_one = false; - /* FIXME: Shouldn't send to everyone! */ + netplay->force_send_savestate = false; } } else diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 3bcb53056d..afbc170214 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -249,9 +249,6 @@ struct netplay_connection /* Player # of connected player */ int player; - - /* Force send a savestate, to this connection only */ - bool force_send_savestate; }; struct netplay @@ -349,13 +346,8 @@ struct netplay /* Quirks in the savestate implementation */ uint64_t quirks; - /* Force our state to be sent to all connections. Used when we explicitly - * load a state. */ - bool force_send_savestate_all; - - /* Set if there is at least one client which must be sent the state, usually - * because they've requested it or just connected. */ - bool force_send_savestate_one; + /* Force our state to be sent to all connections */ + bool force_send_savestate; /* Have we requested a savestate as a sync point? */ bool savestate_request_outstanding; From f6f9905ae3b0f31c3e9721712b7c1babbb62b379 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 19:50:08 -0500 Subject: [PATCH 39/89] Made remote pausing connection-specific --- network/netplay/netplay.c | 23 +++++++++++++++++++++++ network/netplay/netplay_private.h | 3 +++ 2 files changed, 26 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 2240d71298..7ab205090a 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1239,12 +1239,29 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_PAUSE: + connection->paused = true; netplay->remote_paused = true; + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE, NULL, 0); break; case NETPLAY_CMD_RESUME: + { + 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); break; + } default: RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); @@ -2201,6 +2218,12 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) return; netplay->local_paused = paused; + + /* If other connections are paused, nothing to say */ + if (netplay->remote_paused) + return; + + /* Have to send manually because every buffer must be flushed immediately */ for (i = 0; i < netplay->connections_size; i++) { struct netplay_connection *connection = &netplay->connections[i]; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index afbc170214..c363745f8f 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -249,6 +249,9 @@ struct netplay_connection /* Player # of connected player */ int player; + + /* Is this player paused? */ + bool paused; }; struct netplay From 3d7f1f657554122a051b6f3a1e85fd4b1a56e10d Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 20:29:07 -0500 Subject: [PATCH 40/89] Updating Netplay README to be more true --- network/netplay/README | 65 ++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 8fa349d296..89f220a3fc 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -9,11 +9,7 @@ minor constraints: Furthermore, if the core supports serialization (save states), Netplay allows for latency and clock drift, providing both host and client with a smooth -experience. - -Note that this documentation is all for (the poorly-named) "net" mode, which is -the normal mode, and not "spectator" mode, which has its own whole host of -problems. +experience, as well as the option of more than two players. Netplay in RetroArch works by expecting input to come delayed from the network, then rewinding and re-playing with the delayed input to get a consistent state. @@ -24,10 +20,10 @@ correct frame. In terms of the implementation, Netplay is in effect a state buffer (implemented as a ring of buffers) and some pre- and post-frame behaviors. -Within the state buffers, there are three locations: self, other and read. Each -refers to a frame, and a state buffer corresponding to that frame. The state -buffer contains the savestate for the frame, and the input from both the local -and remote players. +Within the state buffers, there are three locations: self, other and unread. +Each refers to a frame, and a state buffer corresponding to that frame. The +state buffer contains the savestate for the frame, and the input from both the +local and remote players. Self is where the emulator believes itself to be, which may be ahead or behind of what it's read from the peer. Generally speaking, self progresses at 1 frame @@ -38,18 +34,31 @@ frame from which both local and remote input have been actioned. As such, other is always less than or equal to both self and read. Since the state buffer is a ring, other is the first frame that it's unsafe to overwrite. -Read is where it's read up to, which can be slightly ahead of other since it -can't always immediately act upon new data. +Unread is the first frame at which not all players' data has been read, which +can be slightly ahead of other since it can't always immediately act upon new +data. -In general, other ≤ read and other ≤ self. In all likelihood, read ≤ self, but -it is both possible and supported for the remote host to get ahead of the local -host. +In general, other ≤ unread and other ≤ self. In all likelihood, unread ≤ self, +but it is both possible and supported for the remote host to get ahead of the +local host. + +The server has a slightly more complicated job as it can handle multiple +clients, however it is not vastly more complicated: For each connection which +is playing (i.e., has a controller), it maintains a per-player unread frame, +and the global unread frame is the earliest of each player unread frame. The +server forwards input data: When input data is received from an earlier frame +than the server's current frame, it forwards it immediately. Otherwise, it +forwards it when the frame is reached. i.e., during frame n, the server may +send its own and any number of other players' data for frame n, but will never +send frame n+1. This is because the server's clock is the arbiter of all +synchronization-related events, such as flipping players, players joining and +parting, and saving/loading states. Pre-frame, Netplay serializes the core's state, polls for local input, and -polls for input from the other side. If the input from the other side is too -far behind, it stalls to allow the other side to catch up. To assure that this -stalling does not block the UI thread, it is implemented similarly to pausing, -rather than by blocking on the socket. +polls for input from the network. If the input from the network is too far +behind (i.e., unread is too far behind self), it stalls to allow the other side +to catch up. To assure that this stalling does not block the UI thread, it is +implemented similarly to pausing, rather than by blocking on the socket. If input has not been received for the other side up to the current frame (the usual case), the remote input is simulated in a simplistic manner. Each @@ -78,6 +87,17 @@ two relevant actions: Reading the data and emulating with the data. The frame count is only incremented after the latter, so there is a period of time during which we've actually read self_frame_count+1 frames of local input. +Clients may come and go, and may start or stop playing even as they're +connected. A client that is not playing is said to be “spectating”: It receives +all the same data but sends none. A client may switch from spectating to +playing by sending the appropriate request, at which point it is allotted a +player number (see the SPECTATE, PLAY and MODE commands below). + +The server may also be in spectator mode, but as the server never sends data +early (i.e., it only forwards data on the frame it's reached), it must also +inform all clients of its own current frame even if it has no input. The +NOINPUT command is provided for that purpose. + * Guarantee not actually a guarantee. @@ -122,6 +142,15 @@ Description: synchronization point: No synchronization events from the given frame may arrive after the server's input for the frame. +Command: NOINPUT +Payload: + { + frame number: uint32 + } +Description: + Sent by the server to indicate a frame has passed when the server is not + otherwise sending data. + Command: NICK Payload: { From 3631ff74ff62f2ae1ab66820f509107011ef9762 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 21:57:14 -0500 Subject: [PATCH 41/89] Very, very partial support for the server spectating --- network/netplay/netplay.c | 133 ++++++++++++++++++++++-------- network/netplay/netplay_common.c | 2 +- network/netplay/netplay_private.h | 3 + 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 7ab205090a..e47cf05fcc 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -60,6 +60,10 @@ static bool in_netplay = false; static void announce_nat_traversal(netplay_t *netplay); #endif +static bool netplay_send_raw_cmd(netplay_t *netplay, + struct netplay_connection *connection, uint32_t cmd, const void *data, + size_t size); + static void netplay_send_raw_cmd_all(netplay_t *netplay, struct netplay_connection *except, uint32_t cmd, const void *data, size_t size); @@ -430,30 +434,45 @@ static bool send_cur_input(netplay_t *netplay, struct netplay_connection *connec struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr]; uint32_t player; - for (player = 0; player < MAX_USERS; player++) + if (netplay->is_server) { - if (connection->mode == NETPLAY_CONNECTION_PLAYING && - connection->player == player) - continue; - if ((netplay->connected_players & (1<have_real[player]) + if (connection->mode == NETPLAY_CONNECTION_PLAYING && + connection->player == player) + continue; + if ((netplay->connected_players & (1<self_frame_count, player, - dframe->real_input_state[player])) - return false; + if (dframe->have_real[player]) + { + if (!send_input_frame(netplay, connection, NULL, + netplay->self_frame_count, player, + dframe->real_input_state[player])) + return false; + } } } - else if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING && - netplay->self_player == player) + + /* If we're not playing, send a NOINPUT */ + if (netplay->self_mode != NETPLAY_CONNECTION_PLAYING) { - if (!send_input_frame(netplay, connection, NULL, - netplay->self_frame_count, - (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | player, - dframe->self_state)) + 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) + { + if (!send_input_frame(netplay, connection, NULL, + netplay->self_frame_count, + (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player, + dframe->self_state)) + return false; } if (!netplay_send_flush(&connection->send_packet_buffer, connection->fd, @@ -773,6 +792,25 @@ static bool netplay_get_cmd(netplay_t *netplay, break; } + case NETPLAY_CMD_NOINPUT: + { + uint32_t frame; + + if (netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + RECV(&frame, sizeof(frame)) + return netplay_cmd_nak(netplay, connection); + frame = ntohl(frame); + + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + + netplay->server_ptr = NEXT_PTR(netplay->server_ptr); + netplay->server_frame_count++; + break; + } + case NETPLAY_CMD_FLIP_PLAYERS: if (cmd_size != sizeof(uint32_t)) { @@ -2046,29 +2084,54 @@ static void netplay_flip_users(netplay_t *netplay) /* Toggle between play mode and spectate mode */ static void netplay_toggle_play_spectate(netplay_t *netplay) { - uint32_t cmd; - size_t i; - if (netplay->is_server) { - /* FIXME */ - return; - } + /* FIXME: Duplication */ + uint32_t payload[2]; + payload[0] = htonl(netplay->self_frame_count+1); + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + /* Mark us as no longer playing */ + payload[1] = htonl(netplay->self_player); + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + } + else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) + { + uint32_t player; - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - { - /* Switch to spectator mode immediately */ - netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - cmd = NETPLAY_CMD_SPECTATE; - } - else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) - { - /* Switch only after getting permission */ - cmd = NETPLAY_CMD_PLAY; - } - else return; + /* Take a player number */ + for (player = 0; player < MAX_USERS; player++) + if (!(netplay->connected_players & (1<self_mode = NETPLAY_CONNECTION_PLAYING; + netplay->self_player = player; + } + + netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + } + else + { + uint32_t cmd; + size_t i; + + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + /* Switch to spectator mode immediately */ + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + cmd = NETPLAY_CMD_SPECTATE; + } + else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) + { + /* Switch only after getting permission */ + cmd = NETPLAY_CMD_PLAY; + } + else return; + + netplay_send_raw_cmd_all(netplay, NULL, cmd, NULL, 0); + } } diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index ca5c8d7f8b..4af02b24e5 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -262,7 +262,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio if (netplay->is_server) { - netplay_log_connection(&connection->addr, 0, connection->nick); + netplay_log_connection(&connection->addr, connection - netplay->connections, connection->nick); /* Send them the savestate */ if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION))) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index c363745f8f..bb8371307d 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -95,6 +95,9 @@ enum netplay_cmd /* Input data */ NETPLAY_CMD_INPUT = 0x0003, + /* Non-input data */ + NETPLAY_CMD_NOINPUT = 0x0004, + /* Initialization commands */ /* Inform the other side of our nick (must be first command) */ From 888e45c88bd6be94aaa0fab23870e2a149460a68 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 22:28:11 -0500 Subject: [PATCH 42/89] Updates to allow the server to toggle game/watch --- network/netplay/netplay.c | 22 ++++++++++++++-------- network/netplay/netplay_net.c | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index e47cf05fcc..a8830bab89 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -344,7 +344,7 @@ static bool netplay_can_poll(netplay_t *netplay) * earliest unread frame count of any connected player */ static void update_unread_ptr(netplay_t *netplay) { - if (!netplay->connected_players) + if (netplay->is_server && !netplay->connected_players) { /* Nothing at all to read! */ netplay->unread_ptr = netplay->self_ptr; @@ -396,11 +396,6 @@ static bool send_input_frame(netplay_t *netplay, if (only) { - if (only->mode == NETPLAY_CONNECTION_PLAYING && only->player == player) - { - hangup(netplay, only); - return false; - } if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer))) { hangup(netplay, only); @@ -1006,7 +1001,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { memcpy(dframe->real_input_state[player], dframe->self_state, sizeof(dframe->self_state)); dframe->have_real[player] = true; - send_input_frame(netplay, NULL, NULL, dframe->frame, player, dframe->self_state); + send_input_frame(netplay, connection, NULL, dframe->frame, player, dframe->self_state); if (dframe->frame == netplay->self_frame_count) break; NEXT(); } @@ -2088,12 +2083,18 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) { /* FIXME: Duplication */ uint32_t payload[2]; - payload[0] = htonl(netplay->self_frame_count+1); + char msg[512]; + payload[0] = htonl(netplay->self_frame_count); if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { /* Mark us as no longer playing */ payload[1] = htonl(netplay->self_player); netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + + strlcpy(msg, "You have left the game", sizeof(msg)); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) { @@ -2107,6 +2108,11 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | player); netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay->self_player = player; + + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); } netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload)); diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 2af25f64c8..845bcf22f4 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -245,7 +245,7 @@ void netplay_sync_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (!netplay->connected_players) + if (netplay->is_server && !netplay->connected_players) { netplay->other_frame_count = netplay->self_frame_count; netplay->other_ptr = netplay->self_ptr; From 5a0328dc09f87b037296345cde60f8f1750fddf3 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Dec 2016 23:07:46 -0500 Subject: [PATCH 43/89] Transfer pad configuration over netplay. --- network/netplay/README | 4 +++- network/netplay/netplay_common.c | 35 +++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 89f220a3fc..c9f8c77bbf 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -165,13 +165,15 @@ Payload: frame number: uint32 connected players: uint32 flip frame: uint32 + controller devices: uint32[16] sram: variable } Description: Initial state synchronization. Mandatory handshake command from server to client only. Sent after receiving client's NICK. Connected players is a bitmap with the lowest bit being player 0. Flip frame is 0 if players - aren't flipped. + aren't flipped. Controller devices are the devices plugged into each + controller port, and 16 is really MAX_USERS. Command: SPECTATE Payload: None diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 4af02b24e5..0811e0f3b6 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -25,6 +25,7 @@ #include "../../movie.h" #include "../../msg_hash.h" +#include "../../configuration.h" #include "../../content.h" #include "../../runloop.h" #include "../../version.h" @@ -320,13 +321,17 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c /* If we're the server, now we send sync info */ uint32_t cmd[5]; uint32_t connected_players; + settings_t *settings = config_get_ptr(); + size_t i; + uint32_t device; retro_ctx_memory_info_t mem_info; mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); + /* Send basic sync info */ cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(3*sizeof(uint32_t) + mem_info.size); + cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + mem_info.size); cmd[2] = htonl(netplay->self_frame_count); connected_players = netplay->connected_players; if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) @@ -340,6 +345,17 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, sizeof(cmd))) return false; + + /* Now send the device info */ + for (i = 0; i < MAX_USERS; i++) + { + device = htonl(settings->input.libretro_device[i]); + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + &device, sizeof(device))) + return false; + } + + /* And finally, the SRAM */ if (!netplay_send(&connection->send_packet_buffer, connection->fd, mem_info.data, mem_info.size) || !netplay_send_flush(&connection->send_packet_buffer, connection->fd, @@ -367,9 +383,12 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c { uint32_t cmd[2]; uint32_t new_frame_count, connected_players, flip_frame; + uint32_t device; uint32_t local_sram_size, remote_sram_size; size_t i; ssize_t recvd; + settings_t *settings = config_get_ptr(); + retro_ctx_controller_info_t pad; retro_ctx_memory_info_t mem_info; RECV(cmd, sizeof(cmd)) @@ -377,7 +396,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c /* Only expecting a sync command */ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || - ntohl(cmd[1]) < 3*sizeof(uint32_t)) + ntohl(cmd[1]) < 3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t)) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); @@ -430,12 +449,22 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c } } + /* Get and set each pad */ + for (i = 0; i < MAX_USERS; i++) + { + RECV(&device, sizeof(device)) + return false; + pad.port = i; + pad.device = ntohl(device); + core_set_controller_port_device(&pad); + } + /* Now check the SRAM */ mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); local_sram_size = mem_info.size; - remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t); + remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t) - MAX_USERS*sizeof(uint32_t); if (local_sram_size != 0 && local_sram_size == remote_sram_size) { From 763a657f823cb806278b4ebe20dbf876b9630bfc Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 15:26:20 -0500 Subject: [PATCH 44/89] Terrible first cut at password (sent in plain text D-8) --- command.c | 3 +- configuration.c | 1 + configuration.h | 1 + network/netplay/netplay.c | 27 ++++- network/netplay/netplay.h | 4 +- network/netplay/netplay_common.c | 190 ++++++++++++++++++++++-------- network/netplay/netplay_private.h | 26 ++-- 7 files changed, 191 insertions(+), 61 deletions(-) diff --git a/command.c b/command.c index abff46c982..304e13ffca 100644 --- a/command.c +++ b/command.c @@ -2363,7 +2363,8 @@ bool command_event(enum event_command cmd, void *data) #ifdef HAVE_NETWORKING if (!init_netplay( data, settings->netplay.server, - settings->netplay.port)) + settings->netplay.port, + settings->netplay.password)) return false; #endif break; diff --git a/configuration.c b/configuration.c index b869a84f66..8d3c52aaef 100644 --- a/configuration.c +++ b/configuration.c @@ -607,6 +607,7 @@ static int populate_settings_path(settings_t *settings, struct config_path_setti SETTING_PATH("core_updater_buildbot_assets_url", settings->network.buildbot_assets_url, false, NULL, true); #ifdef HAVE_NETWORKING SETTING_PATH("netplay_ip_address", settings->netplay.server, false, NULL, true); + SETTING_PATH("netplay_password", settings->netplay.password, false, NULL, true); #endif SETTING_PATH("recording_output_directory", global->record.output_dir, false, NULL, true); diff --git a/configuration.h b/configuration.h index 85093933a1..993a987968 100644 --- a/configuration.h +++ b/configuration.h @@ -405,6 +405,7 @@ typedef struct settings unsigned check_frames; bool swap_input; bool nat_traversal; + char password[127]; } netplay; #endif diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index a8830bab89..d13357c100 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -667,6 +667,14 @@ static bool netplay_get_cmd(netplay_t *netplay, return false; return ret; } + case NETPLAY_CONNECTION_PRE_PASSWORD: + { + bool ret = netplay_handshake_pre_password(netplay, connection, had_input); + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !send_cur_input(netplay, connection)) + return false; + return ret; + } case NETPLAY_CONNECTION_PRE_SYNC: { bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); @@ -1920,6 +1928,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. * @port : Port of server. + * @password : Password required to connect. * @delay_frames : Amount of delay frames. * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. @@ -1933,9 +1942,9 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * Returns: new netplay handle. **/ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, - unsigned delay_frames, unsigned check_frames, - const struct retro_callbacks *cb, bool nat_traversal, - const char *nick, uint64_t quirks) + const char *password, unsigned delay_frames, unsigned 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) @@ -1967,6 +1976,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, } strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); + strlcpy(netplay->password, password ? password : "", sizeof(netplay->password)); if (!init_socket(netplay, direct_host, server, port)) { @@ -2452,7 +2462,11 @@ void deinit_netplay(void) } /** - * init_netplay: + * init_netplay + * @direct_host : Host to connect to directly, if applicable (client only) + * @server : server address to connect to (client only) + * @port : TCP port to host on/connect to + * @password : Password required to connect (server only) * * Initializes netplay. * @@ -2460,8 +2474,8 @@ void deinit_netplay(void) * * Returns: true (1) if successful, otherwise false (0). **/ - -bool init_netplay(void *direct_host, const char *server, unsigned port) +bool init_netplay(void *direct_host, const char *server, unsigned port, + const char *password) { struct retro_callbacks cbs = {0}; settings_t *settings = config_get_ptr(); @@ -2516,6 +2530,7 @@ bool init_netplay(void *direct_host, const char *server, unsigned port) netplay_is_client ? direct_host : NULL, netplay_is_client ? server : NULL, port ? port : RARCH_DEFAULT_PORT, + password, settings->netplay.delay_frames, settings->netplay.check_frames, &cbs, settings->netplay.nat_traversal, settings->username, quirks); diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 61e63a24a5..ea377ccc65 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -59,8 +59,10 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); /** * init_netplay + * @direct_host : Host to connect to directly, if applicable (client only) * @server : server address to connect to (client only) * @port : TCP port to host on/connect to + * @password : Password required to connect (server only) * * Initializes netplay. * @@ -68,7 +70,7 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); * * Returns: true (1) if successful, otherwise false (0). **/ -bool init_netplay(void *direct_host, const char *server, unsigned port); +bool init_netplay(void *direct_host, const char *server, unsigned port, const char *password); void deinit_netplay(void); diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 0811e0f3b6..48058a4d51 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -27,8 +27,10 @@ #include "../../msg_hash.h" #include "../../configuration.h" #include "../../content.h" +#include "../../retroarch.h" #include "../../runloop.h" #include "../../version.h" +#include "../../menu/widgets/menu_input_dialog.h" /** * netplay_impl_magic: @@ -117,7 +119,7 @@ static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb) bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection) { uint32_t *content_crc_ptr = NULL; - uint32_t header[4] = {0}; + uint32_t header[5] = {0}; content_get_crc(&content_crc_ptr); @@ -125,6 +127,8 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection * header[1] = htonl(*content_crc_ptr); header[2] = htonl(netplay_platform_magic()); header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); + header[4] = htonl((netplay->is_server && netplay->password[0]) ? + NETPLAY_FLAG_PASSWORD_REQUIRED : 0); if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, sizeof(header)) || @@ -140,6 +144,12 @@ struct nick_buf_s char nick[32]; }; +struct password_buf_s +{ + uint32_t cmd[2]; + char password[128]; +}; + #define RECV(buf, sz) \ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \ if (recvd >= 0 && recvd < (sz)) \ @@ -149,9 +159,30 @@ struct nick_buf_s } \ else if (recvd < 0) +static netplay_t *handshake_password_netplay; + +static void handshake_password(void *ignore, const char *line) +{ + uint32_t cmd[2]; + netplay_t *netplay = handshake_password_netplay; + struct netplay_connection *connection = &netplay->connections[0]; + + memset(netplay->password, 0, sizeof(netplay->password)); + strlcpy(netplay->password, line, sizeof(netplay->password)); + cmd[0] = ntohl(NETPLAY_CMD_PASSWORD); + cmd[1] = ntohl(sizeof(netplay->password)); + + netplay_send(&connection->send_packet_buffer, connection->fd, cmd, sizeof(cmd)) && + netplay_send(&connection->send_packet_buffer, connection->fd, netplay->password, sizeof(netplay->password)) && + netplay_send_flush(&connection->send_packet_buffer, connection->fd, false); + + menu_input_dialog_end(); + rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL); +} + bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { - uint32_t header[4] = {0}; + uint32_t header[5] = {0}; ssize_t recvd; char msg[512]; struct nick_buf_s nick_buf; @@ -231,6 +262,19 @@ bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *conne return false; } + /* If a password is demanded, ask for it */ + if (!netplay->is_server && (ntohl(header[4]) & NETPLAY_FLAG_PASSWORD_REQUIRED)) + { + menu_input_ctx_line_t line; + rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL); + memset(&line, 0, sizeof(line)); + handshake_password_netplay = netplay; + line.label = "Enter Netplay server password:"; + line.label_setting = "no_setting"; + line.cb = handshake_password; + menu_input_dialog_start(&line); + } + /* Send our nick */ nick_buf.cmd[0] = htonl(NETPLAY_CMD_NICK); nick_buf.cmd[1] = htonl(sizeof(nick_buf.nick)); @@ -285,6 +329,59 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio netplay->stall = 0; } +bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *connection) +{ + /* If we're the server, now we send sync info */ + uint32_t cmd[5]; + uint32_t connected_players; + settings_t *settings = config_get_ptr(); + size_t i; + uint32_t device; + retro_ctx_memory_info_t mem_info; + + mem_info.id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&mem_info); + + /* Send basic sync info */ + cmd[0] = htonl(NETPLAY_CMD_SYNC); + cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + mem_info.size); + cmd[2] = htonl(netplay->self_frame_count); + connected_players = netplay->connected_players; + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + connected_players |= 1<self_player; + cmd[3] = htonl(connected_players); + if (netplay->flip) + cmd[4] = htonl(netplay->flip_frame); + else + cmd[4] = htonl(0); + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, + sizeof(cmd))) + return false; + + /* Now send the device info */ + for (i = 0; i < MAX_USERS; i++) + { + device = htonl(settings->input.libretro_device[i]); + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + &device, sizeof(device))) + return false; + } + + /* And finally, the SRAM */ + 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)) + return false; + + /* Now we're ready! */ + connection->mode = NETPLAY_CONNECTION_SPECTATING; + netplay_handshake_ready(netplay, connection); + + return true; +} + bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { struct nick_buf_s nick_buf; @@ -318,54 +415,17 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c if (netplay->is_server) { - /* If we're the server, now we send sync info */ - uint32_t cmd[5]; - uint32_t connected_players; - settings_t *settings = config_get_ptr(); - size_t i; - uint32_t device; - retro_ctx_memory_info_t mem_info; - - mem_info.id = RETRO_MEMORY_SAVE_RAM; - core_get_memory(&mem_info); - - /* Send basic sync info */ - cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + mem_info.size); - cmd[2] = htonl(netplay->self_frame_count); - connected_players = netplay->connected_players; - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - connected_players |= 1<self_player; - cmd[3] = htonl(connected_players); - if (netplay->flip) - cmd[4] = htonl(netplay->flip_frame); - else - cmd[4] = htonl(0); - - if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd, - sizeof(cmd))) - return false; - - /* Now send the device info */ - for (i = 0; i < MAX_USERS; i++) + if (netplay->password[0]) { - device = htonl(settings->input.libretro_device[i]); - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - &device, sizeof(device))) + /* There's a password, so just put them in PRE_PASSWORD mode */ + connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD; + } + else + { + if (!netplay_handshake_sync(netplay, connection)) return false; } - /* And finally, the SRAM */ - 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)) - return false; - - /* Now we're ready! */ - connection->mode = NETPLAY_CONNECTION_SPECTATING; - netplay_handshake_ready(netplay, connection); - } else { @@ -379,6 +439,44 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c return true; } +bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) +{ + struct password_buf_s password_buf; + ssize_t recvd; + char msg[512]; + + msg[0] = '\0'; + + RECV(&password_buf, sizeof(password_buf)); + + /* Expecting only a nick command */ + if (recvd < 0 || + ntohl(password_buf.cmd[0]) != NETPLAY_CMD_PASSWORD || + ntohl(password_buf.cmd[1]) != sizeof(password_buf.password)) + { + if (netplay->is_server) + strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT), + sizeof(msg)); + else + strlcpy(msg, + msg_hash_to_str(MSG_FAILED_TO_RECEIVE_NICKNAME_FROM_HOST), + sizeof(msg)); + RARCH_ERR("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + return false; + } + + if (strncmp(netplay->password, password_buf.password, sizeof(password_buf.password))) + return false; + + if (!netplay_handshake_sync(netplay, connection)) + return false; + + *had_input = true; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; +} + bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { uint32_t cmd[2]; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index bb8371307d..23f236fca2 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -79,6 +79,9 @@ #define NETPLAY_COMPRESSION_SUPPORTED 0 #endif +/* Netplay connection flags */ +#define NETPLAY_FLAG_PASSWORD_REQUIRED (1<<0) + enum netplay_cmd { /* Basic commands */ @@ -103,17 +106,20 @@ enum netplay_cmd /* Inform the other side of our nick (must be first command) */ NETPLAY_CMD_NICK = 0x0020, + /* Give the connection password */ + NETPLAY_CMD_PASSWORD = 0x0021, + /* Initial synchronization info (frame, sram, player info) */ - NETPLAY_CMD_SYNC = 0x0021, + NETPLAY_CMD_SYNC = 0x0022, /* Join spectator mode */ - NETPLAY_CMD_SPECTATE = 0x0022, + NETPLAY_CMD_SPECTATE = 0x0023, /* Join play mode */ - NETPLAY_CMD_PLAY = 0x0023, + NETPLAY_CMD_PLAY = 0x0024, /* Report player mode */ - NETPLAY_CMD_MODE = 0x0024, + NETPLAY_CMD_MODE = 0x0025, /* Loading and synchronization */ @@ -177,6 +183,7 @@ enum rarch_netplay_connection_mode /* Initialization: */ NETPLAY_CONNECTION_INIT, /* Waiting for header */ NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ + NETPLAY_CONNECTION_PRE_PASSWORD, /* Waiting for password */ NETPLAY_CONNECTION_PRE_SYNC, /* Waiting for sync */ /* Ready: */ @@ -268,6 +275,9 @@ struct netplay /* TCP connection for listening (server only) */ int listen_fd; + /* Password required to connect (server only) */ + char password[128]; + /* Our player number */ uint32_t self_player; @@ -394,6 +404,7 @@ void input_poll_net(void); * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. * @port : Port of server. + * @password : Password required to connect. * @delay_frames : Amount of delay frames. * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. @@ -407,9 +418,9 @@ void input_poll_net(void); * Returns: new netplay handle. **/ netplay_t *netplay_new(void *direct_host, const char *server, - uint16_t port, unsigned delay_frames, unsigned check_frames, - const struct retro_callbacks *cb, bool nat_traversal, - const char *nick, uint64_t quirks); + uint16_t port, const char *password, unsigned delay_frames, + unsigned check_frames, const struct retro_callbacks *cb, + bool nat_traversal, const char *nick, uint64_t quirks); /** * netplay_free: @@ -493,6 +504,7 @@ bool netplay_send_nickname(netplay_t *netplay, int fd); bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection); bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); +bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); uint32_t netplay_impl_magic(void); From 6e6f2bfdbe6e22317d73a3e469a284ca5f16ea41 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 17:05:07 -0500 Subject: [PATCH 45/89] Use a proper password hash across the line. --- network/netplay/README | 9 ++++++ network/netplay/netplay_common.c | 47 ++++++++++++++++++++++--------- network/netplay/netplay_private.h | 6 ++-- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index c9f8c77bbf..a1b5d52e44 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -159,6 +159,15 @@ Payload: Description: Send nickname. Mandatory handshake command. +Command: PASSWORD +Payload: + { + password hash: char[64] + } +Description: + Send hashed password to server. Mandatory handshake command for clients if + the server demands a password. + Command: SYNC Payload: { diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 48058a4d51..329fa86957 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -19,6 +19,7 @@ #include "netplay_private.h" #include +#include #include #include @@ -127,8 +128,18 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection * header[1] = htonl(*content_crc_ptr); header[2] = htonl(netplay_platform_magic()); header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - header[4] = htonl((netplay->is_server && netplay->password[0]) ? - NETPLAY_FLAG_PASSWORD_REQUIRED : 0); + if (netplay->is_server && netplay->password[0]) + { + /* Demand a password */ + /* FIXME: Better randomness, or at least seed it */ + connection->salt = rand(); + if (connection->salt == 0) connection->salt = 1; + header[4] = htonl(connection->salt); + } + else + { + header[4] = htonl(0); + } if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, sizeof(header)) || @@ -147,7 +158,7 @@ struct nick_buf_s struct password_buf_s { uint32_t cmd[2]; - char password[128]; + char password[64]; }; #define RECV(buf, sz) \ @@ -163,17 +174,20 @@ static netplay_t *handshake_password_netplay; static void handshake_password(void *ignore, const char *line) { + struct password_buf_s password_buf; + char password[8+128]; /* 8 for salt, 128 for password */ uint32_t cmd[2]; netplay_t *netplay = handshake_password_netplay; struct netplay_connection *connection = &netplay->connections[0]; - memset(netplay->password, 0, sizeof(netplay->password)); - strlcpy(netplay->password, line, sizeof(netplay->password)); - cmd[0] = ntohl(NETPLAY_CMD_PASSWORD); - cmd[1] = ntohl(sizeof(netplay->password)); + snprintf(password, sizeof(password), "%08X", connection->salt); + strlcpy(password + 8, line, sizeof(password)-8); - netplay_send(&connection->send_packet_buffer, connection->fd, cmd, sizeof(cmd)) && - netplay_send(&connection->send_packet_buffer, connection->fd, netplay->password, sizeof(netplay->password)) && + password_buf.cmd[0] = htonl(NETPLAY_CMD_PASSWORD); + password_buf.cmd[1] = htonl(sizeof(password_buf.password)); + sha256_hash(password_buf.password, (uint8_t *) password, strlen(password)); + + netplay_send(&connection->send_packet_buffer, connection->fd, &password_buf, sizeof(password_buf)) && netplay_send_flush(&connection->send_packet_buffer, connection->fd, false); menu_input_dialog_end(); @@ -263,7 +277,7 @@ bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *conne } /* If a password is demanded, ask for it */ - if (!netplay->is_server && (ntohl(header[4]) & NETPLAY_FLAG_PASSWORD_REQUIRED)) + if (!netplay->is_server && (connection->salt = ntohl(header[4]))) { menu_input_ctx_line_t line; rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL); @@ -441,7 +455,8 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { - struct password_buf_s password_buf; + struct password_buf_s password_buf, corr_password_buf; + char password[8+128]; /* 8 for salt, 128 for password */ ssize_t recvd; char msg[512]; @@ -449,7 +464,7 @@ bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connectio RECV(&password_buf, sizeof(password_buf)); - /* Expecting only a nick command */ + /* 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)) @@ -466,7 +481,13 @@ bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connectio return false; } - if (strncmp(netplay->password, password_buf.password, sizeof(password_buf.password))) + /* Calculate the correct password */ + snprintf(password, sizeof(password), "%08X", connection->salt); + strlcpy(password + 8, netplay->password, sizeof(password)-8); + sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password)); + + /* Compare them */ + if (memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password))) return false; if (!netplay_handshake_sync(netplay, connection)) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 23f236fca2..1ec14c6f57 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -79,9 +79,6 @@ #define NETPLAY_COMPRESSION_SUPPORTED 0 #endif -/* Netplay connection flags */ -#define NETPLAY_FLAG_PASSWORD_REQUIRED (1<<0) - enum netplay_cmd { /* Basic commands */ @@ -251,6 +248,9 @@ struct netplay_connection /* Nickname of peer */ char nick[32]; + /* Salt associated with password transaction */ + uint32_t salt; + /* Buffers for sending and receiving data */ struct socket_buffer send_packet_buffer, recv_packet_buffer; From 28e331b5fd94d5e8e4c7582ec5792b324640b673 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 17:07:49 -0500 Subject: [PATCH 46/89] Remove some magic numbers --- network/netplay/netplay_common.c | 8 ++++---- network/netplay/netplay_private.h | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 329fa86957..8f008cd3b2 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -152,13 +152,13 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection * struct nick_buf_s { uint32_t cmd[2]; - char nick[32]; + char nick[NETPLAY_NICK_LEN]; }; struct password_buf_s { uint32_t cmd[2]; - char password[64]; + char password[NETPLAY_PASS_HASH_LEN]; }; #define RECV(buf, sz) \ @@ -175,7 +175,7 @@ static netplay_t *handshake_password_netplay; static void handshake_password(void *ignore, const char *line) { struct password_buf_s password_buf; - char password[8+128]; /* 8 for salt, 128 for password */ + char password[8+NETPLAY_PASS_LEN]; /* 8 for salt, 128 for password */ uint32_t cmd[2]; netplay_t *netplay = handshake_password_netplay; struct netplay_connection *connection = &netplay->connections[0]; @@ -456,7 +456,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { struct password_buf_s password_buf, corr_password_buf; - char password[8+128]; /* 8 for salt, 128 for password */ + char password[8+NETPLAY_PASS_LEN]; /* 8 for salt */ ssize_t recvd; char msg[512]; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 1ec14c6f57..fd7fc39253 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -42,6 +42,10 @@ #define RARCH_DEFAULT_PORT 55435 #define RARCH_DEFAULT_NICK "Anonymous" +#define NETPLAY_NICK_LEN 32 +#define NETPLAY_PASS_LEN 128 +#define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */ + #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) @@ -246,7 +250,7 @@ struct netplay_connection struct sockaddr_storage addr; /* Nickname of peer */ - char nick[32]; + char nick[NETPLAY_NICK_LEN]; /* Salt associated with password transaction */ uint32_t salt; @@ -270,13 +274,13 @@ struct netplay bool is_server; /* Our nickname */ - char nick[32]; + char nick[NETPLAY_NICK_LEN]; /* TCP connection for listening (server only) */ int listen_fd; /* Password required to connect (server only) */ - char password[128]; + char password[NETPLAY_PASS_LEN]; /* Our player number */ uint32_t self_player; From c8bba9a981ace0b0e54c81168323a670c6bd40a2 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 19:10:44 -0500 Subject: [PATCH 47/89] Simplistic builtin randomizer. --- network/netplay/netplay_common.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 8f008cd3b2..715f04f6bc 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -117,6 +117,29 @@ static bool netplay_endian_mismatch(uint32_t pma, uint32_t pmb) return (pma & ebit) != (pmb & ebit); } +static unsigned long simple_rand_next = 1; + +static int simple_rand() +{ + simple_rand_next = simple_rand_next * 1103515245 + 12345; + return((unsigned)(simple_rand_next/65536) % 32768); +} + +static void simple_srand(unsigned int seed) { + simple_rand_next = seed; +} + +static uint32_t simple_rand_uint32() +{ + uint32_t parts[3]; + parts[0] = simple_rand(); + parts[1] = simple_rand(); + parts[2] = simple_rand(); + return ((parts[0] << 30) + + (parts[1] << 15) + + parts[2]); +} + bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection) { uint32_t *content_crc_ptr = NULL; @@ -131,8 +154,9 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection * if (netplay->is_server && netplay->password[0]) { /* Demand a password */ - /* FIXME: Better randomness, or at least seed it */ - connection->salt = rand(); + if (simple_rand_next == 1) + simple_srand(time(NULL)); + connection->salt = simple_rand_uint32(); if (connection->salt == 0) connection->salt = 1; header[4] = htonl(connection->salt); } From ab989d7f41274b6223abd2ca0ca2afedb9475473 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 19:25:09 -0500 Subject: [PATCH 48/89] More careful disconnection. --- network/netplay/netplay.c | 29 ++++++++++++++++++----------- network/netplay/netplay_net.c | 3 ++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index d13357c100..fb4968827d 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -298,21 +298,28 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) netplay_deinit_socket_buffer(&connection->recv_packet_buffer); if (!netplay->is_server) - netplay->self_mode = NETPLAY_CONNECTION_NONE; - - /* Remove this player */ - if (connection->mode == NETPLAY_CONNECTION_PLAYING) { - netplay->connected_players &= ~(1<player); + netplay->self_mode = NETPLAY_CONNECTION_NONE; + netplay->connected_players = 0; - /* FIXME: Duplication */ - if (netplay->is_server) + } + else + { + /* Remove this player */ + if (connection->mode == NETPLAY_CONNECTION_PLAYING) { - uint32_t payload[2]; - payload[0] = htonl(netplay->read_frame_count[connection->player]); - payload[1] = htonl(connection->player); - netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + netplay->connected_players &= ~(1<player); + + /* FIXME: Duplication */ + if (netplay->is_server) + { + uint32_t payload[2]; + payload[0] = htonl(netplay->read_frame_count[connection->player]); + payload[1] = htonl(connection->player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + } } + } } diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 845bcf22f4..dfcb925c32 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -245,7 +245,8 @@ void netplay_sync_post_frame(netplay_t *netplay) netplay->self_frame_count++; /* Only relevant if we're connected */ - if (netplay->is_server && !netplay->connected_players) + if ((netplay->is_server && !netplay->connected_players) || + (netplay->self_mode < NETPLAY_CONNECTION_CONNECTED)) { netplay->other_frame_count = netplay->self_frame_count; netplay->other_ptr = netplay->self_ptr; From 8c59c7dd7708ff57df693f365a25f682c51cdc51 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 20:16:22 -0500 Subject: [PATCH 49/89] Starting to refactor: Separating frontend stuff into netplay_frontend.c --- Makefile.common | 11 +- griffin/griffin.c | 2 +- network/netplay/netplay.c | 967 ++--------------------------- network/netplay/netplay_frontend.c | 947 ++++++++++++++++++++++++++++ network/netplay/netplay_private.h | 25 + 5 files changed, 1026 insertions(+), 926 deletions(-) create mode 100644 network/netplay/netplay_frontend.c diff --git a/Makefile.common b/Makefile.common index 5b20704103..dcf8eee717 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1122,11 +1122,12 @@ ifeq ($(HAVE_NETWORKING), 1) # Netplay DEFINES += -DHAVE_NETWORK_CMD - OBJ += network/netplay/netplay_net.o \ - network/netplay/netplay_common.o \ - network/netplay/netplay_discovery.o \ - network/netplay/netplay_buf.o \ - network/netplay/netplay.o + OBJ += network/netplay/netplay_frontend.o \ + network/netplay/netplay_net.o \ + network/netplay/netplay_common.o \ + network/netplay/netplay_discovery.o \ + network/netplay/netplay_buf.o \ + network/netplay/netplay.o # Retro Achievements (also depends on threads) diff --git a/griffin/griffin.c b/griffin/griffin.c index ad093012d1..e3076f43e2 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -881,8 +881,8 @@ THREAD NETPLAY ============================================================ */ #ifdef HAVE_NETWORKING +#include "../network/netplay/netplay_frontend.c" #include "../network/netplay/netplay_net.c" -#include "../network/netplay/netplay_spectate.c" #include "../network/netplay/netplay_common.c" #include "../network/netplay/netplay_discovery.c" #include "../network/netplay/netplay.c" diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index fb4968827d..e967b1d2ba 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -38,7 +38,6 @@ #include "../../movie.h" #include "../../runloop.h" -#define MAX_STALL_TIME_USEC (10*1000*1000) #define MAX_RETRIES 16 #define RETRY_MS 500 @@ -46,28 +45,6 @@ #define HAVE_INET6 1 #endif -/* Only used before init_netplay */ -static bool netplay_enabled = false; -static bool netplay_is_client = false; - -/* Used while Netplay is running */ -static netplay_t *netplay_data = NULL; - -/* Used to avoid recursive netplay calls */ -static bool in_netplay = false; - -#ifndef HAVE_SOCKET_LEGACY -static void announce_nat_traversal(netplay_t *netplay); -#endif - -static bool netplay_send_raw_cmd(netplay_t *netplay, - struct netplay_connection *connection, uint32_t cmd, const void *data, - size_t size); - -static void netplay_send_raw_cmd_all(netplay_t *netplay, - struct netplay_connection *except, uint32_t cmd, const void *data, - size_t size); - static int init_tcp_connection(const struct addrinfo *res, bool server, struct sockaddr *other_addr, socklen_t addr_size) @@ -245,24 +222,6 @@ static bool init_tcp_socket(netplay_t *netplay, void *direct_host, return ret; } -static void init_nat_traversal(netplay_t *netplay) -{ - natt_init(); - - if (!natt_new(&netplay->nat_traversal_state)) - { - netplay->nat_traversal = false; - return; - } - - natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP); - -#ifndef HAVE_SOCKET_LEGACY - if (!netplay->nat_traversal_state.request_outstanding) - announce_nat_traversal(netplay); -#endif -} - static bool init_socket(netplay_t *netplay, void *direct_host, const char *server, uint16_t port) { if (!network_init()) @@ -272,17 +231,17 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve return false; if (netplay->is_server && netplay->nat_traversal) - init_nat_traversal(netplay); + netplay_init_nat_traversal(netplay); return true; } /** - * hangup: + * netplay_hangup: * * Disconnects an active Netplay connection due to an error **/ -static void hangup(netplay_t *netplay, struct netplay_connection *connection) +void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) { if (!netplay) return; @@ -324,32 +283,11 @@ static void hangup(netplay_t *netplay, struct netplay_connection *connection) } /** - * netplay_should_skip: - * @netplay : pointer to netplay object + * netplay_update_unread_ptr * - * If we're fast-forward replaying to resync, check if we - * should actually show frame. - * - * Returns: bool (1) if we should skip this frame, otherwise - * false (0). - **/ -static bool netplay_should_skip(netplay_t *netplay) -{ - if (!netplay) - return false; - return netplay->is_replay && (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); -} - -static bool netplay_can_poll(netplay_t *netplay) -{ - if (!netplay) - return false; - return netplay->can_poll; -} - -/* Update the global unread_ptr and unread_frame_count to correspond to the + * Update the global unread_ptr and unread_frame_count to correspond to the * earliest unread frame count of any connected player */ -static void update_unread_ptr(netplay_t *netplay) +void netplay_update_unread_ptr(netplay_t *netplay) { if (netplay->is_server && !netplay->connected_players) { @@ -405,7 +343,7 @@ static bool send_input_frame(netplay_t *netplay, { if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer))) { - hangup(netplay, only); + netplay_hangup(netplay, only); return false; } } @@ -422,7 +360,7 @@ static bool send_input_frame(netplay_t *netplay, { if (!netplay_send(&connection->send_packet_buffer, connection->fd, buffer, sizeof(buffer))) - hangup(netplay, connection); + netplay_hangup(netplay, connection); } } } @@ -431,7 +369,8 @@ static bool send_input_frame(netplay_t *netplay, } /* Send the current input frame */ -static bool send_cur_input(netplay_t *netplay, struct netplay_connection *connection) +bool netplay_send_cur_input(netplay_t *netplay, + struct netplay_connection *connection) { struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr]; uint32_t player; @@ -485,75 +424,13 @@ static bool send_cur_input(netplay_t *netplay, struct netplay_connection *connec } /** - * get_self_input_state: - * @netplay : pointer to netplay object + * netplay_send_raw_cmd * - * Grab our own input state and send this over the network. + * Send a raw Netplay command to the given connection * - * Returns: true (1) if successful, otherwise false (0). - **/ -static bool get_self_input_state(netplay_t *netplay) -{ - uint32_t state[WORDS_PER_INPUT] = {0, 0, 0}; - struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; - size_t i; - - if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) - return false; - - if (ptr->have_local) - { - /* We've already read this frame! */ - return true; - } - - if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) - { - settings_t *settings = config_get_ptr(); - - /* First frame we always give zero input since relying on - * input from first frame screws up when we use -F 0. */ - retro_input_state_t cb = netplay->cbs.state_cb; - for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) - { - int16_t tmp = cb(0, - RETRO_DEVICE_JOYPAD, 0, i); - state[0] |= tmp ? 1 << i : 0; - } - - for (i = 0; i < 2; i++) - { - int16_t tmp_x = cb(0, - RETRO_DEVICE_ANALOG, i, 0); - int16_t tmp_y = cb(0, - RETRO_DEVICE_ANALOG, i, 1); - state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); - } - } - - memcpy(ptr->self_state, state, sizeof(state)); - ptr->have_local = true; - - /* If we're playing, copy it in as real input */ - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - { - memcpy(ptr->real_input_state[netplay->self_player], state, - sizeof(state)); - ptr->have_real[netplay->self_player] = true; - } - - /* 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) - send_cur_input(netplay, &netplay->connections[i]); - } - - return true; -} - -static bool netplay_send_raw_cmd(netplay_t *netplay, + * 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) { @@ -573,7 +450,13 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, return true; } -static void netplay_send_raw_cmd_all(netplay_t *netplay, +/** + * 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) { @@ -586,7 +469,7 @@ static void netplay_send_raw_cmd_all(netplay_t *netplay, if (connection->active && connection->mode >= NETPLAY_CONNECTION_CONNECTED) { if (!netplay_send_raw_cmd(netplay, connection, cmd, data, size)) - hangup(netplay, connection); + netplay_hangup(netplay, connection); } } } @@ -670,7 +553,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { bool ret = netplay_handshake_pre_nick(netplay, connection, had_input); if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !send_cur_input(netplay, connection)) + !netplay_send_cur_input(netplay, connection)) return false; return ret; } @@ -678,7 +561,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { bool ret = netplay_handshake_pre_password(netplay, connection, had_input); if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !send_cur_input(netplay, connection)) + !netplay_send_cur_input(netplay, connection)) return false; return ret; } @@ -686,7 +569,7 @@ static bool netplay_get_cmd(netplay_t *netplay, { bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !send_cur_input(netplay, connection)) + !netplay_send_cur_input(netplay, connection)) return false; return ret; } @@ -1097,7 +980,7 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_DISCONNECT: - hangup(netplay, connection); + netplay_hangup(netplay, connection); return true; case NETPLAY_CMD_CRC: @@ -1330,8 +1213,12 @@ shrt: #undef RECV } -/* FIXME: This is going to be very screwy for delay_frames = 0 */ -static int poll_input(netplay_t *netplay, bool block) +/** + * 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; @@ -1354,7 +1241,7 @@ static int poll_input(netplay_t *netplay, bool block) netplay->timeout_cnt++; /* Make sure we're actually ready for data */ - update_unread_ptr(netplay); + netplay_update_unread_ptr(netplay); if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) break; @@ -1369,12 +1256,12 @@ static int poll_input(netplay_t *netplay, bool block) { struct netplay_connection *connection = &netplay->connections[i]; if (connection->active && !netplay_get_cmd(netplay, connection, &had_input)) - hangup(netplay, connection); + netplay_hangup(netplay, connection); } if (block) { - update_unread_ptr(netplay); + 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->self_frame_count) @@ -1470,128 +1357,11 @@ void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) } /** - * netplay_poll: - * @netplay : pointer to netplay object + * netplay_flip_port * - * Polls network to see if we have anything new. If our - * network buffer is full, we simply have to block - * for new input data. - * - * Returns: true (1) if successful, otherwise false (0). - **/ -static bool netplay_poll(void) -{ - int res; - - netplay_data->can_poll = false; - - get_self_input_state(netplay_data); - - /* Read Netplay input, block if we're configured to stall for input every - * frame */ - if (netplay_data->delay_frames == 0 && - netplay_data->unread_frame_count <= netplay_data->self_frame_count) - res = poll_input(netplay_data, true); - else - res = poll_input(netplay_data, false); - if (res == -1) - { - /* Catastrophe! */ - size_t i; - for (i = 0; i < netplay_data->connections_size; i++) - hangup(netplay_data, &netplay_data->connections[i]); - return false; - } - - /* Simulate the input if we don't have real input */ - netplay_simulate_input(netplay_data, netplay_data->self_ptr, false); - - /* Consider stalling */ - switch (netplay_data->stall) - { - case NETPLAY_STALL_RUNNING_FAST: - update_unread_ptr(netplay_data); - if (netplay_data->unread_frame_count >= netplay_data->self_frame_count) - netplay_data->stall = NETPLAY_STALL_NONE; - break; - - case NETPLAY_STALL_NO_CONNECTION: - /* We certainly haven't fixed this */ - break; - - default: /* not stalling */ - update_unread_ptr(netplay_data); - if (netplay_data->unread_frame_count + netplay_data->delay_frames - <= netplay_data->self_frame_count) - { - netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; - netplay_data->stall_time = cpu_features_get_time_usec(); - } - } - - /* If we're stalling, consider disconnection */ - if (netplay_data->stall) - { - retro_time_t now = cpu_features_get_time_usec(); - - /* Don't stall out while they're paused */ - if (netplay_data->remote_paused) - netplay_data->stall_time = now; - else if (now - netplay_data->stall_time >= MAX_STALL_TIME_USEC) - { - /* Stalled out! (FIXME: Shouldn't be so nuclear) */ - size_t i; - for (i = 0; i < netplay_data->connections_size; i++) - hangup(netplay_data, &netplay_data->connections[i]); - return false; - } - } - - return true; -} - -void input_poll_net(void) -{ - if (!netplay_should_skip(netplay_data) && netplay_can_poll(netplay_data)) - netplay_poll(); -} - -void video_frame_net(const void *data, unsigned width, - unsigned height, size_t pitch) -{ - if (!netplay_should_skip(netplay_data)) - netplay_data->cbs.frame_cb(data, width, height, pitch); -} - -void audio_sample_net(int16_t left, int16_t right) -{ - if (!netplay_should_skip(netplay_data) && !netplay_data->stall) - netplay_data->cbs.sample_cb(left, right); -} - -size_t audio_sample_batch_net(const int16_t *data, size_t frames) -{ - if (!netplay_should_skip(netplay_data) && !netplay_data->stall) - return netplay_data->cbs.sample_batch_cb(data, frames); - return frames; -} - -/** - * netplay_is_alive: - * @netplay : pointer to netplay object - * - * Checks if input port/index is controlled by netplay or not. - * - * Returns: true (1) if alive, otherwise false (0). - **/ -static bool netplay_is_alive(void) -{ - if (!netplay_data) - return false; - return !!netplay_data->connected_players; -} - -static bool netplay_flip_port(netplay_t *netplay) + * Should we flip ports 0 and 1? + */ +bool netplay_flip_port(netplay_t *netplay) { size_t frame = netplay->self_frame_count; @@ -1604,60 +1374,6 @@ static bool netplay_flip_port(netplay_t *netplay) return netplay->flip ^ (frame < netplay->flip_frame); } -static int16_t netplay_input_state(netplay_t *netplay, - unsigned port, unsigned device, - unsigned idx, unsigned id) -{ - size_t ptr = netplay->is_replay ? - netplay->replay_ptr : netplay->self_ptr; - - const uint32_t *curr_input_state = NULL; - - if (port <= 1) - { - /* Possibly flip the port */ - if (netplay_flip_port(netplay)) - port ^= 1; - } - else if (port >= MAX_USERS) - { - return 0; - } - - if (netplay->buffer[ptr].have_real[port]) - { - netplay->buffer[ptr].used_real[port] = true; - curr_input_state = netplay->buffer[ptr].real_input_state[port]; - } - else - { - curr_input_state = netplay->buffer[ptr].simulated_input_state[port]; - } - - switch (device) - { - case RETRO_DEVICE_JOYPAD: - return ((1 << id) & curr_input_state[0]) ? 1 : 0; - - case RETRO_DEVICE_ANALOG: - { - uint32_t state = curr_input_state[1 + idx]; - return (int16_t)(uint16_t)(state >> (id * 16)); - } - - default: - return 0; - } -} - -int16_t input_state_net(unsigned port, unsigned device, - unsigned idx, unsigned id) -{ - if (netplay_is_alive()) - return netplay_input_state(netplay_data, port, device, idx, id); - return netplay_data->cbs.state_cb(port, device, idx, id); -} - #ifndef HAVE_SOCKET_LEGACY /* Custom inet_ntop. Win32 doesn't seem to support this ... */ void netplay_log_connection(const struct sockaddr_storage *their_addr, @@ -1748,40 +1464,6 @@ void netplay_log_connection(const struct sockaddr_storage *their_addr, #endif -#ifndef HAVE_SOCKET_LEGACY -static void announce_nat_traversal(netplay_t *netplay) -{ - char msg[512], host[PATH_MAX_LENGTH], port[6]; - - if (netplay->nat_traversal_state.have_inet4) - { - if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, - sizeof(struct sockaddr_in), - host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) - return; - - } -#ifdef HAVE_INET6 - else if (netplay->nat_traversal_state.have_inet6) - { - if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, - sizeof(struct sockaddr_in6), - host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) - return; - - } -#endif - else - return; - - snprintf(msg, sizeof(msg), "%s: %s:%s\n", - msg_hash_to_str(MSG_PUBLIC_ADDRESS), - host, port); - runloop_msg_queue_push(msg, 1, 180, false); - RARCH_LOG("%s\n", msg); -} -#endif - static bool netplay_init_socket_buffers(netplay_t *netplay) { /* Make our packet buffer big enough for a save state and frames-many frames @@ -1818,6 +1500,13 @@ static bool netplay_init_socket_buffers(netplay_t *netplay) 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; @@ -2029,135 +1718,6 @@ error: return NULL; } -/** - * netplay_command: - * @netplay : pointer to netplay object - * @cmd : command to send - * @data : data to send as argument - * @sz : size of data - * @command_str : name of action - * @success_msg : message to display upon success - * - * Sends a single netplay command and waits for response. - */ -bool netplay_command(netplay_t* netplay, struct netplay_connection *connection, - enum netplay_cmd cmd, void* data, size_t sz, const char* command_str, - const char* success_msg) -{ - char m[256]; - const char* msg = NULL; - - retro_assert(netplay); - - if (!netplay_send_raw_cmd(netplay, connection, cmd, data, sz)) - goto error; - - runloop_msg_queue_push(success_msg, 1, 180, false); - - return true; - -error: - if (msg) - snprintf(m, sizeof(m), msg, command_str); - RARCH_WARN("%s\n", m); - runloop_msg_queue_push(m, 1, 180, false); - return false; -} - -/** - * netplay_flip_users: - * @netplay : pointer to netplay object - * - * On regular netplay, flip who controls user 1 and 2. - **/ -static void netplay_flip_users(netplay_t *netplay) -{ - /* Must be in the future because we may have - * already sent this frame's data */ - uint32_t flip_frame = netplay->self_frame_count + 1; - uint32_t flip_frame_net = htonl(flip_frame); - 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_CONNECTED) - { - netplay_command(netplay, connection, NETPLAY_CMD_FLIP_PLAYERS, - &flip_frame_net, sizeof flip_frame_net, "flip users", - "Successfully flipped users.\n"); - } - } - - netplay->flip ^= true; - netplay->flip_frame = flip_frame; -} - -/* Toggle between play mode and spectate mode */ -static void netplay_toggle_play_spectate(netplay_t *netplay) -{ - if (netplay->is_server) - { - /* FIXME: Duplication */ - uint32_t payload[2]; - char msg[512]; - payload[0] = htonl(netplay->self_frame_count); - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - { - /* Mark us as no longer playing */ - payload[1] = htonl(netplay->self_player); - netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - - strlcpy(msg, "You have left the game", sizeof(msg)); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - - } - else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) - { - uint32_t player; - - /* Take a player number */ - for (player = 0; player < MAX_USERS; player++) - if (!(netplay->connected_players & (1<self_mode = NETPLAY_CONNECTION_PLAYING; - netplay->self_player = player; - - msg[sizeof(msg)-1] = '\0'; - snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - } - - netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload)); - - } - else - { - uint32_t cmd; - size_t i; - - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - { - /* Switch to spectator mode immediately */ - netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - cmd = NETPLAY_CMD_SPECTATE; - } - else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) - { - /* Switch only after getting permission */ - cmd = NETPLAY_CMD_PLAY; - } - else return; - - netplay_send_raw_cmd_all(netplay, NULL, cmd, NULL, 0); - } -} - - /** * netplay_free: * @netplay : pointer to netplay object @@ -2208,436 +1768,3 @@ void netplay_free(netplay_t *netplay) free(netplay); } - -/** - * 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 - **/ -bool netplay_pre_frame(netplay_t *netplay) -{ - retro_assert(netplay); - - /* FIXME: This is an ugly way to learn we're not paused anymore */ - if (netplay->local_paused) - netplay_frontend_paused(netplay, false); - - if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) - { - /* Are we ready now? */ - netplay_try_init_serialization(netplay); - } - - if (netplay->is_server) - { - /* Advertise our server */ - netplay_lan_ad_server(netplay); - - /* NAT traversal if applicable */ - if (netplay->nat_traversal && - 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) - announce_nat_traversal(netplay); -#endif - } - } - - if (!netplay_sync_pre_frame(netplay)) - return false; - - return (!netplay->connected_players || - (!netplay->stall && !netplay->remote_paused)); -} - -/** - * netplay_post_frame: - * @netplay : pointer to netplay object - * - * Post-frame for Netplay. - * We check if we have new input and replay from recorded input. - * Call this after running retro_run(). - **/ -void netplay_post_frame(netplay_t *netplay) -{ - size_t i; - retro_assert(netplay); - update_unread_ptr(netplay); - netplay_sync_post_frame(netplay); - - 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)) - hangup(netplay, &netplay->connections[0]); - } -} - -/** - * netplay_frontend_paused - * @netplay : pointer to netplay object - * @paused : true if frontend is paused - * - * Inform Netplay of the frontend's pause state (paused or otherwise) - **/ -void netplay_frontend_paused(netplay_t *netplay, bool paused) -{ - size_t i; - - /* Nothing to do if we already knew this */ - if (netplay->local_paused == paused) - return; - - netplay->local_paused = paused; - - /* If other connections are paused, nothing to say */ - if (netplay->remote_paused) - return; - - /* Have to send manually because every buffer must be flushed immediately */ - 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_raw_cmd(netplay, connection, - paused ? NETPLAY_CMD_PAUSE : 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_load_savestate - * @netplay : pointer to netplay object - * @serial_info : the savestate being loaded, NULL means - * "load it yourself" - * @save : Whether to save the provided serial_info - * into the frame buffer - * - * Inform Netplay of a savestate load and send it to the other side - **/ -void netplay_load_savestate(netplay_t *netplay, - retro_ctx_serialize_info_t *serial_info, bool save) -{ - uint32_t header[4]; - retro_ctx_serialize_info_t tmp_serial_info; - uint32_t rd, wn; - size_t i; - - /* Record it in our own buffer */ - if (save || !serial_info) - { - if (netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) - { - if (!serial_info) - { - tmp_serial_info.size = netplay->state_size; - tmp_serial_info.data = netplay->buffer[netplay->self_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->self_ptr].state, - serial_info->data_const, serial_info->size); - } - } - } - else - { - /* FIXME: This is a critical failure! */ - return; - } - } - - /* We need to ignore any intervening data from the other side, - * and never rewind past this */ - update_unread_ptr(netplay); - if (netplay->unread_frame_count < netplay->self_frame_count) - { - uint32_t player; - for (player = 0; player < MAX_USERS; player++) - { - if (!(netplay->connected_players & (1<read_frame_count[player] < netplay->self_frame_count) - { - netplay->read_ptr[player] = netplay->self_ptr; - netplay->read_frame_count[player] = netplay->self_frame_count; - } - } - if (netplay->server_frame_count < netplay->self_frame_count) - { - netplay->server_ptr = netplay->self_ptr; - netplay->server_frame_count = netplay->self_frame_count; - } - update_unread_ptr(netplay); - } - if (netplay->other_frame_count < netplay->self_frame_count) - { - netplay->other_ptr = netplay->self_ptr; - netplay->other_frame_count = netplay->self_frame_count; - } - - /* 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; - - /* Compress it */ - if (!netplay->compression_backend) - return; - netplay->compression_backend->set_in(netplay->compression_stream, - (const uint8_t*)serial_info->data_const, serial_info->size); - netplay->compression_backend->set_out(netplay->compression_stream, - netplay->zbuffer, netplay->zbuffer_size); - if (!netplay->compression_backend->trans(netplay->compression_stream, - true, &rd, &wn, NULL)) - { - /* Catastrophe! */ - for (i = 0; i < netplay->connections_size; i++) - hangup(netplay, &netplay->connections[i]); - return; - } - - /* And send it to the peers */ - header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); - header[1] = htonl(wn + 2*sizeof(uint32_t)); - header[2] = htonl(netplay->self_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) continue; - - if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, - sizeof(header)) || - !netplay_send(&connection->send_packet_buffer, connection->fd, - netplay->zbuffer, wn)) - hangup(netplay, connection); - } -} - -/** - * netplay_disconnect - * @netplay : pointer to netplay object - * - * Disconnect netplay. - * - * Returns: true (1) if successful. At present, cannot fail. - **/ -bool netplay_disconnect(netplay_t *netplay) -{ - size_t i; - if (!netplay) - return true; - for (i = 0; i < netplay->connections_size; i++) - hangup(netplay, &netplay->connections[i]); - return true; -} - -void deinit_netplay(void) -{ - if (netplay_data) - netplay_free(netplay_data); - netplay_data = NULL; - core_unset_netplay_callbacks(); -} - -/** - * init_netplay - * @direct_host : Host to connect to directly, if applicable (client only) - * @server : server address to connect to (client only) - * @port : TCP port to host on/connect to - * @password : Password required to connect (server only) - * - * Initializes netplay. - * - * If netplay is already initialized, will return false (0). - * - * Returns: true (1) if successful, otherwise false (0). - **/ -bool init_netplay(void *direct_host, const char *server, unsigned port, - const char *password) -{ - struct retro_callbacks cbs = {0}; - settings_t *settings = config_get_ptr(); - uint64_t serialization_quirks = 0; - uint64_t quirks = 0; - - if (!netplay_enabled) - return false; - - if (bsv_movie_ctl(BSV_MOVIE_CTL_START_PLAYBACK, NULL)) - { - RARCH_WARN("%s\n", - msg_hash_to_str(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED)); - 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(); - if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD)) - { - /* Quirks we don't support! Just disable everything. */ - 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) - { - RARCH_LOG("%s\n", msg_hash_to_str(MSG_CONNECTING_TO_NETPLAY_HOST)); - } - else - { - RARCH_LOG("%s\n", msg_hash_to_str(MSG_WAITING_FOR_CLIENT)); - runloop_msg_queue_push( - msg_hash_to_str(MSG_WAITING_FOR_CLIENT), - 0, 180, false); - } - - netplay_data = (netplay_t*)netplay_new( - netplay_is_client ? direct_host : NULL, - netplay_is_client ? server : NULL, - port ? port : RARCH_DEFAULT_PORT, - password, - settings->netplay.delay_frames, settings->netplay.check_frames, &cbs, - settings->netplay.nat_traversal, settings->username, - quirks); - - if (netplay_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); - return false; -} - -bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) -{ - bool ret = true; - - if (in_netplay) - return true; - in_netplay = true; - - if (!netplay_data) - { - switch (state) - { - case RARCH_NETPLAY_CTL_ENABLE_SERVER: - netplay_enabled = true; - netplay_is_client = false; - goto done; - - case RARCH_NETPLAY_CTL_ENABLE_CLIENT: - netplay_enabled = true; - netplay_is_client = true; - break; - - case RARCH_NETPLAY_CTL_DISABLE: - netplay_enabled = false; - goto done; - - case RARCH_NETPLAY_CTL_IS_ENABLED: - ret = netplay_enabled; - goto done; - - case RARCH_NETPLAY_CTL_IS_DATA_INITED: - 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: - netplay_enabled = false; - deinit_netplay(); - goto done; - case RARCH_NETPLAY_CTL_IS_ENABLED: - goto done; - case RARCH_NETPLAY_CTL_POST_FRAME: - netplay_post_frame(netplay_data); - break; - case RARCH_NETPLAY_CTL_PRE_FRAME: - ret = netplay_pre_frame(netplay_data); - goto done; - case RARCH_NETPLAY_CTL_FLIP_PLAYERS: - { - bool *state = (bool*)data; - if (*state) - netplay_flip_users(netplay_data); - } - break; - case RARCH_NETPLAY_CTL_GAME_WATCH: - netplay_toggle_play_spectate(netplay_data); - break; - case RARCH_NETPLAY_CTL_PAUSE: - netplay_frontend_paused(netplay_data, true); - break; - case RARCH_NETPLAY_CTL_UNPAUSE: - netplay_frontend_paused(netplay_data, false); - break; - case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: - netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true); - break; - case RARCH_NETPLAY_CTL_DISCONNECT: - ret = netplay_disconnect(netplay_data); - goto done; - default: - case RARCH_NETPLAY_CTL_NONE: - ret = false; - } - -done: - in_netplay = false; - return ret; -} diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c new file mode 100644 index 0000000000..3d397526b2 --- /dev/null +++ b/network/netplay/netplay_frontend.c @@ -0,0 +1,947 @@ +/* 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 "../../configuration.h" +#include "../../input/input_driver.h" +#include "../../runloop.h" + +#ifndef HAVE_SOCKET_LEGACY +static void announce_nat_traversal(netplay_t *netplay); +#endif + +/* Only used before init_netplay */ +static bool netplay_enabled = false; +static bool netplay_is_client = false; + +/* Used while Netplay is running */ +static netplay_t *netplay_data = NULL; + +/* Used to avoid recursive netplay calls */ +static bool in_netplay = false; + +/** + * netplay_is_alive: + * @netplay : pointer to netplay object + * + * Checks if input port/index is controlled by netplay or not. + * + * Returns: true (1) if alive, otherwise false (0). + **/ +static bool netplay_is_alive(void) +{ + if (!netplay_data) + return false; + return !!netplay_data->connected_players; +} + +/** + * netplay_should_skip: + * @netplay : pointer to netplay object + * + * If we're fast-forward replaying to resync, check if we + * should actually show frame. + * + * Returns: bool (1) if we should skip this frame, otherwise + * false (0). + **/ +static bool netplay_should_skip(netplay_t *netplay) +{ + if (!netplay) + return false; + return netplay->is_replay && (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED); +} + +/** + * netplay_can_poll + * + * Just a frontend for netplay->can_poll that handles netplay==NULL + */ +static bool netplay_can_poll(netplay_t *netplay) +{ + if (!netplay) + return false; + return netplay->can_poll; +} + +/** + * 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(netplay_t *netplay) +{ + uint32_t state[WORDS_PER_INPUT] = {0, 0, 0}; + struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; + size_t i; + + if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) + return false; + + if (ptr->have_local) + { + /* We've already read this frame! */ + return true; + } + + if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) + { + settings_t *settings = config_get_ptr(); + + /* First frame we always give zero input since relying on + * input from first frame screws up when we use -F 0. */ + retro_input_state_t cb = netplay->cbs.state_cb; + for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) + { + int16_t tmp = cb(0, + RETRO_DEVICE_JOYPAD, 0, i); + state[0] |= tmp ? 1 << i : 0; + } + + for (i = 0; i < 2; i++) + { + int16_t tmp_x = cb(0, + RETRO_DEVICE_ANALOG, i, 0); + int16_t tmp_y = cb(0, + RETRO_DEVICE_ANALOG, i, 1); + state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); + } + } + + memcpy(ptr->self_state, state, sizeof(state)); + ptr->have_local = true; + + /* If we're playing, copy it in as real input */ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + memcpy(ptr->real_input_state[netplay->self_player], state, + sizeof(state)); + ptr->have_real[netplay->self_player] = true; + } + + /* 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]); + } + + return true; +} + +/** + * netplay_poll: + * @netplay : pointer to netplay object + * + * Polls network to see if we have anything new. If our + * network buffer is full, we simply have to block + * for new input data. + * + * Returns: true (1) if successful, otherwise false (0). + **/ +static bool netplay_poll(void) +{ + int res; + + netplay_data->can_poll = false; + + get_self_input_state(netplay_data); + + /* Read Netplay input, block if we're configured to stall for input every + * frame */ + if (netplay_data->delay_frames == 0 && + netplay_data->unread_frame_count <= netplay_data->self_frame_count) + res = netplay_poll_net_input(netplay_data, true); + else + res = netplay_poll_net_input(netplay_data, false); + if (res == -1) + { + /* Catastrophe! */ + size_t i; + for (i = 0; i < netplay_data->connections_size; i++) + netplay_hangup(netplay_data, &netplay_data->connections[i]); + return false; + } + + /* Simulate the input if we don't have real input */ + netplay_simulate_input(netplay_data, netplay_data->self_ptr, false); + + /* Consider stalling */ + switch (netplay_data->stall) + { + case NETPLAY_STALL_RUNNING_FAST: + netplay_update_unread_ptr(netplay_data); + if (netplay_data->unread_frame_count >= netplay_data->self_frame_count) + netplay_data->stall = NETPLAY_STALL_NONE; + break; + + case NETPLAY_STALL_NO_CONNECTION: + /* We certainly haven't fixed this */ + break; + + default: /* not stalling */ + netplay_update_unread_ptr(netplay_data); + if (netplay_data->unread_frame_count + netplay_data->delay_frames + <= netplay_data->self_frame_count) + { + netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; + netplay_data->stall_time = cpu_features_get_time_usec(); + } + } + + /* If we're stalling, consider disconnection */ + if (netplay_data->stall) + { + retro_time_t now = cpu_features_get_time_usec(); + + /* Don't stall out while they're paused */ + if (netplay_data->remote_paused) + netplay_data->stall_time = now; + else if (now - netplay_data->stall_time >= MAX_STALL_TIME_USEC) + { + /* Stalled out! (FIXME: Shouldn't be so nuclear) */ + size_t i; + for (i = 0; i < netplay_data->connections_size; i++) + netplay_hangup(netplay_data, &netplay_data->connections[i]); + return false; + } + } + + return true; +} + +/* Netplay polling callbacks */ +void input_poll_net(void) +{ + if (!netplay_should_skip(netplay_data) && netplay_can_poll(netplay_data)) + netplay_poll(); +} + +void video_frame_net(const void *data, unsigned width, + unsigned height, size_t pitch) +{ + if (!netplay_should_skip(netplay_data)) + netplay_data->cbs.frame_cb(data, width, height, pitch); +} + +void audio_sample_net(int16_t left, int16_t right) +{ + if (!netplay_should_skip(netplay_data) && !netplay_data->stall) + netplay_data->cbs.sample_cb(left, right); +} + +size_t audio_sample_batch_net(const int16_t *data, size_t frames) +{ + if (!netplay_should_skip(netplay_data) && !netplay_data->stall) + return netplay_data->cbs.sample_batch_cb(data, frames); + return frames; +} + +static int16_t netplay_input_state(netplay_t *netplay, + unsigned port, unsigned device, + unsigned idx, unsigned id) +{ + size_t ptr = netplay->is_replay ? + netplay->replay_ptr : netplay->self_ptr; + + const uint32_t *curr_input_state = NULL; + + if (port <= 1) + { + /* Possibly flip the port */ + if (netplay_flip_port(netplay)) + port ^= 1; + } + else if (port >= MAX_USERS) + { + return 0; + } + + if (netplay->buffer[ptr].have_real[port]) + { + netplay->buffer[ptr].used_real[port] = true; + curr_input_state = netplay->buffer[ptr].real_input_state[port]; + } + else + { + curr_input_state = netplay->buffer[ptr].simulated_input_state[port]; + } + + switch (device) + { + case RETRO_DEVICE_JOYPAD: + return ((1 << id) & curr_input_state[0]) ? 1 : 0; + + case RETRO_DEVICE_ANALOG: + { + uint32_t state = curr_input_state[1 + idx]; + return (int16_t)(uint16_t)(state >> (id * 16)); + } + + default: + return 0; + } +} + +int16_t input_state_net(unsigned port, unsigned device, + unsigned idx, unsigned id) +{ + if (netplay_is_alive()) + return netplay_input_state(netplay_data, port, device, idx, id); + return netplay_data->cbs.state_cb(port, device, idx, id); +} +/* ^^^ Netplay polling callbacks */ + +/** + * netplay_command: + * @netplay : pointer to netplay object + * @cmd : command to send + * @data : data to send as argument + * @sz : size of data + * @command_str : name of action + * @success_msg : message to display upon success + * + * Sends a single netplay command and waits for response. Only actually used + * for player flipping. FIXME: Should probably just be removed. + */ +bool netplay_command(netplay_t* netplay, struct netplay_connection *connection, + enum netplay_cmd cmd, void* data, size_t sz, const char* command_str, + const char* success_msg) +{ + char m[256]; + const char* msg = NULL; + + retro_assert(netplay); + + if (!netplay_send_raw_cmd(netplay, connection, cmd, data, sz)) + goto error; + + runloop_msg_queue_push(success_msg, 1, 180, false); + + return true; + +error: + if (msg) + snprintf(m, sizeof(m), msg, command_str); + RARCH_WARN("%s\n", m); + runloop_msg_queue_push(m, 1, 180, false); + return false; +} + +/** + * netplay_flip_users: + * @netplay : pointer to netplay object + * + * Flip who controls user 1 and 2. + */ +static void netplay_flip_users(netplay_t *netplay) +{ + /* Must be in the future because we may have + * already sent this frame's data */ + uint32_t flip_frame = netplay->self_frame_count + 1; + uint32_t flip_frame_net = htonl(flip_frame); + 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_CONNECTED) + { + netplay_command(netplay, connection, NETPLAY_CMD_FLIP_PLAYERS, + &flip_frame_net, sizeof flip_frame_net, "flip users", + "Successfully flipped users.\n"); + } + } + + netplay->flip ^= true; + netplay->flip_frame = flip_frame; +} + +/** + * 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 + **/ +bool netplay_pre_frame(netplay_t *netplay) +{ + retro_assert(netplay); + + /* FIXME: This is an ugly way to learn we're not paused anymore */ + if (netplay->local_paused) + netplay_frontend_paused(netplay, false); + + if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) + { + /* Are we ready now? */ + netplay_try_init_serialization(netplay); + } + + if (netplay->is_server) + { + /* Advertise our server */ + netplay_lan_ad_server(netplay); + + /* NAT traversal if applicable */ + if (netplay->nat_traversal && + 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) + announce_nat_traversal(netplay); +#endif + } + } + + if (!netplay_sync_pre_frame(netplay)) + return false; + + return (!netplay->connected_players || + (!netplay->stall && !netplay->remote_paused)); +} + +/** + * netplay_post_frame: + * @netplay : pointer to netplay object + * + * Post-frame for Netplay. + * We check if we have new input and replay from recorded input. + * Call this after running retro_run(). + **/ +void netplay_post_frame(netplay_t *netplay) +{ + size_t i; + retro_assert(netplay); + netplay_update_unread_ptr(netplay); + netplay_sync_post_frame(netplay); + + 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, &netplay->connections[0]); + } +} + +/** + * netplay_frontend_paused + * @netplay : pointer to netplay object + * @paused : true if frontend is paused + * + * Inform Netplay of the frontend's pause state (paused or otherwise) + **/ +void netplay_frontend_paused(netplay_t *netplay, bool paused) +{ + size_t i; + + /* Nothing to do if we already knew this */ + if (netplay->local_paused == paused) + return; + + netplay->local_paused = paused; + + /* If other connections are paused, nothing to say */ + if (netplay->remote_paused) + return; + + /* Have to send manually because every buffer must be flushed immediately */ + 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_raw_cmd(netplay, connection, + paused ? NETPLAY_CMD_PAUSE : 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_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded, NULL means + * "load it yourself" + * @save : Whether to save the provided serial_info + * into the frame buffer + * + * Inform Netplay of a savestate load and send it to the other side + **/ +void netplay_load_savestate(netplay_t *netplay, + retro_ctx_serialize_info_t *serial_info, bool save) +{ + uint32_t header[4]; + retro_ctx_serialize_info_t tmp_serial_info; + uint32_t rd, wn; + size_t i; + + /* Record it in our own buffer */ + if (save || !serial_info) + { + if (netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + { + if (!serial_info) + { + tmp_serial_info.size = netplay->state_size; + tmp_serial_info.data = netplay->buffer[netplay->self_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->self_ptr].state, + serial_info->data_const, serial_info->size); + } + } + } + else + { + /* FIXME: This is a critical failure! */ + return; + } + } + + /* 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->self_frame_count) + { + uint32_t player; + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1<read_frame_count[player] < netplay->self_frame_count) + { + netplay->read_ptr[player] = netplay->self_ptr; + netplay->read_frame_count[player] = netplay->self_frame_count; + } + } + if (netplay->server_frame_count < netplay->self_frame_count) + { + netplay->server_ptr = netplay->self_ptr; + netplay->server_frame_count = netplay->self_frame_count; + } + netplay_update_unread_ptr(netplay); + } + if (netplay->other_frame_count < netplay->self_frame_count) + { + netplay->other_ptr = netplay->self_ptr; + netplay->other_frame_count = netplay->self_frame_count; + } + + /* 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; + + /* Compress it */ + if (!netplay->compression_backend) + return; + netplay->compression_backend->set_in(netplay->compression_stream, + (const uint8_t*)serial_info->data_const, serial_info->size); + netplay->compression_backend->set_out(netplay->compression_stream, + netplay->zbuffer, netplay->zbuffer_size); + if (!netplay->compression_backend->trans(netplay->compression_stream, + true, &rd, &wn, NULL)) + { + /* Catastrophe! */ + for (i = 0; i < netplay->connections_size; i++) + netplay_hangup(netplay, &netplay->connections[i]); + return; + } + + /* And send it to the peers */ + header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); + header[1] = htonl(wn + 2*sizeof(uint32_t)); + header[2] = htonl(netplay->self_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) continue; + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, + sizeof(header)) || + !netplay_send(&connection->send_packet_buffer, connection->fd, + netplay->zbuffer, wn)) + netplay_hangup(netplay, connection); + } +} + +/** + * netplay_init_nat_traversal + * + * Initialize the NAT traversal library and try to open a port + */ +void netplay_init_nat_traversal(netplay_t *netplay) +{ + natt_init(); + + if (!natt_new(&netplay->nat_traversal_state)) + { + netplay->nat_traversal = false; + return; + } + + natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP); + +#ifndef HAVE_SOCKET_LEGACY + if (!netplay->nat_traversal_state.request_outstanding) + announce_nat_traversal(netplay); +#endif +} + +#ifndef HAVE_SOCKET_LEGACY +static void announce_nat_traversal(netplay_t *netplay) +{ + char msg[512], host[PATH_MAX_LENGTH], port[6]; + + if (netplay->nat_traversal_state.have_inet4) + { + if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, + sizeof(struct sockaddr_in), + host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) + return; + + } +#ifdef HAVE_INET6 + else if (netplay->nat_traversal_state.have_inet6) + { + if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, + sizeof(struct sockaddr_in6), + host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) + return; + + } +#endif + else + return; + + snprintf(msg, sizeof(msg), "%s: %s:%s\n", + msg_hash_to_str(MSG_PUBLIC_ADDRESS), + host, port); + runloop_msg_queue_push(msg, 1, 180, false); + RARCH_LOG("%s\n", msg); +} +#endif + +/** + * netplay_toggle_play_spectate + * + * Toggle between play mode and spectate mode + */ +static void netplay_toggle_play_spectate(netplay_t *netplay) +{ + if (netplay->is_server) + { + /* FIXME: Duplication */ + uint32_t payload[2]; + char msg[512]; + payload[0] = htonl(netplay->self_frame_count); + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + /* Mark us as no longer playing */ + payload[1] = htonl(netplay->self_player); + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + + strlcpy(msg, "You have left the game", sizeof(msg)); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + + } + else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) + { + uint32_t player; + + /* Take a player number */ + for (player = 0; player < MAX_USERS; player++) + if (!(netplay->connected_players & (1<self_mode = NETPLAY_CONNECTION_PLAYING; + netplay->self_player = player; + + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } + + netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + } + else + { + uint32_t cmd; + size_t i; + + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + { + /* Switch to spectator mode immediately */ + netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; + cmd = NETPLAY_CMD_SPECTATE; + } + else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) + { + /* Switch only after getting permission */ + cmd = NETPLAY_CMD_PLAY; + } + else return; + + netplay_send_raw_cmd_all(netplay, NULL, cmd, NULL, 0); + } +} + +/** + * netplay_disconnect + * @netplay : pointer to netplay object + * + * Disconnect netplay. + * + * Returns: true (1) if successful. At present, cannot fail. + **/ +bool netplay_disconnect(netplay_t *netplay) +{ + size_t i; + if (!netplay) + return true; + for (i = 0; i < netplay->connections_size; i++) + netplay_hangup(netplay, &netplay->connections[i]); + return true; +} + +void deinit_netplay(void) +{ + if (netplay_data) + netplay_free(netplay_data); + netplay_data = NULL; +} + +/** + * init_netplay + * @direct_host : Host to connect to directly, if applicable (client only) + * @server : server address to connect to (client only) + * @port : TCP port to host on/connect to + * @password : Password required to connect (server only) + * + * Initializes netplay. + * + * If netplay is already initialized, will return false (0). + * + * Returns: true (1) if successful, otherwise false (0). + **/ +bool init_netplay(void *direct_host, const char *server, unsigned port, + const char *password) +{ + struct retro_callbacks cbs = {0}; + settings_t *settings = config_get_ptr(); + uint64_t serialization_quirks = 0; + uint64_t quirks = 0; + + if (!netplay_enabled) + return false; + +#if 0 + /* FIXME: This may still be relevant? */ + if (bsv_movie_ctl(BSV_MOVIE_CTL_START_PLAYBACK, NULL)) + { + RARCH_WARN("%s\n", + msg_hash_to_str(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED)); + return false; + } +#endif + + core_set_default_callbacks(&cbs); + + /* Map the core's quirks to our quirks */ + serialization_quirks = core_serialization_quirks(); + if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD)) + { + /* Quirks we don't support! Just disable everything. */ + 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) + { + RARCH_LOG("%s\n", msg_hash_to_str(MSG_CONNECTING_TO_NETPLAY_HOST)); + } + else + { + RARCH_LOG("%s\n", msg_hash_to_str(MSG_WAITING_FOR_CLIENT)); + runloop_msg_queue_push( + msg_hash_to_str(MSG_WAITING_FOR_CLIENT), + 0, 180, false); + } + + netplay_data = (netplay_t*)netplay_new( + netplay_is_client ? direct_host : NULL, + netplay_is_client ? server : NULL, + port ? port : RARCH_DEFAULT_PORT, + password, + settings->netplay.delay_frames, settings->netplay.check_frames, &cbs, + settings->netplay.nat_traversal, settings->username, + quirks); + + if (netplay_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); + return false; +} + +/** + * netplay_driver_ctl + * + * Frontend access to Netplay functionality + */ +bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) +{ + bool ret = true; + + if (in_netplay) + return true; + in_netplay = true; + + if (!netplay_data) + { + switch (state) + { + case RARCH_NETPLAY_CTL_ENABLE_SERVER: + netplay_enabled = true; + netplay_is_client = false; + goto done; + + case RARCH_NETPLAY_CTL_ENABLE_CLIENT: + netplay_enabled = true; + netplay_is_client = true; + break; + + case RARCH_NETPLAY_CTL_DISABLE: + netplay_enabled = false; + goto done; + + case RARCH_NETPLAY_CTL_IS_ENABLED: + ret = netplay_enabled; + goto done; + + case RARCH_NETPLAY_CTL_IS_DATA_INITED: + 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_POST_FRAME: + netplay_post_frame(netplay_data); + break; + case RARCH_NETPLAY_CTL_PRE_FRAME: + ret = netplay_pre_frame(netplay_data); + goto done; + case RARCH_NETPLAY_CTL_FLIP_PLAYERS: + { + bool *state = (bool*)data; + if (*state) + netplay_flip_users(netplay_data); + } + break; + case RARCH_NETPLAY_CTL_GAME_WATCH: + netplay_toggle_play_spectate(netplay_data); + break; + case RARCH_NETPLAY_CTL_PAUSE: + netplay_frontend_paused(netplay_data, true); + break; + case RARCH_NETPLAY_CTL_UNPAUSE: + netplay_frontend_paused(netplay_data, false); + break; + case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: + netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true); + break; + case RARCH_NETPLAY_CTL_DISCONNECT: + ret = netplay_disconnect(netplay_data); + goto done; + default: + case RARCH_NETPLAY_CTL_NONE: + ret = false; + } + +done: + in_netplay = false; + return ret; +} diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index fd7fc39253..32be86d8d2 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -46,6 +46,8 @@ #define NETPLAY_PASS_LEN 128 #define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */ +#define MAX_STALL_TIME_USEC (10*1000*1000) + #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) @@ -527,6 +529,29 @@ bool netplay_cmd_mode(netplay_t *netplay, struct netplay_connection *connection, enum rarch_netplay_connection_mode mode); +bool netplay_send_cur_input(netplay_t *netplay, + struct netplay_connection *connection); + +int netplay_poll_net_input(netplay_t *netplay, bool block); + +void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection); + +void netplay_update_unread_ptr(netplay_t *netplay); + +bool netplay_flip_port(netplay_t *netplay); + +bool netplay_send_raw_cmd(netplay_t *netplay, + struct netplay_connection *connection, uint32_t cmd, const void *data, + size_t size); + +void netplay_send_raw_cmd_all(netplay_t *netplay, + struct netplay_connection *except, uint32_t cmd, const void *data, + size_t size); + +bool netplay_try_init_serialization(netplay_t *netplay); + +void netplay_init_nat_traversal(netplay_t *netplay); + /* DISCOVERY: */ bool netplay_lan_ad_server(netplay_t *netplay); From 7e2465ef1f47388b9bf40f816faf45d9d7ac80fb Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 20:42:53 -0500 Subject: [PATCH 50/89] Refactoring: Moving I/O functionality into netplay_io.c --- Makefile.common | 1 + griffin/griffin.c | 1 + network/netplay/netplay.c | 1041 ---------------------------- network/netplay/netplay_io.c | 1065 +++++++++++++++++++++++++++++ network/netplay/netplay_private.h | 2 + 5 files changed, 1069 insertions(+), 1041 deletions(-) create mode 100644 network/netplay/netplay_io.c diff --git a/Makefile.common b/Makefile.common index dcf8eee717..a9ed73c184 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1123,6 +1123,7 @@ ifeq ($(HAVE_NETWORKING), 1) # Netplay DEFINES += -DHAVE_NETWORK_CMD OBJ += network/netplay/netplay_frontend.o \ + network/netplay/netplay_io.o \ network/netplay/netplay_net.o \ network/netplay/netplay_common.o \ network/netplay/netplay_discovery.o \ diff --git a/griffin/griffin.c b/griffin/griffin.c index e3076f43e2..6e1d1b736a 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -882,6 +882,7 @@ NETPLAY ============================================================ */ #ifdef HAVE_NETWORKING #include "../network/netplay/netplay_frontend.c" +#include "../network/netplay/netplay_io.c" #include "../network/netplay/netplay_net.c" #include "../network/netplay/netplay_common.c" #include "../network/netplay/netplay_discovery.c" diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index e967b1d2ba..415ff84672 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -38,9 +38,6 @@ #include "../../movie.h" #include "../../runloop.h" -#define MAX_RETRIES 16 -#define RETRY_MS 500 - #if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) #define HAVE_INET6 1 #endif @@ -236,52 +233,6 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve return true; } -/** - * netplay_hangup: - * - * Disconnects an active Netplay connection due to an error - **/ -void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) -{ - if (!netplay) - return; - if (!connection->active) - return; - - RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); - runloop_msg_queue_push("Netplay has disconnected. Will continue without connection.", 0, 480, false); - - 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 = 0; - - } - else - { - /* Remove this player */ - if (connection->mode == NETPLAY_CONNECTION_PLAYING) - { - netplay->connected_players &= ~(1<player); - - /* FIXME: Duplication */ - if (netplay->is_server) - { - uint32_t payload[2]; - payload[0] = htonl(netplay->read_frame_count[connection->player]); - payload[1] = htonl(connection->player); - netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); - } - } - - } -} - /** * netplay_update_unread_ptr * @@ -323,980 +274,6 @@ void netplay_update_unread_ptr(netplay_t *netplay) } } -/* Send the specified input data */ -static bool send_input_frame(netplay_t *netplay, - struct netplay_connection *only, struct netplay_connection *except, - uint32_t frame, uint32_t player, uint32_t *state) -{ - uint32_t buffer[2 + WORDS_PER_FRAME]; - size_t i; - - buffer[0] = htonl(NETPLAY_CMD_INPUT); - buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); - buffer[2] = htonl(frame); - buffer[3] = htonl(player); - buffer[4] = htonl(state[0]); - buffer[5] = htonl(state[1]); - buffer[6] = htonl(state[2]); - - if (only) - { - if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer))) - { - 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 || - connection->player != player)) - { - if (!netplay_send(&connection->send_packet_buffer, connection->fd, - buffer, sizeof(buffer))) - netplay_hangup(netplay, connection); - } - } - } - - return true; -} - -/* Send the current input frame */ -bool netplay_send_cur_input(netplay_t *netplay, - struct netplay_connection *connection) -{ - struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr]; - uint32_t player; - - if (netplay->is_server) - { - /* Send the other players' input data */ - for (player = 0; player < MAX_USERS; player++) - { - if (connection->mode == NETPLAY_CONNECTION_PLAYING && - connection->player == player) - continue; - if ((netplay->connected_players & (1<have_real[player]) - { - if (!send_input_frame(netplay, connection, NULL, - netplay->self_frame_count, player, - dframe->real_input_state[player])) - 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) - { - if (!send_input_frame(netplay, connection, NULL, - netplay->self_frame_count, - (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player, - dframe->self_state)) - 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); - } - } -} - -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; -} - -bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) -{ - uint32_t payload[2]; - bool success = true; - size_t i; - 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; -} - -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); -} - -bool netplay_cmd_mode(netplay_t *netplay, - struct netplay_connection *connection, - enum rarch_netplay_connection_mode mode) -{ - uint32_t cmd; - switch (mode) - { - case NETPLAY_CONNECTION_SPECTATING: - cmd = NETPLAY_CMD_SPECTATE; - break; - - case NETPLAY_CONNECTION_PLAYING: - cmd = NETPLAY_CMD_PLAY; - break; - - default: - return false; - } - return netplay_send_raw_cmd(netplay, connection, cmd, NULL, 0); -} - -static bool netplay_get_cmd(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input) -{ - uint32_t cmd; - uint32_t flip_frame; - uint32_t cmd_size; - ssize_t recvd; - char msg[512]; - - /* We don't handle the initial handshake here */ - switch (connection->mode) - { - case NETPLAY_CONNECTION_NONE: - /* Huh?! */ - return false; - case NETPLAY_CONNECTION_INIT: - return netplay_handshake_init(netplay, connection, had_input); - case NETPLAY_CONNECTION_PRE_NICK: - { - bool ret = netplay_handshake_pre_nick(netplay, connection, had_input); - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - return ret; - } - case NETPLAY_CONNECTION_PRE_PASSWORD: - { - bool ret = netplay_handshake_pre_password(netplay, connection, had_input); - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - return ret; - } - case NETPLAY_CONNECTION_PRE_SYNC: - { - bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - return ret; - } - default: - break; - } - - /* FIXME: This depends on delta_frame_ready */ - -#define RECV(buf, sz) \ - recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \ - (sz), false); \ - if (recvd >= 0 && recvd < (sz)) goto shrt; \ - else if (recvd < 0) - - RECV(&cmd, sizeof(cmd)) - return false; - - cmd = ntohl(cmd); - - RECV(&cmd_size, sizeof(cmd_size)) - return false; - - cmd_size = ntohl(cmd_size); - - 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 buffer[WORDS_PER_FRAME]; - uint32_t player; - unsigned i; - struct delta_frame *dframe; - - if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) - { - RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); - return netplay_cmd_nak(netplay, connection); - } - - RECV(buffer, sizeof(buffer)) - { - RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); - return netplay_cmd_nak(netplay, connection); - } - - for (i = 0; i < WORDS_PER_FRAME; i++) - buffer[i] = ntohl(buffer[i]); - - if (netplay->is_server) - { - /* Ignore the claimed player #, must be this client */ - if (connection->mode != NETPLAY_CONNECTION_PLAYING) - return netplay_cmd_nak(netplay, connection); - player = connection->player; - } - else - { - player = buffer[1] & ~NETPLAY_CMD_INPUT_BIT_SERVER; - } - - if (player >= MAX_USERS || !(netplay->connected_players & (1<read_frame_count[player]) - { - /* We already had this, so ignore the new transmission */ - break; - } - else if (buffer[0] > netplay->read_frame_count[player]) - { - /* Out of order = out of luck */ - return netplay_cmd_nak(netplay, connection); - } - - /* The data's good! */ - dframe = &netplay->buffer[netplay->read_ptr[player]]; - if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player])) - { - /* FIXME: Catastrophe! */ - return netplay_cmd_nak(netplay, connection); - } - memcpy(dframe->real_input_state[player], buffer + 2, - WORDS_PER_INPUT*sizeof(uint32_t)); - dframe->have_real[player] = true; - netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); - netplay->read_frame_count[player]++; - - if (netplay->is_server) - { - /* Forward it on if it's past data*/ - if (dframe->frame <= netplay->self_frame_count) - send_input_frame(netplay, NULL, connection, buffer[0], - player, dframe->real_input_state[player]); - } - - /* If this was server data, advance our server pointer too */ - if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER)) - { - netplay->server_ptr = netplay->read_ptr[player]; - netplay->server_frame_count = netplay->read_frame_count[player]; - } - break; - } - - case NETPLAY_CMD_NOINPUT: - { - uint32_t frame; - - if (netplay->is_server) - return netplay_cmd_nak(netplay, connection); - - RECV(&frame, sizeof(frame)) - return netplay_cmd_nak(netplay, connection); - frame = ntohl(frame); - - if (frame != netplay->server_frame_count) - return netplay_cmd_nak(netplay, connection); - - netplay->server_ptr = NEXT_PTR(netplay->server_ptr); - netplay->server_frame_count++; - break; - } - - case NETPLAY_CMD_FLIP_PLAYERS: - if (cmd_size != sizeof(uint32_t)) - { - RARCH_ERR("CMD_FLIP_PLAYERS received an unexpected command size.\n"); - return netplay_cmd_nak(netplay, connection); - } - - RECV(&flip_frame, sizeof(flip_frame)) - { - RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n"); - return netplay_cmd_nak(netplay, connection); - } - - if (netplay->is_server) - return netplay_cmd_nak(netplay, connection); - - flip_frame = ntohl(flip_frame); - - if (flip_frame < netplay->server_frame_count) - { - RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n"); - return netplay_cmd_nak(netplay, connection); - } - - netplay->flip ^= true; - netplay->flip_frame = flip_frame; - - /* Force a rewind to assure the flip happens: This just prevents us - * from skipping other past the flip because our prediction was - * correct */ - if (flip_frame < netplay->self_frame_count) - netplay->force_rewind = true; - - RARCH_LOG("%s.\n", msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED)); - runloop_msg_queue_push( - msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED), 1, 180, false); - - break; - - case NETPLAY_CMD_SPECTATE: - { - uint32_t payload[2]; - - if (!netplay->is_server) - return netplay_cmd_nak(netplay, connection); - - if (connection->mode == NETPLAY_CONNECTION_PLAYING) - { - /* The frame we haven't received is their end frame */ - payload[0] = htonl(netplay->read_frame_count[connection->player]); - - /* Mark them as not playing anymore */ - connection->mode = NETPLAY_CONNECTION_SPECTATING; - netplay->connected_players &= ~(1<player); - - /* Tell everyone */ - payload[1] = htonl(connection->player); - netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); - - /* Announce it */ - msg[sizeof(msg)-1] = '\0'; - snprintf(msg, sizeof(msg)-1, "Player %d has left", connection->player+1); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - } - else - { - payload[0] = htonl(0); - } - - /* Tell the player even if they were confused */ - payload[1] = htonl(NETPLAY_CMD_MODE_BIT_YOU | connection->player); - netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); - break; - } - - case NETPLAY_CMD_PLAY: - { - uint32_t payload[2]; - uint32_t player = 0; - payload[0] = htonl(netplay->self_frame_count + 1); - - if (!netplay->is_server) - return netplay_cmd_nak(netplay, connection); - - /* Find an available player slot */ - for (player = 0; player < MAX_USERS; player++) - { - if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING && - netplay->self_player == player) && - !(netplay->connected_players & (1<mode != NETPLAY_CONNECTION_PLAYING) - { - /* Mark them as playing */ - connection->mode = NETPLAY_CONNECTION_PLAYING; - connection->player = player; - netplay->connected_players |= 1<player); - netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); - - /* Announce it */ - msg[sizeof(msg)-1] = '\0'; - snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - - } - - /* Tell the player even if they were confused */ - payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | - NETPLAY_CMD_MODE_BIT_YOU | connection->player); - netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); - - /* And expect their data */ - netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr); - netplay->read_frame_count[player] = netplay->self_frame_count + 1; - break; - } - - case NETPLAY_CMD_MODE: - { - uint32_t payload[2]; - uint32_t frame, mode, player; - size_t ptr; - struct delta_frame *dframe; - -#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 (cmd_size != sizeof(payload) || - netplay->is_server) - return netplay_cmd_nak(netplay, connection); - - RECV(payload, sizeof(payload)) - { - RARCH_ERR("NETPLAY_CMD_MODE failed to receive payload.\n"); - return netplay_cmd_nak(netplay, connection); - } - - if (netplay->is_server) - return netplay_cmd_nak(netplay, connection); - - 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]); - player = mode & 0xFFFF; - if (player >= MAX_USERS) - return netplay_cmd_nak(netplay, connection); - - if (mode & NETPLAY_CMD_MODE_BIT_YOU) - { - /* A change to me! */ - if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) - { - if (frame != netplay->server_frame_count) - return netplay_cmd_nak(netplay, connection); - - /* Hooray, I get to play now! */ - if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) - return netplay_cmd_nak(netplay, connection); - - netplay->self_mode = NETPLAY_CONNECTION_PLAYING; - netplay->self_player = player; - - /* Fix up current frame info */ - if (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) - { - memcpy(dframe->real_input_state[player], dframe->self_state, sizeof(dframe->self_state)); - dframe->have_real[player] = true; - send_input_frame(netplay, connection, NULL, dframe->frame, player, dframe->self_state); - if (dframe->frame == netplay->self_frame_count) break; - NEXT(); - } - - } - else - { - /* It wants future frames, make sure we don't capture or send intermediate ones */ - START(netplay->self_ptr); - while (dframe->used && dframe->frame < frame) - { - memset(dframe->self_state, 0, sizeof(dframe->self_state)); - memset(dframe->real_input_state[player], 0, sizeof(dframe->self_state)); - dframe->have_local = true; - NEXT(); - } - - } - - /* Announce it */ - msg[sizeof(msg)-1] = '\0'; - snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - - } - else /* YOU && !PLAYING */ - { - /* I'm no longer playing, but I should already know this */ - if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING) - return netplay_cmd_nak(netplay, connection); - - /* Announce it */ - strlcpy(msg, "You have left the game", sizeof(msg)); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - - } - - } - else /* !YOU */ - { - /* Somebody else is joining or parting */ - if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) - { - if (frame != netplay->server_frame_count) - return netplay_cmd_nak(netplay, connection); - - netplay->connected_players |= (1<read_ptr[player] = netplay->server_ptr; - netplay->read_frame_count[player] = netplay->server_frame_count; - - /* Announce it */ - msg[sizeof(msg)-1] = '\0'; - snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); - - } - else - { - netplay->connected_players &= ~(1<self_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)) - { - RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n"); - return netplay_cmd_nak(netplay, connection); - } - - 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->self_ptr); - - if (!found) - { - /* Oh well, we got rid of it! */ - 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 = netplay_delta_frame_crc( - netplay, &netplay->buffer[tmp_ptr]); - - if (buffer[1] != local_crc) - { - /* Problem! */ - 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: - { - uint32_t frame; - uint32_t isize; - uint32_t rd, wn; - uint32_t player; - - /* 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->self_ptr; - netplay->replay_frame_count = netplay->self_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) - 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. */ - if (cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t)) - { - RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); - return netplay_cmd_nak(netplay, connection); - } - - RECV(&frame, sizeof(frame)) - { - RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n"); - return netplay_cmd_nak(netplay, connection); - } - frame = ntohl(frame); - - if (frame != netplay->read_frame_count[connection->player]) - { - RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); - return netplay_cmd_nak(netplay, connection); - } - - RECV(&isize, sizeof(isize)) - { - RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); - return netplay_cmd_nak(netplay, connection); - } - 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)) - { - RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); - return netplay_cmd_nak(netplay, connection); - } - - /* And decompress it */ - netplay->decompression_backend->set_in(netplay->decompression_stream, - netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); - netplay->decompression_backend->set_out(netplay->decompression_stream, - (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state, - netplay->state_size); - netplay->decompression_backend->trans(netplay->decompression_stream, - true, &rd, &wn, NULL); - - /* Skip ahead if it's past where we are */ - if (frame > netplay->self_frame_count) - { - /* 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->self_ptr = PREV_PTR(netplay->read_ptr[connection->player]); - netplay->self_frame_count = frame - 1; - } - - /* Don't expect earlier data from other clients */ - for (player = 0; player < MAX_USERS; player++) - { - if (!(netplay->connected_players & (1< netplay->read_frame_count[player]) - { - netplay->read_ptr[player] = netplay->read_ptr[connection->player]; - netplay->read_frame_count[player] = frame; - } - } - - /* And force rewind to it */ - netplay->force_rewind = true; - netplay->savestate_request_outstanding = false; - netplay->other_ptr = netplay->read_ptr[connection->player]; - netplay->other_frame_count = frame; - break; - } - - case NETPLAY_CMD_PAUSE: - connection->paused = true; - netplay->remote_paused = true; - netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE, NULL, 0); - break; - - case NETPLAY_CMD_RESUME: - { - 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); - break; - } - - default: - RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); - return netplay_cmd_nak(netplay, connection); - } - - 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; - - do - { - had_input = false; - - netplay->timeout_cnt++; - - /* Make sure we're actually ready for data */ - netplay_update_unread_ptr(netplay); - if (!netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) - break; - if (!netplay->is_server && - !netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->server_ptr], - netplay->server_frame_count)) - break; - - /* 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->self_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->self_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_simulate_input: * @netplay : pointer to netplay object @@ -1356,24 +333,6 @@ void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) } } -/** - * netplay_flip_port - * - * Should we flip ports 0 and 1? - */ -bool netplay_flip_port(netplay_t *netplay) -{ - size_t frame = netplay->self_frame_count; - - if (netplay->flip_frame == 0) - return false; - - if (netplay->is_replay) - frame = netplay->replay_frame_count; - - return netplay->flip ^ (frame < netplay->flip_frame); -} - #ifndef HAVE_SOCKET_LEGACY /* Custom inet_ntop. Win32 doesn't seem to support this ... */ void netplay_log_connection(const struct sockaddr_storage *their_addr, diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c new file mode 100644 index 0000000000..2a29d72592 --- /dev/null +++ b/network/netplay/netplay_io.c @@ -0,0 +1,1065 @@ +/* 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 "../../runloop.h" + +/** + * netplay_hangup: + * + * Disconnects an active Netplay connection due to an error + **/ +void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) +{ + if (!netplay) + return; + if (!connection->active) + return; + + RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); + runloop_msg_queue_push("Netplay has disconnected. Will continue without connection.", 0, 480, false); + + 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 = 0; + + } + else + { + /* Remove this player */ + if (connection->mode == NETPLAY_CONNECTION_PLAYING) + { + netplay->connected_players &= ~(1<player); + + /* FIXME: Duplication */ + if (netplay->is_server) + { + uint32_t payload[2]; + payload[0] = htonl(netplay->read_frame_count[connection->player]); + payload[1] = htonl(connection->player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + } + } + + } +} + +/* Send the specified input data */ +static bool send_input_frame(netplay_t *netplay, + struct netplay_connection *only, struct netplay_connection *except, + uint32_t frame, uint32_t player, uint32_t *state) +{ + uint32_t buffer[2 + WORDS_PER_FRAME]; + size_t i; + + buffer[0] = htonl(NETPLAY_CMD_INPUT); + buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); + buffer[2] = htonl(frame); + buffer[3] = htonl(player); + buffer[4] = htonl(state[0]); + buffer[5] = htonl(state[1]); + buffer[6] = htonl(state[2]); + + if (only) + { + if (!netplay_send(&only->send_packet_buffer, only->fd, buffer, sizeof(buffer))) + { + 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 || + connection->player != player)) + { + if (!netplay_send(&connection->send_packet_buffer, connection->fd, + buffer, sizeof(buffer))) + netplay_hangup(netplay, connection); + } + } + } + + return true; +} + +/* Send the current input frame */ +bool netplay_send_cur_input(netplay_t *netplay, + struct netplay_connection *connection) +{ + struct delta_frame *dframe = &netplay->buffer[netplay->self_ptr]; + uint32_t player; + + if (netplay->is_server) + { + /* Send the other players' input data */ + for (player = 0; player < MAX_USERS; player++) + { + if (connection->mode == NETPLAY_CONNECTION_PLAYING && + connection->player == player) + continue; + if ((netplay->connected_players & (1<have_real[player]) + { + if (!send_input_frame(netplay, connection, NULL, + netplay->self_frame_count, player, + dframe->real_input_state[player])) + 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) + { + if (!send_input_frame(netplay, connection, NULL, + netplay->self_frame_count, + (netplay->is_server ? NETPLAY_CMD_INPUT_BIT_SERVER : 0) | netplay->self_player, + dframe->self_state)) + 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); + } + } +} + +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; +} + +bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) +{ + uint32_t payload[2]; + bool success = true; + size_t i; + 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; +} + +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); +} + +bool netplay_cmd_mode(netplay_t *netplay, + struct netplay_connection *connection, + enum rarch_netplay_connection_mode mode) +{ + uint32_t cmd; + switch (mode) + { + case NETPLAY_CONNECTION_SPECTATING: + cmd = NETPLAY_CMD_SPECTATE; + break; + + case NETPLAY_CONNECTION_PLAYING: + cmd = NETPLAY_CMD_PLAY; + break; + + default: + return false; + } + return netplay_send_raw_cmd(netplay, connection, cmd, NULL, 0); +} + +static bool netplay_get_cmd(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + uint32_t cmd; + uint32_t flip_frame; + uint32_t cmd_size; + ssize_t recvd; + char msg[512]; + + /* We don't handle the initial handshake here */ + switch (connection->mode) + { + case NETPLAY_CONNECTION_NONE: + /* Huh?! */ + return false; + case NETPLAY_CONNECTION_INIT: + return netplay_handshake_init(netplay, connection, had_input); + case NETPLAY_CONNECTION_PRE_NICK: + { + bool ret = netplay_handshake_pre_nick(netplay, connection, had_input); + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !netplay_send_cur_input(netplay, connection)) + return false; + return ret; + } + case NETPLAY_CONNECTION_PRE_PASSWORD: + { + bool ret = netplay_handshake_pre_password(netplay, connection, had_input); + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !netplay_send_cur_input(netplay, connection)) + return false; + return ret; + } + case NETPLAY_CONNECTION_PRE_SYNC: + { + bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !netplay_send_cur_input(netplay, connection)) + return false; + return ret; + } + default: + break; + } + + /* FIXME: This depends on delta_frame_ready */ + +#define RECV(buf, sz) \ + recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \ + (sz), false); \ + if (recvd >= 0 && recvd < (sz)) goto shrt; \ + else if (recvd < 0) + + RECV(&cmd, sizeof(cmd)) + return false; + + cmd = ntohl(cmd); + + RECV(&cmd_size, sizeof(cmd_size)) + return false; + + cmd_size = ntohl(cmd_size); + + 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 buffer[WORDS_PER_FRAME]; + uint32_t player; + unsigned i; + struct delta_frame *dframe; + + if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) + { + RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(buffer, sizeof(buffer)) + { + RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); + return netplay_cmd_nak(netplay, connection); + } + + for (i = 0; i < WORDS_PER_FRAME; i++) + buffer[i] = ntohl(buffer[i]); + + if (netplay->is_server) + { + /* Ignore the claimed player #, must be this client */ + if (connection->mode != NETPLAY_CONNECTION_PLAYING) + return netplay_cmd_nak(netplay, connection); + player = connection->player; + } + else + { + player = buffer[1] & ~NETPLAY_CMD_INPUT_BIT_SERVER; + } + + if (player >= MAX_USERS || !(netplay->connected_players & (1<read_frame_count[player]) + { + /* We already had this, so ignore the new transmission */ + break; + } + else if (buffer[0] > netplay->read_frame_count[player]) + { + /* Out of order = out of luck */ + return netplay_cmd_nak(netplay, connection); + } + + /* The data's good! */ + dframe = &netplay->buffer[netplay->read_ptr[player]]; + if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player])) + { + /* FIXME: Catastrophe! */ + return netplay_cmd_nak(netplay, connection); + } + memcpy(dframe->real_input_state[player], buffer + 2, + WORDS_PER_INPUT*sizeof(uint32_t)); + dframe->have_real[player] = true; + netplay->read_ptr[player] = NEXT_PTR(netplay->read_ptr[player]); + netplay->read_frame_count[player]++; + + if (netplay->is_server) + { + /* Forward it on if it's past data*/ + if (dframe->frame <= netplay->self_frame_count) + send_input_frame(netplay, NULL, connection, buffer[0], + player, dframe->real_input_state[player]); + } + + /* If this was server data, advance our server pointer too */ + if (!netplay->is_server && (buffer[1] & NETPLAY_CMD_INPUT_BIT_SERVER)) + { + netplay->server_ptr = netplay->read_ptr[player]; + netplay->server_frame_count = netplay->read_frame_count[player]; + } + break; + } + + case NETPLAY_CMD_NOINPUT: + { + uint32_t frame; + + if (netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + RECV(&frame, sizeof(frame)) + return netplay_cmd_nak(netplay, connection); + frame = ntohl(frame); + + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + + netplay->server_ptr = NEXT_PTR(netplay->server_ptr); + netplay->server_frame_count++; + break; + } + + case NETPLAY_CMD_FLIP_PLAYERS: + if (cmd_size != sizeof(uint32_t)) + { + RARCH_ERR("CMD_FLIP_PLAYERS received an unexpected command size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(&flip_frame, sizeof(flip_frame)) + { + RARCH_ERR("Failed to receive CMD_FLIP_PLAYERS argument.\n"); + return netplay_cmd_nak(netplay, connection); + } + + if (netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + flip_frame = ntohl(flip_frame); + + if (flip_frame < netplay->server_frame_count) + { + RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n"); + return netplay_cmd_nak(netplay, connection); + } + + netplay->flip ^= true; + netplay->flip_frame = flip_frame; + + /* Force a rewind to assure the flip happens: This just prevents us + * from skipping other past the flip because our prediction was + * correct */ + if (flip_frame < netplay->self_frame_count) + netplay->force_rewind = true; + + RARCH_LOG("%s.\n", msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED)); + runloop_msg_queue_push( + msg_hash_to_str(MSG_NETPLAY_USERS_HAS_FLIPPED), 1, 180, false); + + break; + + case NETPLAY_CMD_SPECTATE: + { + uint32_t payload[2]; + + if (!netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + if (connection->mode == NETPLAY_CONNECTION_PLAYING) + { + /* The frame we haven't received is their end frame */ + payload[0] = htonl(netplay->read_frame_count[connection->player]); + + /* Mark them as not playing anymore */ + connection->mode = NETPLAY_CONNECTION_SPECTATING; + netplay->connected_players &= ~(1<player); + + /* Tell everyone */ + payload[1] = htonl(connection->player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "Player %d has left", connection->player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + } + else + { + payload[0] = htonl(0); + } + + /* Tell the player even if they were confused */ + payload[1] = htonl(NETPLAY_CMD_MODE_BIT_YOU | connection->player); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + break; + } + + case NETPLAY_CMD_PLAY: + { + uint32_t payload[2]; + uint32_t player = 0; + payload[0] = htonl(netplay->self_frame_count + 1); + + if (!netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + /* Find an available player slot */ + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING && + netplay->self_player == player) && + !(netplay->connected_players & (1<mode != NETPLAY_CONNECTION_PLAYING) + { + /* Mark them as playing */ + connection->mode = NETPLAY_CONNECTION_PLAYING; + connection->player = player; + netplay->connected_players |= 1<player); + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + + } + + /* Tell the player even if they were confused */ + payload[1] = htonl(NETPLAY_CMD_MODE_BIT_PLAYING | + NETPLAY_CMD_MODE_BIT_YOU | connection->player); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE, payload, sizeof(payload)); + + /* And expect their data */ + netplay->read_ptr[player] = NEXT_PTR(netplay->self_ptr); + netplay->read_frame_count[player] = netplay->self_frame_count + 1; + break; + } + + case NETPLAY_CMD_MODE: + { + uint32_t payload[2]; + uint32_t frame, mode, player; + size_t ptr; + struct delta_frame *dframe; + +#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 (cmd_size != sizeof(payload) || + netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + RECV(payload, sizeof(payload)) + { + RARCH_ERR("NETPLAY_CMD_MODE failed to receive payload.\n"); + return netplay_cmd_nak(netplay, connection); + } + + if (netplay->is_server) + return netplay_cmd_nak(netplay, connection); + + 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]); + player = mode & 0xFFFF; + if (player >= MAX_USERS) + return netplay_cmd_nak(netplay, connection); + + if (mode & NETPLAY_CMD_MODE_BIT_YOU) + { + /* A change to me! */ + if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) + { + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + + /* Hooray, I get to play now! */ + if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) + return netplay_cmd_nak(netplay, connection); + + netplay->self_mode = NETPLAY_CONNECTION_PLAYING; + netplay->self_player = player; + + /* Fix up current frame info */ + if (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) + { + memcpy(dframe->real_input_state[player], dframe->self_state, sizeof(dframe->self_state)); + dframe->have_real[player] = true; + send_input_frame(netplay, connection, NULL, dframe->frame, player, dframe->self_state); + if (dframe->frame == netplay->self_frame_count) break; + NEXT(); + } + + } + else + { + /* It wants future frames, make sure we don't capture or send intermediate ones */ + START(netplay->self_ptr); + while (dframe->used && dframe->frame < frame) + { + memset(dframe->self_state, 0, sizeof(dframe->self_state)); + memset(dframe->real_input_state[player], 0, sizeof(dframe->self_state)); + dframe->have_local = true; + NEXT(); + } + + } + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + + } + else /* YOU && !PLAYING */ + { + /* I'm no longer playing, but I should already know this */ + if (netplay->self_mode != NETPLAY_CONNECTION_SPECTATING) + return netplay_cmd_nak(netplay, connection); + + /* Announce it */ + strlcpy(msg, "You have left the game", sizeof(msg)); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + + } + + } + else /* !YOU */ + { + /* Somebody else is joining or parting */ + if (mode & NETPLAY_CMD_MODE_BIT_PLAYING) + { + if (frame != netplay->server_frame_count) + return netplay_cmd_nak(netplay, connection); + + netplay->connected_players |= (1<read_ptr[player] = netplay->server_ptr; + netplay->read_frame_count[player] = netplay->server_frame_count; + + /* Announce it */ + msg[sizeof(msg)-1] = '\0'; + snprintf(msg, sizeof(msg)-1, "Player %d has joined", player+1); + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + + } + else + { + netplay->connected_players &= ~(1<self_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)) + { + RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n"); + return netplay_cmd_nak(netplay, connection); + } + + 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->self_ptr); + + if (!found) + { + /* Oh well, we got rid of it! */ + 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 = netplay_delta_frame_crc( + netplay, &netplay->buffer[tmp_ptr]); + + if (buffer[1] != local_crc) + { + /* Problem! */ + 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: + { + uint32_t frame; + uint32_t isize; + uint32_t rd, wn; + uint32_t player; + + /* 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->self_ptr; + netplay->replay_frame_count = netplay->self_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) + 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. */ + if (cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(&frame, sizeof(frame)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n"); + return netplay_cmd_nak(netplay, connection); + } + frame = ntohl(frame); + + if (frame != netplay->read_frame_count[connection->player]) + { + RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); + return netplay_cmd_nak(netplay, connection); + } + + RECV(&isize, sizeof(isize)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); + return netplay_cmd_nak(netplay, connection); + } + 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)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); + return netplay_cmd_nak(netplay, connection); + } + + /* And decompress it */ + netplay->decompression_backend->set_in(netplay->decompression_stream, + netplay->zbuffer, cmd_size - 2*sizeof(uint32_t)); + netplay->decompression_backend->set_out(netplay->decompression_stream, + (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state, + netplay->state_size); + netplay->decompression_backend->trans(netplay->decompression_stream, + true, &rd, &wn, NULL); + + /* Skip ahead if it's past where we are */ + if (frame > netplay->self_frame_count) + { + /* 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->self_ptr = PREV_PTR(netplay->read_ptr[connection->player]); + netplay->self_frame_count = frame - 1; + } + + /* Don't expect earlier data from other clients */ + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1< netplay->read_frame_count[player]) + { + netplay->read_ptr[player] = netplay->read_ptr[connection->player]; + netplay->read_frame_count[player] = frame; + } + } + + /* And force rewind to it */ + netplay->force_rewind = true; + netplay->savestate_request_outstanding = false; + netplay->other_ptr = netplay->read_ptr[connection->player]; + netplay->other_frame_count = frame; + break; + } + + case NETPLAY_CMD_PAUSE: + connection->paused = true; + netplay->remote_paused = true; + netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE, NULL, 0); + break; + + case NETPLAY_CMD_RESUME: + { + 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); + break; + } + + default: + RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); + return netplay_cmd_nak(netplay, connection); + } + + 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; + + do + { + had_input = false; + + netplay->timeout_cnt++; + + /* Make sure we're actually ready for data */ + netplay_update_unread_ptr(netplay); + if (!netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) + break; + if (!netplay->is_server && + !netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->server_ptr], + netplay->server_frame_count)) + break; + + /* 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->self_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->self_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_flip_port + * + * Should we flip ports 0 and 1? + */ +bool netplay_flip_port(netplay_t *netplay) +{ + size_t frame = netplay->self_frame_count; + + if (netplay->flip_frame == 0) + return false; + + if (netplay->is_replay) + frame = netplay->replay_frame_count; + + return netplay->flip ^ (frame < netplay->flip_frame); +} diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 32be86d8d2..c40e9e4c4b 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -47,6 +47,8 @@ #define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */ #define MAX_STALL_TIME_USEC (10*1000*1000) +#define MAX_RETRIES 16 +#define RETRY_MS 500 #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) From 257c5d3188ea9bc7ae000f1bd163de26fa83725c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 20:50:17 -0500 Subject: [PATCH 51/89] Refactoring netplay_init.c/netplay_net.c Split the remainder of netplay.c into netplay_init.c and netplay_net.c (which will soon be netplay_sync.c) --- Makefile.common | 4 +- griffin/griffin.c | 2 +- network/netplay/{netplay.c => netplay_init.c} | 113 +----------------- network/netplay/netplay_net.c | 101 ++++++++++++++++ 4 files changed, 108 insertions(+), 112 deletions(-) rename network/netplay/{netplay.c => netplay_init.c} (80%) diff --git a/Makefile.common b/Makefile.common index a9ed73c184..63b6a67e73 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1123,12 +1123,12 @@ ifeq ($(HAVE_NETWORKING), 1) # Netplay DEFINES += -DHAVE_NETWORK_CMD OBJ += network/netplay/netplay_frontend.o \ + network/netplay/netplay_init.o \ network/netplay/netplay_io.o \ network/netplay/netplay_net.o \ network/netplay/netplay_common.o \ network/netplay/netplay_discovery.o \ - network/netplay/netplay_buf.o \ - network/netplay/netplay.o + network/netplay/netplay_buf.o # Retro Achievements (also depends on threads) diff --git a/griffin/griffin.c b/griffin/griffin.c index 6e1d1b736a..55c0fe48ec 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -882,11 +882,11 @@ NETPLAY ============================================================ */ #ifdef HAVE_NETWORKING #include "../network/netplay/netplay_frontend.c" +#include "../network/netplay/netplay_init.c" #include "../network/netplay/netplay_io.c" #include "../network/netplay/netplay_net.c" #include "../network/netplay/netplay_common.c" #include "../network/netplay/netplay_discovery.c" -#include "../network/netplay/netplay.c" #include "../libretro-common/net/net_compat.c" #include "../libretro-common/net/net_socket.c" #include "../libretro-common/net/net_http.c" diff --git a/network/netplay/netplay.c b/network/netplay/netplay_init.c similarity index 80% rename from network/netplay/netplay.c rename to network/netplay/netplay_init.c index 415ff84672..33ca0d65da 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay_init.c @@ -19,23 +19,18 @@ #pragma comment(lib, "ws2_32") #endif +#include #include -#include +#include +#include #include -#include -#include -#include -#include -#include #include "netplay_private.h" + #include "netplay_discovery.h" #include "../../autosave.h" -#include "../../configuration.h" -#include "../../command.h" -#include "../../movie.h" #include "../../runloop.h" #if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) @@ -233,106 +228,6 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve return true; } -/** - * 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) - { - /* 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 player; - - for (player = 0; player < MAX_USERS; player++) - { - if (!(netplay->connected_players & (1<read_frame_count[player] < new_unread_frame_count) - { - new_unread_ptr = netplay->read_ptr[player]; - new_unread_frame_count = netplay->read_frame_count[player]; - } - } - - 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; - } - - netplay->unread_ptr = new_unread_ptr; - netplay->unread_frame_count = new_unread_frame_count; - } -} - -/** - * netplay_simulate_input: - * @netplay : pointer to netplay object - * @sim_ptr : frame index for which to simulate 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. - */ -void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) -{ - uint32_t player; - size_t prev; - struct delta_frame *simframe, *pframe; - - simframe = &netplay->buffer[sim_ptr]; - - for (player = 0; player < MAX_USERS; player++) - { - if (!(netplay->connected_players & (1<have_real[player]) continue; - - prev = PREV_PTR(netplay->read_ptr[player]); - pframe = &netplay->buffer[prev]; - - if (resim) - { - /* 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 = (1U<simulated_input_state[player][0] & keep; - sim_state |= pframe->real_input_state[player][0] & ~keep; - simframe->simulated_input_state[player][0] = sim_state; - } - else - { - memcpy(simframe->simulated_input_state[player], - pframe->real_input_state[player], - WORDS_PER_INPUT * sizeof(uint32_t)); - } - } -} - #ifndef HAVE_SOCKET_LEGACY /* Custom inet_ntop. Win32 doesn't seem to support this ... */ void netplay_log_connection(const struct sockaddr_storage *their_addr, diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index dfcb925c32..559417f330 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -32,6 +32,107 @@ #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) + { + /* 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 player; + + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1<read_frame_count[player] < new_unread_frame_count) + { + new_unread_ptr = netplay->read_ptr[player]; + new_unread_frame_count = netplay->read_frame_count[player]; + } + } + + 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; + } + + netplay->unread_ptr = new_unread_ptr; + netplay->unread_frame_count = new_unread_frame_count; + } +} + +/** + * netplay_simulate_input: + * @netplay : pointer to netplay object + * @sim_ptr : frame index for which to simulate 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. + */ +void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) +{ + uint32_t player; + size_t prev; + struct delta_frame *simframe, *pframe; + + simframe = &netplay->buffer[sim_ptr]; + + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay->connected_players & (1<have_real[player]) continue; + + prev = PREV_PTR(netplay->read_ptr[player]); + pframe = &netplay->buffer[prev]; + + if (resim) + { + /* 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 = (1U<simulated_input_state[player][0] & keep; + sim_state |= pframe->real_input_state[player][0] & ~keep; + simframe->simulated_input_state[player][0] = sim_state; + } + else + { + memcpy(simframe->simulated_input_state[player], + pframe->real_input_state[player], + WORDS_PER_INPUT * sizeof(uint32_t)); + } + } +} + static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta) { static bool crcs_valid = true; From 4e905bf524968376efbb2eae852e907c8579f143 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 20:52:44 -0500 Subject: [PATCH 52/89] Refactoring: netplay_sync.c Renamed netplay_net.c to netplay_sync.c, as all that remains in that file is synchronization-related functions. --- Makefile.common | 2 +- griffin/griffin.c | 2 +- network/netplay/{netplay_net.c => netplay_sync.c} | 11 +++-------- 3 files changed, 5 insertions(+), 10 deletions(-) rename network/netplay/{netplay_net.c => netplay_sync.c} (99%) diff --git a/Makefile.common b/Makefile.common index 63b6a67e73..966532b100 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1125,7 +1125,7 @@ ifeq ($(HAVE_NETWORKING), 1) OBJ += network/netplay/netplay_frontend.o \ network/netplay/netplay_init.o \ network/netplay/netplay_io.o \ - network/netplay/netplay_net.o \ + network/netplay/netplay_sync.o \ network/netplay/netplay_common.o \ network/netplay/netplay_discovery.o \ network/netplay/netplay_buf.o diff --git a/griffin/griffin.c b/griffin/griffin.c index 55c0fe48ec..e9f4a45bd5 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -884,7 +884,7 @@ NETPLAY #include "../network/netplay/netplay_frontend.c" #include "../network/netplay/netplay_init.c" #include "../network/netplay/netplay_io.c" -#include "../network/netplay/netplay_net.c" +#include "../network/netplay/netplay_sync.c" #include "../network/netplay/netplay_common.c" #include "../network/netplay/netplay_discovery.c" #include "../libretro-common/net/net_compat.c" diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_sync.c similarity index 99% rename from network/netplay/netplay_net.c rename to network/netplay/netplay_sync.c index 559417f330..e2992473c3 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_sync.c @@ -15,24 +15,19 @@ * If not, see . */ -#include -#include +#include +#include -#include -#include -#include +#include #include "netplay_private.h" -#include "retro_assert.h" - #include "../../autosave.h" #if 0 #define DEBUG_NONDETERMINISTIC_CORES #endif - /** * netplay_update_unread_ptr * From f619789e48c04c4500c532366a4465f860a4220f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 21:01:31 -0500 Subject: [PATCH 53/89] Refactoring: netplay_common.c -> netplay_handshake.c/netplay_delta.c Refactoring netplay_common into its two actual components, the handshake and delta-frame related functions. --- Makefile.common | 5 +- griffin/griffin.c | 4 +- network/netplay/netplay_delta.c | 51 +++++++ .../{netplay_common.c => netplay_handshake.c} | 137 ++++++++++++------ network/netplay/netplay_init.c | 90 ------------ network/netplay/netplay_sync.c | 2 +- 6 files changed, 154 insertions(+), 135 deletions(-) create mode 100644 network/netplay/netplay_delta.c rename network/netplay/{netplay_common.c => netplay_handshake.c} (88%) diff --git a/Makefile.common b/Makefile.common index 966532b100..2851353841 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1122,11 +1122,12 @@ ifeq ($(HAVE_NETWORKING), 1) # Netplay DEFINES += -DHAVE_NETWORK_CMD - OBJ += network/netplay/netplay_frontend.o \ + OBJ += network/netplay/netplay_delta.o \ + network/netplay/netplay_frontend.o \ + network/netplay/netplay_handshake.o \ network/netplay/netplay_init.o \ network/netplay/netplay_io.o \ network/netplay/netplay_sync.o \ - network/netplay/netplay_common.o \ network/netplay/netplay_discovery.o \ network/netplay/netplay_buf.o diff --git a/griffin/griffin.c b/griffin/griffin.c index e9f4a45bd5..bd80e87312 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -881,12 +881,14 @@ THREAD NETPLAY ============================================================ */ #ifdef HAVE_NETWORKING +#include "../network/netplay/netplay_delta.c" #include "../network/netplay/netplay_frontend.c" +#include "../network/netplay/netplay_handshake.c" #include "../network/netplay/netplay_init.c" #include "../network/netplay/netplay_io.c" #include "../network/netplay/netplay_sync.c" -#include "../network/netplay/netplay_common.c" #include "../network/netplay/netplay_discovery.c" +#include "../network/netplay/netplay_buf.c" #include "../libretro-common/net/net_compat.c" #include "../libretro-common/net/net_socket.c" #include "../libretro-common/net/net_http.c" diff --git a/network/netplay/netplay_delta.c b/network/netplay/netplay_delta.c new file mode 100644 index 0000000000..b5c49760e8 --- /dev/null +++ b/network/netplay/netplay_delta.c @@ -0,0 +1,51 @@ +/* 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 "netplay_private.h" + +bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) +{ + void *remember_state; + if (delta->used) + { + if (delta->frame == frame) return true; + if (netplay->other_frame_count <= delta->frame) + { + /* We haven't even replayed this frame yet, so we can't overwrite it! */ + return false; + } + } + remember_state = delta->state; + memset(delta, 0, sizeof(struct delta_frame)); + delta->used = true; + delta->frame = frame; + delta->state = remember_state; + return true; +} + +uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) +{ + if (!netplay->state_size) + return 0; + return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size); +} diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_handshake.c similarity index 88% rename from network/netplay/netplay_common.c rename to network/netplay/netplay_handshake.c index 715f04f6bc..0f4ac0e374 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_handshake.c @@ -15,17 +15,16 @@ * If not, see . */ +#include +#include +#include + +#include #include +#include #include "netplay_private.h" -#include -#include -#include -#include - -#include "../../movie.h" -#include "../../msg_hash.h" #include "../../configuration.h" #include "../../content.h" #include "../../retroarch.h" @@ -33,6 +32,96 @@ #include "../../version.h" #include "../../menu/widgets/menu_input_dialog.h" +#ifndef HAVE_SOCKET_LEGACY +/* Custom inet_ntop. Win32 doesn't seem to support this ... */ +void netplay_log_connection(const struct sockaddr_storage *their_addr, + unsigned slot, const char *nick) +{ + 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}; + char msg[512]; + + msg[0] = '\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(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM_NAME), + nick, str); + runloop_msg_queue_push(msg, 1, 180, false); + RARCH_LOG("%s\n", msg); + } + else + { + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM), + nick); + runloop_msg_queue_push(msg, 1, 180, false); + RARCH_LOG("%s\n", msg); + } + RARCH_LOG("%s %u\n", msg_hash_to_str(MSG_CONNECTION_SLOT), + slot); +} + +#else +void netplay_log_connection(const struct sockaddr_storage *their_addr, + unsigned slot, const char *nick) +{ + char msg[512]; + + msg[0] = '\0'; + + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM), + nick); + runloop_msg_queue_push(msg, 1, 180, false); + RARCH_LOG("%s\n", msg); + RARCH_LOG("%s %u\n", + msg_hash_to_str(MSG_CONNECTION_SLOT), slot); +} + +#endif + /** * netplay_impl_magic: * @@ -649,37 +738,3 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *c /* Ask to go to player mode */ return netplay_cmd_mode(netplay, connection, NETPLAY_CONNECTION_PLAYING); } - -bool netplay_is_server(netplay_t* netplay) -{ - if (!netplay) - return false; - return netplay->is_server; -} - -bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) -{ - void *remember_state; - if (delta->used) - { - if (delta->frame == frame) return true; - if (netplay->other_frame_count <= delta->frame) - { - /* We haven't even replayed this frame yet, so we can't overwrite it! */ - return false; - } - } - remember_state = delta->state; - memset(delta, 0, sizeof(struct delta_frame)); - delta->used = true; - delta->frame = frame; - delta->state = remember_state; - return true; -} - -uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) -{ - if (!netplay->state_size) - return 0; - return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size); -} diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 33ca0d65da..665dae8f61 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -228,96 +228,6 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve return true; } -#ifndef HAVE_SOCKET_LEGACY -/* Custom inet_ntop. Win32 doesn't seem to support this ... */ -void netplay_log_connection(const struct sockaddr_storage *their_addr, - unsigned slot, const char *nick) -{ - 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}; - char msg[512]; - - msg[0] = '\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(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM_NAME), - nick, str); - runloop_msg_queue_push(msg, 1, 180, false); - RARCH_LOG("%s\n", msg); - } - else - { - snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM), - nick); - runloop_msg_queue_push(msg, 1, 180, false); - RARCH_LOG("%s\n", msg); - } - RARCH_LOG("%s %u\n", msg_hash_to_str(MSG_CONNECTION_SLOT), - slot); -} - -#else -void netplay_log_connection(const struct sockaddr_storage *their_addr, - unsigned slot, const char *nick) -{ - char msg[512]; - - msg[0] = '\0'; - - snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_GOT_CONNECTION_FROM), - nick); - runloop_msg_queue_push(msg, 1, 180, false); - RARCH_LOG("%s\n", msg); - RARCH_LOG("%s %u\n", - msg_hash_to_str(MSG_CONNECTION_SLOT), slot); -} - -#endif - static bool netplay_init_socket_buffers(netplay_t *netplay) { /* Make our packet buffer big enough for a save state and frames-many frames diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index e2992473c3..7635074ca7 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -131,7 +131,7 @@ void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta) { static bool crcs_valid = true; - if (netplay_is_server(netplay)) + if (netplay->is_server) { if (netplay->check_frames && (delta->frame % netplay->check_frames == 0 || delta->frame == 1)) From 24a39078beeed6a18ffcb22171280ab231d9d802 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Dec 2016 22:03:10 -0500 Subject: [PATCH 54/89] Adding netplay password to menu. --- config.def.h | 2 ++ intl/msg_hash_lbl.h | 2 ++ intl/msg_hash_us.c | 5 +++++ intl/msg_hash_us.h | 4 +++- menu/menu_displaylist.c | 8 ++++++-- menu/menu_setting.c | 18 ++++++++++++++++-- msg_hash.h | 1 + 7 files changed, 35 insertions(+), 5 deletions(-) diff --git a/config.def.h b/config.def.h index d44acd7f50..12c2773810 100644 --- a/config.def.h +++ b/config.def.h @@ -798,6 +798,8 @@ static const unsigned autosave_interval = 0; * user 1 rather than user 2. */ static const bool netplay_client_swap_input = true; +static const bool netplay_nat_traversal = false; + static const unsigned netplay_delay_frames = 16; static const unsigned netplay_check_frames = 30; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 1e5e29a070..7a8b8557b2 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -589,6 +589,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, "netplay_nat_traversal") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_NICKNAME, "netplay_nickname") +MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PASSWORD, + "netplay_password") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS, "menu_netplay_settings") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index ace43f647f..1620d6f4a7 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1697,6 +1697,11 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) snprintf(s, len, "The address of the host to connect to."); break; + case MENU_ENUM_LABEL_NETPLAY_PASSWORD: + snprintf(s, len, + "The password for connecting to the netplay \n" + "host. Used only in host mode."); + break; case MENU_ENUM_LABEL_STDIN_CMD_ENABLE: snprintf(s, len, "Enable stdin command interface."); diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 5b84cfa9aa..dae1823cfd 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -920,12 +920,14 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_MODE, "Netplay Client Enable") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NICKNAME, "Username") +MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD, + "Server Password") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS, "Netplay settings") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE, "Netplay Spectator Enable") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_TCP_UDP_PORT, - "Netplay TCP/UDP Port") + "Netplay TCP Port") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL, "Netplay NAT Traversal") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_ENABLE, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 44f118ae6b..e19204bd26 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4689,6 +4689,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, PARSE_ONLY_UINT, false) != -1) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_NETPLAY_PASSWORD, + PARSE_ONLY_STRING, false) != -1) + count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, PARSE_ONLY_UINT, false) != -1) @@ -4698,11 +4702,11 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) PARSE_ONLY_UINT, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, - MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, + MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, PARSE_ONLY_BOOL, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, - MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, + MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, PARSE_ONLY_BOOL, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 789e76bdd3..f897490a3d 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -5577,6 +5577,20 @@ static bool setting_append_list( menu_settings_list_current_add_range(list, list_info, 0, 65535, 1, true, true); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + CONFIG_STRING( + list, list_info, + settings->netplay.password, + sizeof(settings->netplay.password), + MENU_ENUM_LABEL_NETPLAY_PASSWORD, + MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD, + "", + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + CONFIG_UINT( list, list_info, &settings->netplay.delay_frames, @@ -5588,7 +5602,7 @@ static bool setting_append_list( parent_group, general_write_handler, general_read_handler); - menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false); + menu_settings_list_current_add_range(list, list_info, 0, 60, 1, true, false); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_UINT( @@ -5602,7 +5616,7 @@ static bool setting_append_list( parent_group, general_write_handler, general_read_handler); - menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false); + menu_settings_list_current_add_range(list, list_info, 0, 600, 1, true, false); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_BOOL( diff --git a/msg_hash.h b/msg_hash.h index 3f975fc885..ca879df1ba 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1003,6 +1003,7 @@ enum msg_hash_enums MENU_LABEL(SORT_SAVEFILES_ENABLE), MENU_LABEL(SORT_SAVESTATES_ENABLE), MENU_LABEL(NETPLAY_IP_ADDRESS), + MENU_LABEL(NETPLAY_PASSWORD), MENU_LABEL(NETPLAY_MODE), MENU_LABEL(PERFCNT_ENABLE), MENU_LABEL(OVERLAY_SCALE), From 1b22191869c27a5466c155127920f8c5010a27df Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Dec 2016 11:45:11 -0500 Subject: [PATCH 55/89] Deduplicating some code. --- network/netplay/netplay_io.c | 45 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 2a29d72592..c910de072d 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -26,6 +26,30 @@ #include "../../runloop.h" +/** + * 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: * @@ -70,6 +94,10 @@ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) } } + + /* Unpause them */ + if (connection->paused) + remote_unpaused(netplay, connection); } /* Send the specified input data */ @@ -925,23 +953,8 @@ static bool netplay_get_cmd(netplay_t *netplay, break; case NETPLAY_CMD_RESUME: - { - 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); + remote_unpaused(netplay, connection); break; - } default: RARCH_ERR("%s.\n", msg_hash_to_str(MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED)); From 7ad4e3f11500d561c1535a2f363824df9beef5fb Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Dec 2016 21:28:20 -0500 Subject: [PATCH 56/89] Per-connection stalling --- network/netplay/netplay_frontend.c | 61 ++++++++++++++++++++++++++--- network/netplay/netplay_handshake.c | 2 +- network/netplay/netplay_private.h | 7 +++- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 3d397526b2..0a5bda016e 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -169,6 +169,8 @@ static bool get_self_input_state(netplay_t *netplay) static bool netplay_poll(void) { int res; + uint32_t player; + size_t i; netplay_data->can_poll = false; @@ -184,7 +186,6 @@ static bool netplay_poll(void) if (res == -1) { /* Catastrophe! */ - size_t i; for (i = 0; i < netplay_data->connections_size; i++) netplay_hangup(netplay_data, &netplay_data->connections[i]); return false; @@ -199,7 +200,15 @@ static bool netplay_poll(void) case NETPLAY_STALL_RUNNING_FAST: netplay_update_unread_ptr(netplay_data); if (netplay_data->unread_frame_count >= netplay_data->self_frame_count) + { netplay_data->stall = NETPLAY_STALL_NONE; + for (i = 0; i < netplay_data->connections_size; i++) + { + struct netplay_connection *connection = &netplay_data->connections[i]; + if (connection->active && connection->stall) + connection->stall = NETPLAY_STALL_NONE; + } + } break; case NETPLAY_STALL_NO_CONNECTION: @@ -213,6 +222,29 @@ static bool netplay_poll(void) { netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; netplay_data->stall_time = cpu_features_get_time_usec(); + + /* Figure out who to blame */ + if (netplay_data->is_server) + { + for (player = 0; player < MAX_USERS; player++) + { + if (!(netplay_data->connected_players & (1<read_frame_count[player] > netplay_data->unread_frame_count) continue; + for (i = 0; i < netplay_data->connections_size; i++) + { + struct netplay_connection *connection = &netplay_data->connections[i]; + if (connection->active && + connection->mode == NETPLAY_CONNECTION_PLAYING && + connection->player == player) + { + connection->stall = NETPLAY_STALL_RUNNING_FAST; + connection->stall_time = netplay_data->stall_time; + break; + } + } + } + } + } } @@ -224,12 +256,29 @@ static bool netplay_poll(void) /* Don't stall out while they're paused */ if (netplay_data->remote_paused) netplay_data->stall_time = now; - else if (now - netplay_data->stall_time >= MAX_STALL_TIME_USEC) + else if (now - netplay_data->stall_time >= + (netplay_data->is_server ? MAX_SERVER_STALL_TIME_USEC : + MAX_CLIENT_STALL_TIME_USEC)) { - /* Stalled out! (FIXME: Shouldn't be so nuclear) */ - size_t i; - for (i = 0; i < netplay_data->connections_size; i++) - netplay_hangup(netplay_data, &netplay_data->connections[i]); + /* Stalled out! */ + if (netplay_data->is_server) + { + for (i = 0; i < netplay_data->connections_size; i++) + { + struct netplay_connection *connection = &netplay_data->connections[i]; + if (connection->active && + connection->mode == NETPLAY_CONNECTION_PLAYING && + connection->stall && + now - connection->stall_time >= MAX_SERVER_STALL_TIME_USEC) + { + netplay_hangup(netplay_data, connection); + } + } + } + else + { + netplay_hangup(netplay_data, &netplay_data->connections[0]); + } return false; } } diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 0f4ac0e374..86a0791c7a 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -453,7 +453,7 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio /* Unstall if we were waiting for this */ if (netplay->stall == NETPLAY_STALL_NO_CONNECTION) - netplay->stall = 0; + netplay->stall = NETPLAY_STALL_NONE; } bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *connection) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index c40e9e4c4b..77e8358187 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -46,7 +46,8 @@ #define NETPLAY_PASS_LEN 128 #define NETPLAY_PASS_HASH_LEN 64 /* length of a SHA-256 hash */ -#define MAX_STALL_TIME_USEC (10*1000*1000) +#define MAX_SERVER_STALL_TIME_USEC (5*1000*1000) +#define MAX_CLIENT_STALL_TIME_USEC (10*1000*1000) #define MAX_RETRIES 16 #define RETRY_MS 500 @@ -270,6 +271,10 @@ struct netplay_connection /* Is this player paused? */ bool paused; + + /* Is this connection stalling? */ + enum rarch_netplay_stall_reason stall; + retro_time_t stall_time; }; struct netplay From 694b7a9723946539a12447009a185acf0f51800f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Dec 2016 22:02:01 -0500 Subject: [PATCH 57/89] Don't allow more players to join than are actually being polled --- network/netplay/netplay_frontend.c | 3 +++ network/netplay/netplay_init.c | 1 + network/netplay/netplay_io.c | 8 ++++---- network/netplay/netplay_private.h | 5 ++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 0a5bda016e..f69695a368 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -333,6 +333,9 @@ static int16_t netplay_input_state(netplay_t *netplay, return 0; } + if (port > netplay->player_max) + netplay->player_max = port; + if (netplay->buffer[ptr].have_real[port]) { netplay->buffer[ptr].used_real[port] = true; diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 665dae8f61..ec6fd26d83 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -414,6 +414,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->tcp_port = port; netplay->cbs = *cb; netplay->connected_players = 0; + netplay->player_max = 1; netplay->is_server = server == NULL; netplay->nat_traversal = netplay->is_server ? nat_traversal : false; netplay->delay_frames = delay_frames; diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index c910de072d..cc4ef274ea 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -567,17 +567,17 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); /* Find an available player slot */ - for (player = 0; player < MAX_USERS; player++) + for (player = 0; player <= netplay->player_max; player++) { if (!(netplay->self_mode == NETPLAY_CONNECTION_PLAYING && netplay->self_player == player) && !(netplay->connected_players & (1< netplay->player_max) { - /* FIXME */ - return netplay_cmd_nak(netplay, connection); + /* Sorry, you can't play! */ + break; } if (connection->mode != NETPLAY_CONNECTION_PLAYING) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 77e8358187..42c6e80695 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -304,7 +304,10 @@ struct netplay /* Bitmap of players with controllers (whether local or remote) (low bit is * player 1) */ - int connected_players; + uint32_t connected_players; + + /* Maximum player number */ + uint32_t player_max; struct retro_callbacks cbs; From 03415c261dea93417ed19dfcc1f92c4b5b4666b8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Dec 2016 22:18:24 -0500 Subject: [PATCH 58/89] Added netplay spectator password separate from play password --- command.c | 3 ++- configuration.c | 1 + configuration.h | 3 ++- network/netplay/netplay.h | 6 ++++-- network/netplay/netplay_frontend.c | 7 +++--- network/netplay/netplay_handshake.c | 33 +++++++++++++++++++++++------ network/netplay/netplay_init.c | 12 ++++++----- network/netplay/netplay_io.c | 3 +++ network/netplay/netplay_private.h | 23 +++++++++++++------- 9 files changed, 64 insertions(+), 27 deletions(-) diff --git a/command.c b/command.c index 304e13ffca..510b3c5ab2 100644 --- a/command.c +++ b/command.c @@ -2364,7 +2364,8 @@ bool command_event(enum event_command cmd, void *data) if (!init_netplay( data, settings->netplay.server, settings->netplay.port, - settings->netplay.password)) + settings->netplay.password, + settings->netplay.spectate_password)) return false; #endif break; diff --git a/configuration.c b/configuration.c index 8d3c52aaef..33d65f2a7c 100644 --- a/configuration.c +++ b/configuration.c @@ -608,6 +608,7 @@ static int populate_settings_path(settings_t *settings, struct config_path_setti #ifdef HAVE_NETWORKING SETTING_PATH("netplay_ip_address", settings->netplay.server, false, NULL, true); SETTING_PATH("netplay_password", settings->netplay.password, false, NULL, true); + SETTING_PATH("netplay_spectate_password", settings->netplay.spectate_password, false, NULL, true); #endif SETTING_PATH("recording_output_directory", global->record.output_dir, false, NULL, true); diff --git a/configuration.h b/configuration.h index 993a987968..ade5ac6815 100644 --- a/configuration.h +++ b/configuration.h @@ -405,7 +405,8 @@ typedef struct settings unsigned check_frames; bool swap_input; bool nat_traversal; - char password[127]; + char password[128]; + char spectate_password[128]; } netplay; #endif diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index ea377ccc65..3d87d620da 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -62,7 +62,8 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); * @direct_host : Host to connect to directly, if applicable (client only) * @server : server address to connect to (client only) * @port : TCP port to host on/connect to - * @password : Password required to connect (server only) + * @play_password : Password required to play (server only) + * @spectate_password : Password required to connect (server only) * * Initializes netplay. * @@ -70,7 +71,8 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); * * Returns: true (1) if successful, otherwise false (0). **/ -bool init_netplay(void *direct_host, const char *server, unsigned port, const char *password); +bool init_netplay(void *direct_host, const char *server, unsigned port, + const char *play_password, const char *spectate_password); void deinit_netplay(void); diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index f69695a368..73754c8672 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -824,7 +824,8 @@ void deinit_netplay(void) * @direct_host : Host to connect to directly, if applicable (client only) * @server : server address to connect to (client only) * @port : TCP port to host on/connect to - * @password : Password required to connect (server only) + * @play_password : Password required to play (server only) + * @spectate_password : Password required to connect (server only) * * Initializes netplay. * @@ -833,7 +834,7 @@ void deinit_netplay(void) * Returns: true (1) if successful, otherwise false (0). **/ bool init_netplay(void *direct_host, const char *server, unsigned port, - const char *password) + const char *play_password, const char *spectate_password) { struct retro_callbacks cbs = {0}; settings_t *settings = config_get_ptr(); @@ -889,7 +890,7 @@ bool init_netplay(void *direct_host, const char *server, unsigned port, netplay_is_client ? direct_host : NULL, netplay_is_client ? server : NULL, port ? port : RARCH_DEFAULT_PORT, - password, + play_password, spectate_password, settings->netplay.delay_frames, settings->netplay.check_frames, &cbs, settings->netplay.nat_traversal, settings->username, quirks); diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 86a0791c7a..90e6cc7996 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -240,7 +240,7 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection * header[1] = htonl(*content_crc_ptr); header[2] = htonl(netplay_platform_magic()); header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); - if (netplay->is_server && netplay->password[0]) + if (netplay->is_server && (netplay->play_password[0] || netplay->spectate_password[0])) { /* Demand a password */ if (simple_rand_next == 1) @@ -542,13 +542,14 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c if (netplay->is_server) { - if (netplay->password[0]) + if (netplay->play_password[0] || netplay->spectate_password[0]) { /* There's a password, so just put them in PRE_PASSWORD mode */ connection->mode = NETPLAY_CONNECTION_PRE_PASSWORD; } else { + connection->can_play = true; if (!netplay_handshake_sync(netplay, connection)) return false; } @@ -572,6 +573,7 @@ bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connectio char password[8+NETPLAY_PASS_LEN]; /* 8 for salt */ ssize_t recvd; char msg[512]; + bool correct; msg[0] = '\0'; @@ -594,15 +596,32 @@ bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connectio return false; } - /* Calculate the correct password */ + /* Calculate the correct password hash(es) and compare */ + correct = false; snprintf(password, sizeof(password), "%08X", connection->salt); - strlcpy(password + 8, netplay->password, sizeof(password)-8); - sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password)); + if (netplay->play_password[0]) + { + strlcpy(password + 8, netplay->play_password, sizeof(password)-8); + sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password)); + if (!memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password))) + { + correct = true; + connection->can_play = true; + } + } + if (netplay->spectate_password[0]) + { + strlcpy(password + 8, netplay->spectate_password, sizeof(password)-8); + sha256_hash(corr_password_buf.password, (uint8_t *) password, strlen(password)); + if (!memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password))) + correct = true; + } - /* Compare them */ - if (memcmp(password_buf.password, corr_password_buf.password, sizeof(password_buf.password))) + /* Just disconnect if it was wrong */ + if (!correct) return false; + /* Otherwise, we're ready! */ if (!netplay_handshake_sync(netplay, connection)) return false; diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index ec6fd26d83..dcbd2e71cb 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -388,7 +388,8 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. * @port : Port of server. - * @password : Password required to connect. + * @play_password : Password required to play. + * @spectate_password : Password required to connect. * @delay_frames : Amount of delay frames. * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. @@ -402,9 +403,9 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * Returns: new netplay handle. **/ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, - const char *password, unsigned delay_frames, unsigned check_frames, - const struct retro_callbacks *cb, bool nat_traversal, const char *nick, - uint64_t quirks) + const char *play_password, const char *spectate_password, + unsigned delay_frames, unsigned 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) @@ -437,7 +438,8 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, } strlcpy(netplay->nick, nick[0] ? nick : RARCH_DEFAULT_NICK, sizeof(netplay->nick)); - strlcpy(netplay->password, password ? password : "", sizeof(netplay->password)); + strlcpy(netplay->play_password, play_password ? play_password : "", sizeof(netplay->play_password)); + strlcpy(netplay->spectate_password, spectate_password ? spectate_password : "", sizeof(netplay->spectate_password)); if (!init_socket(netplay, direct_host, server, port)) { diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index cc4ef274ea..ab603ed386 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -566,6 +566,9 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay->is_server) return netplay_cmd_nak(netplay, connection); + if (!connection->can_play) + break; + /* Find an available player slot */ for (player = 0; player <= netplay->player_max; player++) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 42c6e80695..81c4a2fb05 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -260,6 +260,9 @@ struct netplay_connection /* Salt associated with password transaction */ uint32_t salt; + /* Is this connection allowed to play (server only)? */ + bool can_play; + /* Buffers for sending and receiving data */ struct socket_buffer send_packet_buffer, recv_packet_buffer; @@ -288,8 +291,11 @@ struct netplay /* TCP connection for listening (server only) */ int listen_fd; + /* Password required to play (server only) */ + char play_password[NETPLAY_PASS_LEN]; + /* Password required to connect (server only) */ - char password[NETPLAY_PASS_LEN]; + char spectate_password[NETPLAY_PASS_LEN]; /* Our player number */ uint32_t self_player; @@ -420,23 +426,24 @@ void input_poll_net(void); * @direct_host : Netplay host discovered from scanning. * @server : IP address of server. * @port : Port of server. - * @password : Password required to connect. + * @play_password : Password required to play. + * @spectate_password : Password required to connect. * @delay_frames : Amount of delay frames. * @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. + * @quirks : Netplay quirks required for this session. * - * Creates a new netplay handle. A NULL host means we're + * Creates a new netplay handle. A NULL host means we're * hosting (user 1). * * Returns: new netplay handle. **/ -netplay_t *netplay_new(void *direct_host, const char *server, - uint16_t port, const char *password, unsigned delay_frames, - unsigned check_frames, const struct retro_callbacks *cb, - bool nat_traversal, const char *nick, uint64_t quirks); +netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, + const char *play_password, const char *spectate_password, + unsigned delay_frames, unsigned check_frames, const struct retro_callbacks + *cb, bool nat_traversal, const char *nick, uint64_t quirks); /** * netplay_free: From a2d377f08975c139911f4c282112b6eab8fbdfa0 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Dec 2016 22:25:46 -0500 Subject: [PATCH 59/89] Adding spectate password to the menu --- intl/msg_hash_lbl.h | 2 ++ intl/msg_hash_us.c | 6 ++++++ intl/msg_hash_us.h | 2 ++ menu/menu_displaylist.c | 4 ++++ menu/menu_setting.c | 14 ++++++++++++++ msg_hash.h | 1 + 6 files changed, 29 insertions(+) diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 7a8b8557b2..73886da4cc 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -593,6 +593,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_PASSWORD, "netplay_password") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SETTINGS, "menu_netplay_settings") +MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD, + "netplay_spectate_password") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, "netplay_spectator_mode_enable") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 1620d6f4a7..cce55e8447 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1702,6 +1702,12 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) "The password for connecting to the netplay \n" "host. Used only in host mode."); break; + case MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD: + snprintf(s, len, + "The password for connecting to the netplay \n" + "host with only spectator privileges. Used \n" + "only in host mode."); + break; case MENU_ENUM_LABEL_STDIN_CMD_ENABLE: snprintf(s, len, "Enable stdin command interface."); diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index dae1823cfd..af1e9544aa 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -924,6 +924,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD, "Server Password") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS, "Netplay settings") +MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD, + "Server Spectate-Only Password") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE, "Netplay Spectator Enable") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_TCP_UDP_PORT, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index e19204bd26..048f67be9d 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4693,6 +4693,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NETPLAY_PASSWORD, PARSE_ONLY_STRING, false) != -1) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD, + PARSE_ONLY_STRING, false) != -1) + count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, PARSE_ONLY_UINT, false) != -1) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index f897490a3d..d6cd6a53c4 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -5591,6 +5591,20 @@ static bool setting_append_list( general_read_handler); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + CONFIG_STRING( + list, list_info, + settings->netplay.spectate_password, + sizeof(settings->netplay.spectate_password), + MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD, + MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD, + "", + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + CONFIG_UINT( list, list_info, &settings->netplay.delay_frames, diff --git a/msg_hash.h b/msg_hash.h index ca879df1ba..7793fd026c 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1004,6 +1004,7 @@ enum msg_hash_enums MENU_LABEL(SORT_SAVESTATES_ENABLE), MENU_LABEL(NETPLAY_IP_ADDRESS), MENU_LABEL(NETPLAY_PASSWORD), + MENU_LABEL(NETPLAY_SPECTATE_PASSWORD), MENU_LABEL(NETPLAY_MODE), MENU_LABEL(PERFCNT_ENABLE), MENU_LABEL(OVERLAY_SCALE), From 9c6ac2b9346ea7ced9bdb0f69f1d5433f956fd4c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Dec 2016 22:31:01 -0500 Subject: [PATCH 60/89] Only the server can flip users. --- network/netplay/netplay_frontend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 73754c8672..fd9e0c00c4 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -969,6 +969,8 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) goto done; case RARCH_NETPLAY_CTL_FLIP_PLAYERS: { + if (!netplay_data->is_server) + return false; bool *state = (bool*)data; if (*state) netplay_flip_users(netplay_data); From da7efcb93981ee529f8d1b56839b581791ef53cb Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 08:42:03 -0500 Subject: [PATCH 61/89] Cleaning up netplay headers. --- network/netplay/netplay_buf.c | 55 +++- network/netplay/netplay_delta.c | 17 +- network/netplay/netplay_discovery.c | 6 + network/netplay/netplay_frontend.c | 142 +++------ network/netplay/netplay_handshake.c | 44 ++- network/netplay/netplay_init.c | 139 +++++---- network/netplay/netplay_io.c | 91 +++++- network/netplay/netplay_private.h | 449 ++++++++++++++++++++-------- network/netplay/netplay_sync.c | 13 +- 9 files changed, 646 insertions(+), 310 deletions(-) diff --git a/network/netplay/netplay_buf.c b/network/netplay/netplay_buf.c index 8a69930bcc..6e18939eee 100644 --- a/network/netplay/netplay_buf.c +++ b/network/netplay/netplay_buf.c @@ -13,6 +13,8 @@ * If not, see . */ +#include + #include #include @@ -47,6 +49,11 @@ static size_t buf_remaining(struct socket_buffer *sbuf) return sbuf->bufsz - buf_used(sbuf) - 1; } +/** + * netplay_init_socket_buffer + * + * Initialize a new socket buffer. + */ bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size) { sbuf->data = malloc(size); @@ -57,6 +64,11 @@ bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size) return true; } +/** + * netplay_resize_socket_buffer + * + * Resize the given socket_buffer's buffer to the requested size. + */ bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize) { unsigned char *newdata = malloc(newsize); @@ -91,6 +103,11 @@ bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize) return true; } +/** + * netplay_deinit_socket_buffer + * + * Free a socket buffer. + */ void netplay_deinit_socket_buffer(struct socket_buffer *sbuf) { if (sbuf->data) @@ -102,7 +119,13 @@ void netplay_clear_socket_buffer(struct socket_buffer *sbuf) sbuf->start = sbuf->read = sbuf->end = 0; } -bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf, size_t len) +/** + * 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) { @@ -143,6 +166,14 @@ bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf, size_ return netplay_send_flush(sbuf, sockfd, false); } +/** + * 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; @@ -205,7 +236,15 @@ bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block) return true; } -ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t len, bool block) +/** + * 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) { bool error; ssize_t recvd; @@ -295,11 +334,23 @@ ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t l 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; diff --git a/network/netplay/netplay_delta.c b/network/netplay/netplay_delta.c index b5c49760e8..894478107b 100644 --- a/network/netplay/netplay_delta.c +++ b/network/netplay/netplay_delta.c @@ -23,7 +23,17 @@ #include "netplay_private.h" -bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) +/** + * 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) { void *remember_state; if (delta->used) @@ -43,6 +53,11 @@ bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, ui return true; } +/** + * netplay_delta_frame_crc + * + * Get the CRC for the serialization of this frame. + */ uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) { if (!netplay->state_size) diff --git a/network/netplay/netplay_discovery.c b/network/netplay/netplay_discovery.c index b360db50b4..34780a6c17 100644 --- a/network/netplay/netplay_discovery.c +++ b/network/netplay/netplay_discovery.c @@ -32,6 +32,7 @@ */ #include +#include #include #include @@ -191,6 +192,11 @@ error: 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) { fd_set fds; diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index fd9e0c00c4..e33771334f 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -28,10 +28,6 @@ #include "../../input/input_driver.h" #include "../../runloop.h" -#ifndef HAVE_SOCKET_LEGACY -static void announce_nat_traversal(netplay_t *netplay); -#endif - /* Only used before init_netplay */ static bool netplay_enabled = false; static bool netplay_is_client = false; @@ -286,13 +282,18 @@ static bool netplay_poll(void) return true; } -/* Netplay polling callbacks */ +/** + * input_poll_net + * + * Poll the network if necessary. + */ void input_poll_net(void) { if (!netplay_should_skip(netplay_data) && netplay_can_poll(netplay_data)) netplay_poll(); } +/* Netplay polling callbacks */ void video_frame_net(const void *data, unsigned width, unsigned height, size_t pitch) { @@ -436,6 +437,42 @@ static void netplay_flip_users(netplay_t *netplay) netplay->flip_frame = flip_frame; } +/** + * netplay_frontend_paused + * @netplay : pointer to netplay object + * @paused : true if frontend is paused + * + * Inform Netplay of the frontend's pause state (paused or otherwise) + */ +static void netplay_frontend_paused(netplay_t *netplay, bool paused) +{ + size_t i; + + /* Nothing to do if we already knew this */ + if (netplay->local_paused == paused) + return; + + netplay->local_paused = paused; + + /* If other connections are paused, nothing to say */ + if (netplay->remote_paused) + return; + + /* Have to send manually because every buffer must be flushed immediately */ + 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_raw_cmd(netplay, connection, + paused ? NETPLAY_CMD_PAUSE : 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_pre_frame: * @netplay : pointer to netplay object @@ -478,7 +515,7 @@ bool netplay_pre_frame(netplay_t *netplay) #ifndef HAVE_SOCKET_LEGACY if (!netplay->nat_traversal_state.request_outstanding || netplay->nat_traversal_state.have_inet4) - announce_nat_traversal(netplay); + netplay_announce_nat_traversal(netplay); #endif } } @@ -515,42 +552,6 @@ void netplay_post_frame(netplay_t *netplay) } } -/** - * netplay_frontend_paused - * @netplay : pointer to netplay object - * @paused : true if frontend is paused - * - * Inform Netplay of the frontend's pause state (paused or otherwise) - **/ -void netplay_frontend_paused(netplay_t *netplay, bool paused) -{ - size_t i; - - /* Nothing to do if we already knew this */ - if (netplay->local_paused == paused) - return; - - netplay->local_paused = paused; - - /* If other connections are paused, nothing to say */ - if (netplay->remote_paused) - return; - - /* Have to send manually because every buffer must be flushed immediately */ - 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_raw_cmd(netplay, connection, - paused ? NETPLAY_CMD_PAUSE : 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_load_savestate * @netplay : pointer to netplay object @@ -669,63 +670,6 @@ void netplay_load_savestate(netplay_t *netplay, } } -/** - * netplay_init_nat_traversal - * - * Initialize the NAT traversal library and try to open a port - */ -void netplay_init_nat_traversal(netplay_t *netplay) -{ - natt_init(); - - if (!natt_new(&netplay->nat_traversal_state)) - { - netplay->nat_traversal = false; - return; - } - - natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP); - -#ifndef HAVE_SOCKET_LEGACY - if (!netplay->nat_traversal_state.request_outstanding) - announce_nat_traversal(netplay); -#endif -} - -#ifndef HAVE_SOCKET_LEGACY -static void announce_nat_traversal(netplay_t *netplay) -{ - char msg[512], host[PATH_MAX_LENGTH], port[6]; - - if (netplay->nat_traversal_state.have_inet4) - { - if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, - sizeof(struct sockaddr_in), - host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) - return; - - } -#ifdef HAVE_INET6 - else if (netplay->nat_traversal_state.have_inet6) - { - if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, - sizeof(struct sockaddr_in6), - host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) - return; - - } -#endif - else - return; - - snprintf(msg, sizeof(msg), "%s: %s:%s\n", - msg_hash_to_str(MSG_PUBLIC_ADDRESS), - host, port); - runloop_msg_queue_push(msg, 1, 180, false); - RARCH_LOG("%s\n", msg); -} -#endif - /** * netplay_toggle_play_spectate * diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 90e6cc7996..e8856810f6 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -229,7 +229,13 @@ static uint32_t simple_rand_uint32() parts[2]); } -bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection) +/** + * 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 *content_crc_ptr = NULL; uint32_t header[5] = {0}; @@ -307,7 +313,14 @@ static void handshake_password(void *ignore, const char *line) rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL); } -bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) +/** + * 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) { uint32_t header[5] = {0}; ssize_t recvd; @@ -509,7 +522,14 @@ bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *conne return true; } -bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) +/** + * netplay_handshake_pre_nick + * + * Data receiver for the second stage of handshake, receiving the other side's + * nickname. + */ +bool netplay_handshake_pre_nick(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) { struct nick_buf_s nick_buf; ssize_t recvd; @@ -567,7 +587,14 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *c return true; } -bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) +/** + * netplay_handshake_pre_password + * + * Data receiver for the third, optional stage of server handshake, receiving + * the password. + */ +bool netplay_handshake_pre_password(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) { struct password_buf_s password_buf, corr_password_buf; char password[8+NETPLAY_PASS_LEN]; /* 8 for salt */ @@ -630,7 +657,14 @@ bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connectio return true; } -bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) +/** + * netplay_handshake_pre_sync + * + * Data receiver for the client's third handshake stage, receiving the + * synchronization information. + */ +bool netplay_handshake_pre_sync(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) { uint32_t cmd[2]; uint32_t new_frame_count, connected_players, flip_frame; diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index dcbd2e71cb..579f1cb09d 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -264,62 +264,6 @@ static bool netplay_init_socket_buffers(netplay_t *netplay) 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; - size_t packet_buffer_size; - - 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->self_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); -} - -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; -} - bool netplay_init_serialization(netplay_t *netplay) { unsigned i; @@ -358,6 +302,70 @@ bool netplay_init_serialization(netplay_t *netplay) 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; + size_t packet_buffer_size; + + 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->self_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, unsigned frames) { size_t packet_buffer_size; @@ -397,15 +405,16 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * @nick : Nickname of user. * @quirks : Netplay quirks required for this session. * - * Creates a new netplay handle. A NULL host means we're - * hosting (user 1). + * Creates a new netplay handle. A NULL server means we're + * hosting. * - * Returns: new netplay handle. - **/ + * Returns: new netplay data. + */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, const char *play_password, const char *spectate_password, - unsigned delay_frames, unsigned check_frames, const struct retro_callbacks - *cb, bool nat_traversal, const char *nick, uint64_t quirks) + unsigned delay_frames, unsigned 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) @@ -486,11 +495,11 @@ error: } /** - * netplay_free: + * netplay_free * @netplay : pointer to netplay object * - * Frees netplay handle. - **/ + * Frees netplay data/ + */ void netplay_free(netplay_t *netplay) { size_t i; diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index ab603ed386..b1e15c1c15 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -54,7 +54,7 @@ static void remote_unpaused(netplay_t *netplay, struct netplay_connection *conne * netplay_hangup: * * Disconnects an active Netplay connection due to an error - **/ + */ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) { if (!netplay) @@ -145,7 +145,13 @@ static bool send_input_frame(netplay_t *netplay, return true; } -/* Send the current input frame */ +/** + * 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) { @@ -203,9 +209,9 @@ bool netplay_send_cur_input(netplay_t *netplay, /** * netplay_send_raw_cmd * - * Send a raw Netplay command to the given connection + * Send a raw Netplay command to the given connection. * - * Returns true on success, false on failure + * 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, @@ -258,6 +264,11 @@ static bool netplay_cmd_nak(netplay_t *netplay, return false; } +/** + * netplay_cmd_crc + * + * Send a CRC command to all active clients. + */ bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) { uint32_t payload[2]; @@ -275,6 +286,11 @@ bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) return success; } +/** + * netplay_cmd_request_savestate + * + * Send a savestate request command. + */ bool netplay_cmd_request_savestate(netplay_t *netplay) { if (netplay->connections_size == 0 || @@ -288,6 +304,11 @@ bool netplay_cmd_request_savestate(netplay_t *netplay) NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); } +/** + * netplay_cmd_mode + * + * Send a mode request command to either play or spectate. + */ bool netplay_cmd_mode(netplay_t *netplay, struct netplay_connection *connection, enum rarch_netplay_connection_mode mode) @@ -1079,3 +1100,65 @@ bool netplay_flip_port(netplay_t *netplay) return netplay->flip ^ (frame < netplay->flip_frame); } + +/** + * netplay_announce_nat_traversal + * + * Announce successful NAT traversal. + */ +void netplay_announce_nat_traversal(netplay_t *netplay) +{ +#ifndef HAVE_SOCKET_LEGACY + char msg[512], host[PATH_MAX_LENGTH], port[6]; + + if (netplay->nat_traversal_state.have_inet4) + { + if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, + sizeof(struct sockaddr_in), + host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) + return; + + } +#ifdef HAVE_INET6 + else if (netplay->nat_traversal_state.have_inet6) + { + if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, + sizeof(struct sockaddr_in6), + host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) + return; + + } +#endif + else + return; + + snprintf(msg, sizeof(msg), "%s: %s:%s\n", + msg_hash_to_str(MSG_PUBLIC_ADDRESS), + host, port); + runloop_msg_queue_push(msg, 1, 180, false); + RARCH_LOG("%s\n", msg); +#endif +} + +/** + * netplay_init_nat_traversal + * + * Initialize the NAT traversal library and try to open a port + */ +void netplay_init_nat_traversal(netplay_t *netplay) +{ + natt_init(); + + if (!natt_new(&netplay->nat_traversal_state)) + { + netplay->nat_traversal = false; + return; + } + + natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP); + +#ifndef HAVE_SOCKET_LEGACY + if (!netplay->nat_traversal_state.request_outstanding) + netplay_announce_nat_traversal(netplay); +#endif +} diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 81c4a2fb05..25b7cae79d 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -24,16 +24,10 @@ #include #include #include -#include -#include "../../core.h" #include "../../msg_hash.h" #include "../../verbosity.h" -#ifdef ANDROID -#define HAVE_IPV6 -#endif - #define WORDS_PER_INPUT 3 /* Buttons, left stick, right stick */ #define WORDS_PER_FRAME (WORDS_PER_INPUT+2) /* + frameno, playerno */ @@ -419,8 +413,209 @@ struct netplay uint32_t check_frames; }; + +/*************************************************************** + * NETPLAY-BUF.C + **************************************************************/ + +/** + * netplay_init_socket_buffer + * + * Initialize a new socket buffer. + */ +bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size); + +/** + * netplay_resize_socket_buffer + * + * Resize the given socket_buffer's buffer to the requested size. + */ +bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize); + +/** + * netplay_deinit_socket_buffer + * + * Free a socket buffer. + */ +void netplay_deinit_socket_buffer(struct socket_buffer *sbuf); + +/** + * netplay_send + * + * Queue the given data for sending. + */ +bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf, + size_t len); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + + +/*************************************************************** + * NETPLAY-DELTA.C + **************************************************************/ + +/** + * 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); + +/** + * netplay_delta_frame_crc + * + * Get the CRC for the serialization of this frame. + */ +uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta); + + +/*************************************************************** + * NETPLAY-DISCOVERY.C + **************************************************************/ + +/** + * netplay_lan_ad_server + * + * Respond to any LAN ad queries that the netplay server has received. + */ +bool netplay_lan_ad_server(netplay_t *netplay); + + +/*************************************************************** + * NETPLAY-FRONTEND.C + **************************************************************/ + +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded, NULL means + * "load it yourself" + * @save : Whether to save the provided serial_info + * into the frame buffer + * + * Inform Netplay of a savestate load and send it to the other side + **/ +void netplay_load_savestate(netplay_t *netplay, + retro_ctx_serialize_info_t *serial_info, bool save); + +/** + * input_poll_net + * + * Poll the network if necessary. + */ void input_poll_net(void); +/*************************************************************** + * NETPLAY-HANDSHAKE.C + **************************************************************/ + +/** + * 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); + +/** + * 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); + +/** + * netplay_handshake_pre_nick + * + * Data receiver for the second stage of handshake, receiving the other side's + * nickname. + */ +bool netplay_handshake_pre_nick(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input); + +/** + * netplay_handshake_pre_password + * + * Data receiver for the third, optional stage of server handshake, receiving + * the password. + */ +bool netplay_handshake_pre_password(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input); + +/** + * netplay_handshake_pre_sync + * + * Data receiver for the client's third handshake stage, receiving the + * synchronization information. + */ +bool netplay_handshake_pre_sync(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input); + + +/*************************************************************** + * NETPLAY-INIT.C + **************************************************************/ + +/** + * 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); + +/** + * 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); + /** * netplay_new: * @direct_host : Netplay host discovered from scanning. @@ -435,160 +630,158 @@ void input_poll_net(void); * @nick : Nickname of user. * @quirks : Netplay quirks required for this session. * - * Creates a new netplay handle. A NULL host means we're - * hosting (user 1). + * Creates a new netplay handle. A NULL server means we're + * hosting. * - * Returns: new netplay handle. - **/ + * Returns: new netplay data. + */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, const char *play_password, const char *spectate_password, - unsigned delay_frames, unsigned check_frames, const struct retro_callbacks - *cb, bool nat_traversal, const char *nick, uint64_t quirks); + unsigned delay_frames, unsigned check_frames, + const struct retro_callbacks *cb, bool nat_traversal, const char *nick, + uint64_t quirks); /** - * netplay_free: + * netplay_free * @netplay : pointer to netplay object * - * Frees netplay handle. - **/ -void netplay_free(netplay_t *handle); + * Frees netplay data/ + */ +void netplay_free(netplay_t *netplay); + + +/*************************************************************** + * NETPLAY-IO.C + **************************************************************/ /** - * netplay_pre_frame: - * @netplay : pointer to netplay object + * netplay_hangup: * - * Pre-frame for Netplay. - * Call this before running retro_run(). - * - * Returns: true (1) if the frontend is clear to emulate the frame, false (0) - * if we're stalled or paused - **/ -bool netplay_pre_frame(netplay_t *handle); + * Disconnects an active Netplay connection due to an error + */ +void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection); /** - * netplay_post_frame: - * @netplay : pointer to netplay object + * netplay_send_cur_input * - * Post-frame for Netplay. - * We check if we have new input and replay from recorded input. - * Call this after running retro_run(). - **/ -void netplay_post_frame(netplay_t *handle); - -/** - * netplay_frontend_paused - * @netplay : pointer to netplay object - * @paused : true if frontend is paused + * Send the current input frame to a given connection. * - * Inform Netplay of the frontend's pause state (paused or otherwise) - **/ -void netplay_frontend_paused(netplay_t *netplay, bool paused); - -/** - * netplay_load_savestate - * @netplay : pointer to netplay object - * @serial_info : the savestate being loaded, NULL means "load it yourself" - * @save : whether to save the provided serial_info into the frame buffer - * - * Inform Netplay of a savestate load and send it to the other side - **/ -void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save); - -/** - * netplay_disconnect - * @netplay : pointer to netplay object - * - * Disconnect netplay. - * - * Returns: true (1) if successful. At present, cannot fail. - **/ -bool netplay_disconnect(netplay_t *netplay); - -bool netplay_sync_pre_frame(netplay_t *netplay); - -void netplay_sync_post_frame(netplay_t *netplay); - -/* Normally called at init time, unless the INITIALIZATION quirk is set */ -bool netplay_init_serialization(netplay_t *netplay); - -/* Force serialization to be ready by fast-forwarding the core */ -bool netplay_wait_and_init_serialization(netplay_t *netplay); - -void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim); - -void netplay_log_connection(const struct sockaddr_storage *their_addr, - unsigned slot, const char *nick); - -bool netplay_get_nickname(netplay_t *netplay, int fd); - -bool netplay_send_nickname(netplay_t *netplay, int fd); - -/* Various netplay initialization modes: */ -bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection); -bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); -bool netplay_handshake_pre_nick(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); -bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); -bool netplay_handshake_pre_sync(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); - -uint32_t netplay_impl_magic(void); - -bool netplay_is_server(netplay_t* netplay); - -bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame); - -uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta); - -bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta); - -bool netplay_cmd_request_savestate(netplay_t *netplay); - -bool netplay_cmd_mode(netplay_t *netplay, - struct netplay_connection *connection, - enum rarch_netplay_connection_mode mode); - + * Returns true if successful, false otherwise. + */ bool netplay_send_cur_input(netplay_t *netplay, struct netplay_connection *connection); -int netplay_poll_net_input(netplay_t *netplay, bool block); - -void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection); - -void netplay_update_unread_ptr(netplay_t *netplay); - -bool netplay_flip_port(netplay_t *netplay); - +/** + * 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); +/** + * 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); -bool netplay_try_init_serialization(netplay_t *netplay); +/** + * netplay_cmd_crc + * + * Send a CRC command to all active clients. + */ +bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta); +/** + * netplay_cmd_request_savestate + * + * Send a savestate request command. + */ +bool netplay_cmd_request_savestate(netplay_t *netplay); + +/** + * netplay_cmd_mode + * + * Send a mode request command to either play or spectate. + */ +bool netplay_cmd_mode(netplay_t *netplay, + struct netplay_connection *connection, + enum rarch_netplay_connection_mode mode); + +/** + * netplay_poll_net_input + * + * Poll input from the network + */ +int netplay_poll_net_input(netplay_t *netplay, bool block); + +/** + * netplay_flip_port + * + * Should we flip ports 0 and 1? + */ +bool netplay_flip_port(netplay_t *netplay); + +/** + * netplay_announce_nat_traversal + * + * Announce successful NAT traversal. + */ +void netplay_announce_nat_traversal(netplay_t *netplay); + +/** + * netplay_init_nat_traversal + * + * Initialize the NAT traversal library and try to open a port + */ void netplay_init_nat_traversal(netplay_t *netplay); -/* DISCOVERY: */ -bool netplay_lan_ad_server(netplay_t *netplay); +/*************************************************************** + * NETPLAY-SYNC.C + **************************************************************/ -bool netplay_init_socket_buffer(struct socket_buffer *sbuf, size_t size); +/** + * 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); -bool netplay_resize_socket_buffer(struct socket_buffer *sbuf, size_t newsize); +/** + * netplay_simulate_input + * @netplay : pointer to netplay object + * @sim_ptr : frame index for which to simulate 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. + */ +void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim); -void netplay_deinit_socket_buffer(struct socket_buffer *sbuf); +/** + * netplay_sync_pre_frame + * @netplay : pointer to netplay object + * + * Pre-frame for Netplay synchronization. + */ +bool netplay_sync_pre_frame(netplay_t *netplay); -void netplay_clear_socket_buffer(struct socket_buffer *sbuf); - -bool netplay_send(struct socket_buffer *sbuf, int sockfd, const void *buf, size_t len); - -bool netplay_send_flush(struct socket_buffer *sbuf, int sockfd, bool block); - -ssize_t netplay_recv(struct socket_buffer *sbuf, int sockfd, void *buf, size_t len, bool block); - -void netplay_recv_reset(struct socket_buffer *sbuf); - -void netplay_recv_flush(struct socket_buffer *sbuf); +/** + * 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); #endif diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 7635074ca7..f927f24719 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -32,7 +32,8 @@ * 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 */ + * earliest unread frame count of any connected player + */ void netplay_update_unread_ptr(netplay_t *netplay) { if (netplay->is_server && !netplay->connected_players) @@ -70,7 +71,7 @@ void netplay_update_unread_ptr(netplay_t *netplay) } /** - * netplay_simulate_input: + * netplay_simulate_input * @netplay : pointer to netplay object * @sim_ptr : frame index for which to simulate input * @resim : are we resimulating, or simulating this frame for the @@ -162,11 +163,11 @@ static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *de } /** - * netplay_sync_pre_frame: + * 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; @@ -329,12 +330,12 @@ process: } /** - * netplay_sync_post_frame: + * 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) { netplay->self_ptr = NEXT_PTR(netplay->self_ptr); From 70d04ec6a5bb7c98bd6b0ac3c8dd0a64feadbc88 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 10:15:43 -0500 Subject: [PATCH 62/89] Apparently the frontend gets really pissy if it can't check the flip state --- network/netplay/netplay_frontend.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index e33771334f..cb452074fc 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -913,10 +913,8 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) goto done; case RARCH_NETPLAY_CTL_FLIP_PLAYERS: { - if (!netplay_data->is_server) - return false; bool *state = (bool*)data; - if (*state) + if (netplay_data->is_server && *state) netplay_flip_users(netplay_data); } break; From 4e01481b39299673fc132d07696802341b2d753a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 10:21:15 -0500 Subject: [PATCH 63/89] Only allow clients to load state if there's no further synchronization --- network/netplay/netplay_io.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index b1e15c1c15..f114d09d73 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -882,6 +882,11 @@ static bool netplay_get_cmd(netplay_t *netplay, if (connection->mode != NETPLAY_CONNECTION_PLAYING) 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) + return netplay_cmd_nak(netplay, connection); + /* There is a subtlty in whether the load comes before or after the * current frame: * From bade067d9a4a77a10b2f699491d0a407e3ce0469 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 11:04:05 -0500 Subject: [PATCH 64/89] Support for catching up if the netplay peer is ahead of us. --- network/netplay/netplay.h | 1 + network/netplay/netplay_frontend.c | 3 +++ network/netplay/netplay_private.h | 3 +++ network/netplay/netplay_sync.c | 7 +++++++ runloop.c | 5 +++++ 5 files changed, 19 insertions(+) diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 3d87d620da..97db7b6d4f 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -43,6 +43,7 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_IS_DATA_INITED, RARCH_NETPLAY_CTL_PAUSE, RARCH_NETPLAY_CTL_UNPAUSE, + RARCH_NETPLAY_CTL_CATCH_UP, RARCH_NETPLAY_CTL_LOAD_SAVESTATE, RARCH_NETPLAY_CTL_DISCONNECT }; diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index cb452074fc..8c43f59b2a 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -927,6 +927,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) case RARCH_NETPLAY_CTL_UNPAUSE: netplay_frontend_paused(netplay_data, false); break; + case RARCH_NETPLAY_CTL_CATCH_UP: + ret = netplay_data->catch_up; + break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true); break; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 25b7cae79d..0e1c1c666a 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -409,6 +409,9 @@ struct netplay enum rarch_netplay_stall_reason stall; retro_time_t stall_time; + /* Opposite of stalling, should we be catching up? */ + bool catch_up; + /* Frequency with which to check CRCs */ uint32_t check_frames; }; diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index f927f24719..551b61afd7 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -347,6 +347,7 @@ void netplay_sync_post_frame(netplay_t *netplay) { netplay->other_frame_count = netplay->self_frame_count; netplay->other_ptr = netplay->self_ptr; + netplay->catch_up = false; return; } @@ -454,6 +455,12 @@ void netplay_sync_post_frame(netplay_t *netplay) netplay->force_rewind = false; } + /* If we're behind, try to catch up */ + if (netplay->self_frame_count < netplay->unread_frame_count - 2) + netplay->catch_up = true; + else + netplay->catch_up = false; + /* If we're supposed to stall, rewind (we shouldn't get this far if we're * stalled, so this is a last resort) */ if (netplay->stall) diff --git a/runloop.c b/runloop.c index c05f34aa65..028e1f9391 100644 --- a/runloop.c +++ b/runloop.c @@ -1226,6 +1226,11 @@ int runloop_iterate(unsigned *sleep_ms) if (!settings->fastforward_ratio) return 0; +#ifdef HAVE_NETWORKING + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_CATCH_UP, NULL)) + return 0; +#endif + end: current = cpu_features_get_time_usec(); From df2600fbf44152c6b2562e26bd0930e8cf3d744b Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 11:56:47 -0500 Subject: [PATCH 65/89] Added error reporting when a player is not allowed to play. --- network/netplay/README | 8 +++++++ network/netplay/netplay_io.c | 40 ++++++++++++++++++++++++++++++- network/netplay/netplay_private.h | 16 +++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/network/netplay/README b/network/netplay/README index a1b5d52e44..096aaf3ded 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -214,6 +214,14 @@ Description: players the frame number must be later than the last frame of the relevant player's input that has been transmitted. +Command: MODE_REFUSED +Payload: + { + reason: uint32 + } +Description: + Inform a client that its request to change modes has been refused. + Command: CRC Payload: { diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index f114d09d73..9db7631e3f 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -588,7 +588,12 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); if (!connection->can_play) + { + /* Not allowed to play */ + payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t)); break; + } /* Find an available player slot */ for (player = 0; player <= netplay->player_max; player++) @@ -600,7 +605,9 @@ static bool netplay_get_cmd(netplay_t *netplay, } if (player > netplay->player_max) { - /* Sorry, you can't play! */ + /* No slots free! */ + payload[0] = htonl(NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS); + netplay_send_raw_cmd(netplay, connection, NETPLAY_CMD_MODE_REFUSED, payload, sizeof(uint32_t)); break; } @@ -780,6 +787,37 @@ static bool netplay_get_cmd(netplay_t *netplay, #undef NEXT } + case NETPLAY_CMD_MODE_REFUSED: + { + uint32_t reason; + char msg[512]; + + if (cmd_size != sizeof(uint32_t)) + return netplay_cmd_nak(netplay, connection); + + RECV(&reason, sizeof(reason)) + return netplay_cmd_nak(netplay, connection); + reason = ntohl(reason); + + switch (reason) + { + case NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED: + strlcpy(msg, "You do not have permission to play.", sizeof(msg)); + break; + + case NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS: + strlcpy(msg, "There are no free player slots.", sizeof(msg)); + break; + + default: + strlcpy(msg, "Cannot switch to play mode.", sizeof(msg)); + } + + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + break; + } + case NETPLAY_CMD_DISCONNECT: netplay_hangup(netplay, connection); return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 0e1c1c666a..25f16ab8cc 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -121,6 +121,9 @@ enum netplay_cmd /* Report player mode */ NETPLAY_CMD_MODE = 0x0025, + /* Report player mode refused */ + NETPLAY_CMD_MODE_REFUSED = 0x0026, + /* Loading and synchronization */ /* Send the CRC hash of a frame's state */ @@ -160,6 +163,19 @@ enum netplay_cmd #define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<17) #define NETPLAY_CMD_MODE_BIT_YOU (1U<<16) +/* These are the reasons given for mode changes to be rejected */ +enum netplay_cmd_mode_reasons +{ + /* Other/unknown reason */ + NETPLAY_CMD_MODE_REFUSED_REASON_OTHER, + + /* You don't have permission to play */ + NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED, + + /* There are no free player slots */ + NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS +}; + /* These are the configurations sent by NETPLAY_CMD_CFG. */ enum netplay_cmd_cfg { From a6cd8c3aa1f537568e972e1d52e7927b07bea804 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 12:15:02 -0500 Subject: [PATCH 66/89] Error reporting when a password is refused, plus a bugfix so that socket_receive_all_nonblocking actually reports disconnects. --- libretro-common/net/net_socket.c | 9 ++++++++- network/netplay/netplay_handshake.c | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libretro-common/net/net_socket.c b/libretro-common/net/net_socket.c index 44482c1868..6d0cfdf700 100644 --- a/libretro-common/net/net_socket.c +++ b/libretro-common/net/net_socket.c @@ -74,9 +74,16 @@ ssize_t socket_receive_all_nonblocking(int fd, bool *error, const uint8_t *data = (const uint8_t*)data_; ssize_t ret = recv(fd, (char*)data, size, 0); - if (ret >= 0) + if (ret > 0) return ret; + if (ret == 0) + { + /* Socket closed */ + *error = true; + return -1; + } + if (isagain(ret)) return 0; diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index e8856810f6..e2b65d1454 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -677,7 +677,12 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, retro_ctx_memory_info_t mem_info; RECV(cmd, sizeof(cmd)) + { + char *msg = "Incorrect password."; + RARCH_ERR("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); return false; + } /* Only expecting a sync command */ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || From 6658826759c41bf989e0754a9d3072a81a9b077c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 12:43:29 -0500 Subject: [PATCH 67/89] CRC validity checking. Ignore CRCs if they don't work. --- network/netplay/netplay_init.c | 2 ++ network/netplay/netplay_private.h | 6 ++++++ network/netplay/netplay_sync.c | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 579f1cb09d..5494e532a1 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -429,6 +429,8 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->nat_traversal = netplay->is_server ? nat_traversal : false; netplay->delay_frames = delay_frames; 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_PLAYING : diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 25f16ab8cc..f404c3b480 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -430,6 +430,12 @@ struct netplay /* Frequency with which to check CRCs */ uint32_t check_frames; + + /* Have we checked whether CRCs are valid at all? */ + bool crc_validity_checked; + + /* Are they valid? */ + bool crcs_valid; }; diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 551b61afd7..5307651528 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -131,34 +131,37 @@ void netplay_simulate_input(netplay_t *netplay, size_t sim_ptr, bool resim) static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta) { - static bool crcs_valid = true; if (netplay->is_server) { if (netplay->check_frames && - (delta->frame % netplay->check_frames == 0 || delta->frame == 1)) + delta->frame % netplay->check_frames == 0) { delta->crc = netplay_delta_frame_crc(netplay, delta); netplay_cmd_crc(netplay, delta); } } - else if (delta->crc && crcs_valid) + else if (delta->crc && netplay->crcs_valid) { /* We have a remote CRC, so check it */ uint32_t local_crc = netplay_delta_frame_crc(netplay, delta); if (local_crc != delta->crc) { - if (delta->frame == 1) + if (!netplay->crc_validity_checked) { - /* We check frame 1 just to make sure the CRCs make sense at all. - * If we've diverged at frame 1, we assume CRCs are not useful. */ - crcs_valid = false; + /* If the very first check frame is wrong, they probably just don't + * work */ + netplay->crcs_valid = false; } - else if (crcs_valid) + else if (netplay->crcs_valid) { /* Fix this! */ netplay_cmd_request_savestate(netplay); } } + else if (!netplay->crc_validity_checked) + { + netplay->crc_validity_checked = true; + } } } From 6890456ac0eed1a44c104cb80631821afecb1741 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 17:20:04 -0500 Subject: [PATCH 68/89] Updated error messages and fixed some stall-related disconnected-client bugs. --- intl/msg_hash_chs.c | 2 - intl/msg_hash_eo.h | 2 - intl/msg_hash_es.c | 2 - intl/msg_hash_fr.h | 2 - intl/msg_hash_ja.h | 2 - intl/msg_hash_nl.h | 2 - intl/msg_hash_pl.c | 2 - intl/msg_hash_ru.h | 2 - intl/msg_hash_us.h | 54 ++++++++++++++- intl/msg_hash_vn.c | 2 - msg_hash.h | 16 ++++- network/netplay/netplay_frontend.c | 27 +++----- network/netplay/netplay_handshake.c | 27 ++++---- network/netplay/netplay_io.c | 103 +++++++++++++++++++++++++--- 14 files changed, 181 insertions(+), 64 deletions(-) diff --git a/intl/msg_hash_chs.c b/intl/msg_hash_chs.c index d0af2f7528..5afbaa8a9e 100644 --- a/intl/msg_hash_chs.c +++ b/intl/msg_hash_chs.c @@ -3543,8 +3543,6 @@ const char *msg_hash_to_str_chs(enum msg_hash_enums msg) return "Stopping movie record."; case MSG_NETPLAY_FAILED: return "Failed to initialize netplay."; - case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED: - return "Movie playback has started. Cannot start netplay."; case MSG_NO_CONTENT_STARTING_DUMMY_CORE: return "No content, starting dummy core."; case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET: diff --git a/intl/msg_hash_eo.h b/intl/msg_hash_eo.h index 9d02ebdb47..192e07cdef 100644 --- a/intl/msg_hash_eo.h +++ b/intl/msg_hash_eo.h @@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED, "Stopping movie record.") MSG_HASH(MSG_NETPLAY_FAILED, "Failed to initialize netplay.") -MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, - "Movie playback has started. Cannot start netplay.") MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, "No content, starting dummy core.") MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, diff --git a/intl/msg_hash_es.c b/intl/msg_hash_es.c index c1ca78b1ed..24af507256 100644 --- a/intl/msg_hash_es.c +++ b/intl/msg_hash_es.c @@ -1418,8 +1418,6 @@ const char *msg_hash_to_str_es(enum msg_hash_enums msg) return "Deteniendo grabación de vídeo."; case MSG_NETPLAY_FAILED: return "Error al iniciar el juego en red."; - case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED: - return "Se ha iniciado una reproducción. No se puede ejecutar el juego en red."; case MSG_PAUSED: return "En pausa."; case MSG_PROGRAM: diff --git a/intl/msg_hash_fr.h b/intl/msg_hash_fr.h index 5bfa10339b..9b1eb68d15 100644 --- a/intl/msg_hash_fr.h +++ b/intl/msg_hash_fr.h @@ -1928,8 +1928,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED, "Arrêt de l'enregistrement vidéo.") MSG_HASH(MSG_NETPLAY_FAILED, "Échec de l'initialisation du jeu en réseau") -MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, - "Lecture en cours. Impossible d'activer le jeu en réseau.") MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, "No content, starting dummy core.") MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index db08136d9d..14fc3b3dfc 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -1997,8 +1997,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED, "Stopping movie record.") MSG_HASH(MSG_NETPLAY_FAILED, "Failed to initialize netplay.") -MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, - "Movie playback has started. Cannot start netplay.") MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, "No content, starting dummy core.") MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, diff --git a/intl/msg_hash_nl.h b/intl/msg_hash_nl.h index 29b22cf2b7..aec4814f3f 100644 --- a/intl/msg_hash_nl.h +++ b/intl/msg_hash_nl.h @@ -1962,8 +1962,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED, "Stopping movie record.") MSG_HASH(MSG_NETPLAY_FAILED, "Failed to initialize netplay.") -MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, - "Movie playback has started. Cannot start netplay.") MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, "No content, starting dummy core.") MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, diff --git a/intl/msg_hash_pl.c b/intl/msg_hash_pl.c index 6a25406182..dc708fa921 100644 --- a/intl/msg_hash_pl.c +++ b/intl/msg_hash_pl.c @@ -925,8 +925,6 @@ const char *msg_hash_to_str_pl(enum msg_hash_enums msg) return "Zatrzymano nagrywanie filmu."; case MSG_NETPLAY_FAILED: return "Nie udało się zainicjalizować gry sieciowej."; - case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED: - return "Odtwarzanie filmu w toku. Nie można rozpocząć gry sieciowej."; case MSG_PAUSED: return "Wstrzymano."; case MSG_PROGRAM: diff --git a/intl/msg_hash_ru.h b/intl/msg_hash_ru.h index 7d67b620a8..e181db67e1 100644 --- a/intl/msg_hash_ru.h +++ b/intl/msg_hash_ru.h @@ -1961,8 +1961,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED, "Запись остановлена.") MSG_HASH(MSG_NETPLAY_FAILED, "Ошибка запуска сетевой игры.") -MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, - "Воспроизведение записи. Невозможно начать сетевую игру.") MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, "No content, starting dummy core.") MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index af1e9544aa..2b263696ed 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -46,6 +46,58 @@ MSG_HASH( MSG_WAITING_FOR_CLIENT, "Waiting for client ..." ) +MSG_HASH( + MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME, + "You have left the game." + ) +MSG_HASH( + MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N, + "You have joined as player %d." + ) +MSG_HASH( + MSG_NETPLAY_IMPLEMENTATIONS_DIFFER, + "Implementations differ. Make sure you're using the exact same versions of RetroArch and the core." + ) +MSG_HASH( + MSG_NETPLAY_ENDIAN_DEPENDENT, + "This core does not support inter-architecture netplay between these systems." + ) +MSG_HASH( + MSG_NETPLAY_PLATFORM_DEPENDENT, + "This core does not support inter-architecture netplay." + ) +MSG_HASH( + MSG_NETPLAY_ENTER_PASSWORD, + "Enter netplay server password:" + ) +MSG_HASH( + MSG_NETPLAY_INCORRECT_PASSWORD, + "Incorrect password." + ) +MSG_HASH( + MSG_NETPLAY_SERVER_NAMED_HANGUP, + "\"%s\" has disconnected." + ) +MSG_HASH( + MSG_NETPLAY_SERVER_HANGUP, + "A netplay client has disconnected." + ) +MSG_HASH( + MSG_NETPLAY_CLIENT_HANGUP, + "Netplay disconnected." + ) +MSG_HASH( + MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED, + "You do not have permission to play." + ) +MSG_HASH( + MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS, + "There are no free player slots." + ) +MSG_HASH( + MSG_NETPLAY_CANNOT_PLAY, + "Cannot switch to play mode." + ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT, "Give hardware-rendered cores their own private context. Avoids having to assume hardware state changes inbetween frames." @@ -1992,8 +2044,6 @@ MSG_HASH(MSG_MOVIE_RECORD_STOPPED, "Stopping movie record.") MSG_HASH(MSG_NETPLAY_FAILED, "Failed to initialize netplay.") -MSG_HASH(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, - "Movie playback has started. Cannot start netplay.") MSG_HASH(MSG_NO_CONTENT_STARTING_DUMMY_CORE, "No content, starting dummy core.") MSG_HASH(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, diff --git a/intl/msg_hash_vn.c b/intl/msg_hash_vn.c index bf101e0281..410367850c 100644 --- a/intl/msg_hash_vn.c +++ b/intl/msg_hash_vn.c @@ -3591,8 +3591,6 @@ const char *msg_hash_to_str_vn(enum msg_hash_enums msg) return "Stopping movie record."; case MSG_NETPLAY_FAILED: return "Failed to initialize netplay."; - case MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED: - return "Movie playback has started. Cannot start netplay."; case MSG_NO_CONTENT_STARTING_DUMMY_CORE: return "No content, starting dummy core."; case MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET: diff --git a/msg_hash.h b/msg_hash.h index 7793fd026c..ab8d972ae9 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -140,12 +140,26 @@ enum msg_hash_enums MSG_UNKNOWN = 0, MSG_SETTING_DISK_IN_TRAY, MSG_FAILED_TO_SET_DISK, + MSG_NETPLAY_FAILED, MSG_NETPLAY_USERS_HAS_FLIPPED, MSG_UNKNOWN_NETPLAY_COMMAND_RECEIVED, MSG_CONNECTING_TO_NETPLAY_HOST, MSG_NETPLAY_LAN_SCAN_COMPLETE, MSG_NETPLAY_LAN_SCANNING, MSG_WAITING_FOR_CLIENT, + MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME, + MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N, + MSG_NETPLAY_IMPLEMENTATIONS_DIFFER, + MSG_NETPLAY_ENDIAN_DEPENDENT, + MSG_NETPLAY_PLATFORM_DEPENDENT, + MSG_NETPLAY_ENTER_PASSWORD, + MSG_NETPLAY_INCORRECT_PASSWORD, + MSG_NETPLAY_SERVER_NAMED_HANGUP, + MSG_NETPLAY_SERVER_HANGUP, + MSG_NETPLAY_CLIENT_HANGUP, + MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED, + MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS, + MSG_NETPLAY_CANNOT_PLAY, MSG_AUTODETECT, MSG_AUDIO_VOLUME, MSG_LIBRETRO_FRONTEND, @@ -273,8 +287,6 @@ enum msg_hash_enums MSG_REWIND_INIT_FAILED, MSG_REWIND_INIT_FAILED_THREADED_AUDIO, MSG_LIBRETRO_ABI_BREAK, - MSG_NETPLAY_FAILED, - MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED, MSG_DETECTED_VIEWPORT_OF, MSG_RECORDING_TO, MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING, diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 8c43f59b2a..ab75f96735 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -172,6 +172,10 @@ static bool netplay_poll(void) get_self_input_state(netplay_data); + /* If we're not connected, we're done */ + if (netplay_data->self_mode == NETPLAY_CONNECTION_NONE) + return true; + /* Read Netplay input, block if we're configured to stall for input every * frame */ if (netplay_data->delay_frames == 0 && @@ -682,6 +686,7 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) /* FIXME: Duplication */ uint32_t payload[2]; char msg[512]; + const char *dmsg; payload[0] = htonl(netplay->self_frame_count); if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) { @@ -689,9 +694,7 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) payload[1] = htonl(netplay->self_player); netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; - strlcpy(msg, "You have left the game", sizeof(msg)); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); + dmsg = msg_hash_to_str(MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME); } else if (netplay->self_mode == NETPLAY_CONNECTION_SPECTATING) @@ -707,12 +710,14 @@ static void netplay_toggle_play_spectate(netplay_t *netplay) netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay->self_player = player; + dmsg = msg; msg[sizeof(msg)-1] = '\0'; - snprintf(msg, sizeof(msg)-1, "You have joined as player %d", player+1); - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); + snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N), player+1); } + RARCH_LOG("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false); + netplay_send_raw_cmd_all(netplay, NULL, NETPLAY_CMD_MODE, payload, sizeof(payload)); } @@ -788,16 +793,6 @@ bool init_netplay(void *direct_host, const char *server, unsigned port, if (!netplay_enabled) return false; -#if 0 - /* FIXME: This may still be relevant? */ - if (bsv_movie_ctl(BSV_MOVIE_CTL_START_PLAYBACK, NULL)) - { - RARCH_WARN("%s\n", - msg_hash_to_str(MSG_NETPLAY_FAILED_MOVIE_PLAYBACK_HAS_STARTED)); - return false; - } -#endif - core_set_default_callbacks(&cbs); /* Map the core's quirks to our quirks */ diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index e2b65d1454..337dc033bf 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -324,31 +324,30 @@ bool netplay_handshake_init(netplay_t *netplay, { uint32_t header[5] = {0}; ssize_t recvd; - char msg[512]; + const char *dmsg; struct nick_buf_s nick_buf; uint32_t *content_crc_ptr = NULL; uint32_t local_pmagic, remote_pmagic; uint32_t compression; - msg[0] = '\0'; + dmsg = NULL; RECV(header, sizeof(header)) { - strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT), sizeof(msg)); + dmsg = msg_hash_to_str(MSG_FAILED_TO_RECEIVE_HEADER_FROM_CLIENT); goto error; } if (netplay_impl_magic() != ntohl(header[0])) { - strlcpy(msg, "Implementations differ. Make sure you're using exact same " - "libretro implementations and RetroArch version.", sizeof(msg)); + dmsg = msg_hash_to_str(MSG_NETPLAY_IMPLEMENTATIONS_DIFFER); goto error; } content_get_crc(&content_crc_ptr); if (*content_crc_ptr != ntohl(header[1])) { - strlcpy(msg, msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER), sizeof(msg)); + dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); goto error; } @@ -359,16 +358,14 @@ bool netplay_handshake_init(netplay_t *netplay, netplay_endian_mismatch(local_pmagic, remote_pmagic)) { RARCH_ERR("Endianness mismatch with an endian-sensitive core.\n"); - strlcpy(msg, "This core does not support inter-architecture netplay " - "between these systems.", sizeof(msg)); + dmsg = msg_hash_to_str(MSG_NETPLAY_ENDIAN_DEPENDENT); goto error; } if ((netplay->quirks & NETPLAY_QUIRK_PLATFORM_DEPENDENT) && (local_pmagic != remote_pmagic)) { RARCH_ERR("Platform mismatch with a platform-sensitive core.\n"); - strlcpy(msg, "This core does not support inter-architecture netplay.", - sizeof(msg)); + dmsg = msg_hash_to_str(MSG_NETPLAY_PLATFORM_DEPENDENT); goto error; } @@ -409,7 +406,7 @@ bool netplay_handshake_init(netplay_t *netplay, rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL); memset(&line, 0, sizeof(line)); handshake_password_netplay = netplay; - line.label = "Enter Netplay server password:"; + line.label = msg_hash_to_str(MSG_NETPLAY_ENTER_PASSWORD); line.label_setting = "no_setting"; line.cb = handshake_password; menu_input_dialog_start(&line); @@ -432,10 +429,10 @@ bool netplay_handshake_init(netplay_t *netplay, return true; error: - if (msg[0]) + if (dmsg) { - RARCH_ERR("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); + RARCH_ERR("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false); } return false; } @@ -678,7 +675,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, RECV(cmd, sizeof(cmd)) { - char *msg = "Incorrect password."; + const char *msg = msg_hash_to_str(MSG_NETPLAY_INCORRECT_PASSWORD); RARCH_ERR("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false); return false; diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 9db7631e3f..781f2f5b13 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -57,13 +57,31 @@ static void remote_unpaused(netplay_t *netplay, struct netplay_connection *conne */ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) { + char msg[512]; + const char *dmsg; + if (!netplay) return; if (!connection->active) return; - RARCH_WARN("Netplay has disconnected. Will continue without connection ...\n"); - runloop_msg_queue_push("Netplay has disconnected. Will continue without connection.", 0, 480, false); + msg[0] = msg[sizeof(msg)-1] = '\0'; + dmsg = msg; + + /* Report this disconnection */ + if (netplay->is_server) + { + if (connection->nick[0]) + snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_SERVER_NAMED_HANGUP), connection->nick); + else + dmsg = msg_hash_to_str(MSG_NETPLAY_SERVER_HANGUP); + } + else + { + dmsg = msg_hash_to_str(MSG_NETPLAY_CLIENT_HANGUP); + } + RARCH_LOG("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false); socket_close(connection->fd); connection->active = false; @@ -74,6 +92,7 @@ void netplay_hangup(netplay_t *netplay, struct netplay_connection *connection) { netplay->self_mode = NETPLAY_CONNECTION_NONE; netplay->connected_players = 0; + netplay->stall = NETPLAY_STALL_NONE; } else @@ -431,7 +450,10 @@ static bool netplay_get_cmd(netplay_t *netplay, { /* Ignore the claimed player #, must be this client */ if (connection->mode != NETPLAY_CONNECTION_PLAYING) + { + RARCH_ERR("Netplay input from non-participating player.\n"); return netplay_cmd_nak(netplay, connection); + } player = connection->player; } else @@ -440,7 +462,10 @@ static bool netplay_get_cmd(netplay_t *netplay, } if (player >= MAX_USERS || !(netplay->connected_players & (1<read_frame_count[player]) { @@ -450,6 +475,7 @@ static bool netplay_get_cmd(netplay_t *netplay, else if (buffer[0] > netplay->read_frame_count[player]) { /* Out of order = out of luck */ + RARCH_ERR("Netplay input out of order.\n"); return netplay_cmd_nak(netplay, connection); } @@ -458,6 +484,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player])) { /* FIXME: Catastrophe! */ + RARCH_ERR("Netplay input without a ready delta frame!\n"); return netplay_cmd_nak(netplay, connection); } memcpy(dframe->real_input_state[player], buffer + 2, @@ -488,14 +515,23 @@ static bool netplay_get_cmd(netplay_t *netplay, uint32_t frame; if (netplay->is_server) + { + RARCH_ERR("NETPLAY_CMD_NOINPUT from a client.\n"); return netplay_cmd_nak(netplay, connection); + } RECV(&frame, sizeof(frame)) + { + RARCH_ERR("Failed to receive NETPLAY_CMD_NOINPUT payload.\n"); return netplay_cmd_nak(netplay, connection); + } frame = ntohl(frame); 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++; @@ -516,7 +552,10 @@ static bool netplay_get_cmd(netplay_t *netplay, } if (netplay->is_server) + { + RARCH_ERR("NETPLAY_CMD_FLIP_PLAYERS from a client.\n"); return netplay_cmd_nak(netplay, connection); + } flip_frame = ntohl(flip_frame); @@ -546,7 +585,10 @@ static bool netplay_get_cmd(netplay_t *netplay, uint32_t payload[2]; if (!netplay->is_server) + { + RARCH_ERR("NETPLAY_CMD_SPECTATE from a server.\n"); return netplay_cmd_nak(netplay, connection); + } if (connection->mode == NETPLAY_CONNECTION_PLAYING) { @@ -585,7 +627,10 @@ static bool netplay_get_cmd(netplay_t *netplay, payload[0] = htonl(netplay->self_frame_count + 1); if (!netplay->is_server) + { + RARCH_ERR("NETPLAY_CMD_PLAY from a server.\n"); return netplay_cmd_nak(netplay, connection); + } if (!connection->can_play) { @@ -661,7 +706,10 @@ static bool netplay_get_cmd(netplay_t *netplay, if (cmd_size != sizeof(payload) || netplay->is_server) + { + RARCH_ERR("Invalid payload size for NETPLAY_CMD_MODE.\n"); return netplay_cmd_nak(netplay, connection); + } RECV(payload, sizeof(payload)) { @@ -669,9 +717,6 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } - if (netplay->is_server) - return netplay_cmd_nak(netplay, connection); - frame = ntohl(payload[0]); /* We're changing past input, so must replay it */ @@ -681,7 +726,10 @@ static bool netplay_get_cmd(netplay_t *netplay, mode = ntohl(payload[1]); player = mode & 0xFFFF; if (player >= MAX_USERS) + { + RARCH_ERR("Received NETPLAY_CMD_MODE for a higher player number than we support.\n"); return netplay_cmd_nak(netplay, connection); + } if (mode & NETPLAY_CMD_MODE_BIT_YOU) { @@ -689,11 +737,17 @@ static bool netplay_get_cmd(netplay_t *netplay, 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); + } netplay->self_mode = NETPLAY_CONNECTION_PLAYING; netplay->self_player = player; @@ -738,7 +792,10 @@ static bool netplay_get_cmd(netplay_t *netplay, { /* 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); + } /* Announce it */ strlcpy(msg, "You have left the game", sizeof(msg)); @@ -754,7 +811,10 @@ static bool netplay_get_cmd(netplay_t *netplay, 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<is_server) + { + RARCH_ERR("NETPLAY_CMD_MODE_REFUSED from client.\n"); + return netplay_cmd_nak(netplay, connection); + } if (cmd_size != sizeof(uint32_t)) + { + RARCH_ERR("Received invalid payload size for NETPLAY_CMD_MODE_REFUSED.\n"); return netplay_cmd_nak(netplay, connection); + } RECV(&reason, sizeof(reason)) + { + RARCH_ERR("Failed to receive NETPLAY_CMD_MODE_REFUSED payload.\n"); return netplay_cmd_nak(netplay, connection); + } reason = ntohl(reason); switch (reason) { case NETPLAY_CMD_MODE_REFUSED_REASON_UNPRIVILEGED: - strlcpy(msg, "You do not have permission to play.", sizeof(msg)); + dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED); break; case NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS: - strlcpy(msg, "There are no free player slots.", sizeof(msg)); + dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS); break; default: - strlcpy(msg, "Cannot switch to play mode.", sizeof(msg)); + dmsg = msg_hash_to_str(MSG_NETPLAY_CANNOT_PLAY); } - RARCH_LOG("%s\n", msg); - runloop_msg_queue_push(msg, 1, 180, false); + if (dmsg) + { + RARCH_LOG("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false); + } break; } @@ -918,12 +993,18 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Only players may load states */ if (connection->mode != NETPLAY_CONNECTION_PLAYING) + { + 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: From 45d732a014d4611c2cf5f743c281dd3aa7025798 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 22:34:18 -0500 Subject: [PATCH 69/89] New sync system The idea: * Use a fixed number of delay_frames (eventually to be fixed at 120, currently still uses the config variable, 0 will still be an option) * Determine how long it takes to simulate a frame. * Stall only if resimulating the intervening frames would be sufficiently annoying (currently fixed at three frames worth of time) Because clients always try to catch up, the actual frame delay works out automatically to be minimally zero and maximally the latency. If one client is underpowered but the other is fine, the powerful one will automatically take up the slack. Seems like the most reasonable system. --- network/netplay/netplay.h | 1 - network/netplay/netplay_frontend.c | 21 +++++++++++++---- network/netplay/netplay_private.h | 19 +++++++++++++--- network/netplay/netplay_sync.c | 36 ++++++++++++++++++++++++++++-- runloop.c | 5 ----- 5 files changed, 67 insertions(+), 15 deletions(-) diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 97db7b6d4f..3d87d620da 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -43,7 +43,6 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_IS_DATA_INITED, RARCH_NETPLAY_CTL_PAUSE, RARCH_NETPLAY_CTL_UNPAUSE, - RARCH_NETPLAY_CTL_CATCH_UP, RARCH_NETPLAY_CTL_LOAD_SAVESTATE, RARCH_NETPLAY_CTL_DISCONNECT }; diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index ab75f96735..5ab01f89e6 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -216,8 +216,23 @@ static bool netplay_poll(void) break; default: /* not stalling */ + { + retro_time_t max_ahead; + + /* Figure out how many frames we're allowed to be ahead: Ideally we need to be + * able to run our entire stall worth of frames in one real frame. In + * practice, we'll allow a couple jitter frames. (FIXME: hard coded + * as three 60FPS frame) */ + if (netplay_data->frame_run_time_avg) + max_ahead = 50000 / netplay_data->frame_run_time_avg; + else + max_ahead = netplay_data->delay_frames; + if (max_ahead > netplay_data->delay_frames) + max_ahead = netplay_data->delay_frames; + + /* Are we too far ahead? */ netplay_update_unread_ptr(netplay_data); - if (netplay_data->unread_frame_count + netplay_data->delay_frames + if (netplay_data->unread_frame_count + max_ahead <= netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_RUNNING_FAST; @@ -246,6 +261,7 @@ static bool netplay_poll(void) } } + } } /* If we're stalling, consider disconnection */ @@ -922,9 +938,6 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) case RARCH_NETPLAY_CTL_UNPAUSE: netplay_frontend_paused(netplay_data, false); break; - case RARCH_NETPLAY_CTL_CATCH_UP: - ret = netplay_data->catch_up; - break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true); break; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index f404c3b480..aba8cf1179 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -45,6 +45,8 @@ #define MAX_RETRIES 16 #define RETRY_MS 500 +#define NETPLAY_FRAME_RUN_TIME_WINDOW 128 + #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) @@ -415,14 +417,25 @@ struct netplay bool flip; uint32_t flip_frame; - /* Netplay pausing - */ + /* Netplay pausing */ bool local_paused; bool remote_paused; - /* And stalling */ + /* Old-style stalling (to be removed) */ uint32_t delay_frames; + + /* We stall if we're far enough ahead that we couldn't transparently rewind. + * To know if we could transparently rewind, we need to know how long + * running a frame takes. We record that every frame and get a running + * (window) average */ + retro_time_t frame_run_time[NETPLAY_FRAME_RUN_TIME_WINDOW]; + int frame_run_time_ptr; + retro_time_t frame_run_time_sum, frame_run_time_avg; + + /* Are we stalled? */ enum rarch_netplay_stall_reason stall; + + /* How long have we been stalled? */ retro_time_t stall_time; /* Opposite of stalling, should we be catching up? */ diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 5307651528..7601b6abc4 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -23,6 +23,8 @@ #include "netplay_private.h" #include "../../autosave.h" +#include "../../driver.h" +#include "../../input/input_driver.h" #if 0 #define DEBUG_NONDETERMINISTIC_CORES @@ -341,6 +343,8 @@ process: */ void netplay_sync_post_frame(netplay_t *netplay) { + int catch_up_ct; + netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; @@ -407,11 +411,15 @@ void netplay_sync_post_frame(netplay_t *netplay) while (netplay->replay_frame_count < netplay->self_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); @@ -442,8 +450,20 @@ void netplay_sync_post_frame(netplay_t *netplay) RARCH_LOG("POST %u: %X\n", netplay->replay_frame_count-1, netplay_delta_frame_crc(netplay, ptr)); } #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->self_frame_count) { netplay->other_ptr = netplay->unread_ptr; @@ -459,10 +479,22 @@ void netplay_sync_post_frame(netplay_t *netplay) } /* If we're behind, try to catch up */ - if (netplay->self_frame_count < netplay->unread_frame_count - 2) - netplay->catch_up = true; + /* FIXME: Any use in interacting with the real fast forwarding? */ + if (netplay->catch_up) + catch_up_ct = 0; else + catch_up_ct = 2; + if (netplay->self_frame_count < netplay->unread_frame_count - catch_up_ct) + { + netplay->catch_up = true; + input_driver_set_nonblock_state(); + } + else + { netplay->catch_up = false; + input_driver_unset_nonblock_state(); + } + driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL); /* If we're supposed to stall, rewind (we shouldn't get this far if we're * stalled, so this is a last resort) */ diff --git a/runloop.c b/runloop.c index 028e1f9391..c05f34aa65 100644 --- a/runloop.c +++ b/runloop.c @@ -1226,11 +1226,6 @@ int runloop_iterate(unsigned *sleep_ms) if (!settings->fastforward_ratio) return 0; -#ifdef HAVE_NETWORKING - if (netplay_driver_ctl(RARCH_NETPLAY_CTL_CATCH_UP, NULL)) - return 0; -#endif - end: current = cpu_features_get_time_usec(); From f7f6590156d40818991576ff809b7519da2dfff4 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Dec 2016 23:09:55 -0500 Subject: [PATCH 70/89] Goodbye delay_frames! stateless_mode is the new delay_frames=0 --- config.def.h | 3 +++ configuration.c | 6 +++--- configuration.h | 2 +- intl/msg_hash_lbl.h | 4 ++-- intl/msg_hash_us.c | 9 +++++++++ intl/msg_hash_us.h | 2 ++ menu/menu_displaylist.c | 2 +- menu/menu_setting.c | 28 +++++++++++----------------- msg_hash.h | 1 + network/netplay/netplay_frontend.c | 10 +++++----- network/netplay/netplay_init.c | 23 ++++++++++------------- network/netplay/netplay_private.h | 27 ++++++--------------------- network/netplay/netplay_sync.c | 3 ++- retroarch.c | 26 ++++++++++++++------------ retroarch.h | 2 +- 15 files changed, 71 insertions(+), 77 deletions(-) diff --git a/config.def.h b/config.def.h index 12c2773810..78ac7d8191 100644 --- a/config.def.h +++ b/config.def.h @@ -794,6 +794,9 @@ static const bool pause_nonactive = true; * It is measured in seconds. A value of 0 disables autosave. */ static const unsigned autosave_interval = 0; +/* Netplay without savestates/rewind */ +static const bool netplay_stateless_mode = false; + /* When being client over netplay, use keybinds for * user 1 rather than user 2. */ static const bool netplay_client_swap_input = true; diff --git a/configuration.c b/configuration.c index 33d65f2a7c..3b3499cfe3 100644 --- a/configuration.c +++ b/configuration.c @@ -718,6 +718,7 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti SETTING_BOOL("all_users_control_menu", &settings->input.all_users_control_menu, true, all_users_control_menu, false); SETTING_BOOL("menu_swap_ok_cancel_buttons", &settings->input.menu_swap_ok_cancel_buttons, true, menu_swap_ok_cancel_buttons, false); #ifdef HAVE_NETWORKING + SETTING_BOOL("netplay_stateless_mode", &settings->netplay.stateless_mode, false, netplay_stateless_mode, false); SETTING_BOOL("netplay_client_swap_input", &settings->netplay.swap_input, true, netplay_client_swap_input, false); #endif SETTING_BOOL("input_descriptor_label_show", &settings->input.input_descriptor_label_show, true, input_descriptor_label_show, false); @@ -947,7 +948,6 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting SETTING_INT("state_slot", (unsigned*)&settings->state_slot, false, 0 /* TODO */, false); #ifdef HAVE_NETWORKING SETTING_INT("netplay_ip_port", &settings->netplay.port, true, RARCH_DEFAULT_PORT, false); - SETTING_INT("netplay_delay_frames", &settings->netplay.delay_frames, true, netplay_delay_frames, false); SETTING_INT("netplay_check_frames", &settings->netplay.check_frames, true, netplay_check_frames, false); #endif #ifdef HAVE_LANGEXTRA @@ -1856,8 +1856,8 @@ static bool config_load_file(const char *path, bool set_defaults, } #ifdef HAVE_NETWORKING - if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL)) - CONFIG_GET_INT_BASE(conf, settings, netplay.delay_frames, "netplay_delay_frames"); + if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL)) + CONFIG_GET_BOOL_BASE(conf, settings, netplay.stateless_mode, "netplay_stateless_mode"); if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL)) CONFIG_GET_INT_BASE(conf, settings, netplay.check_frames, "netplay_check_frames"); if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, NULL)) diff --git a/configuration.h b/configuration.h index ade5ac6815..4f32cef7ff 100644 --- a/configuration.h +++ b/configuration.h @@ -401,7 +401,7 @@ typedef struct settings { char server[255]; unsigned port; - unsigned delay_frames; + bool stateless_mode; unsigned check_frames; bool swap_input; bool nat_traversal; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 73886da4cc..da4e6a5521 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -571,8 +571,6 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, "netplay_check_frames") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, "netplay_client_swap_input") -MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, - "netplay_delay_frames") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_DISCONNECT, "menu_netplay_disconnect") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_ENABLE, @@ -597,6 +595,8 @@ MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATE_PASSWORD, "netplay_spectate_password") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, "netplay_spectator_mode_enable") +MSG_HASH(MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE, + "netplay_stateless_mode") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, "netplay_tcp_udp_port") MSG_HASH(MENU_ENUM_LABEL_NETPLAY_LAN_SCAN_SETTINGS, diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index cce55e8447..aee30281a6 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1561,6 +1561,15 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) "Increasing this value will increase \n" "performance, but introduce more latency."); break; + case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE: + snprintf(s, len, + "Whether to run netplay in a mode not requiring\n" + "save states. \n" + " \n" + "If set to true, a very fast network is required,\n" + "but no rewinding is performed, so there will be\n" + "no netplay jitter.\n"); + break; case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: snprintf(s, len, "The frequency in frames with which netplay \n" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2b263696ed..e8ff6da8c0 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -976,6 +976,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_PASSWORD, "Server Password") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SETTINGS, "Netplay settings") +MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_STATELESS_MODE, + "Netplay Stateless Mode") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATE_PASSWORD, "Server Spectate-Only Password") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 048f67be9d..f59782c09c 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4698,7 +4698,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) PARSE_ONLY_STRING, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, - MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, + MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE, PARSE_ONLY_UINT, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index d6cd6a53c4..1a8499c44f 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -1721,16 +1721,9 @@ void general_write_handler(void *data) retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL); #endif break; - case MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES: + case MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE: #ifdef HAVE_NETWORKING - { - bool val = (settings->netplay.delay_frames > 0); - - if (val) - retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL); - else - retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL); - } + retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL); #endif break; case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: @@ -5605,19 +5598,20 @@ static bool setting_append_list( general_read_handler); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); - CONFIG_UINT( + CONFIG_BOOL( list, list_info, - &settings->netplay.delay_frames, - MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, - MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES, - netplay_delay_frames, + &settings->netplay.stateless_mode, + MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE, + MENU_ENUM_LABEL_VALUE_NETPLAY_STATELESS_MODE, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, &group_info, &subgroup_info, parent_group, general_write_handler, - general_read_handler); - menu_settings_list_current_add_range(list, list_info, 0, 60, 1, true, false); - settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); + general_read_handler, + SD_FLAG_NONE); CONFIG_UINT( list, list_info, diff --git a/msg_hash.h b/msg_hash.h index ab8d972ae9..1bb53899e1 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1008,6 +1008,7 @@ enum msg_hash_enums MENU_LABEL(BLUETOOTH_ENABLE), MENU_LABEL(NETPLAY_CLIENT_SWAP_INPUT), MENU_LABEL(NETPLAY_DELAY_FRAMES), + MENU_LABEL(NETPLAY_STATELESS_MODE), MENU_LABEL(NETPLAY_CHECK_FRAMES), MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE), MENU_LABEL(NETPLAY_TCP_UDP_PORT), diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 5ab01f89e6..524c5e814f 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -178,7 +178,7 @@ static bool netplay_poll(void) /* Read Netplay input, block if we're configured to stall for input every * frame */ - if (netplay_data->delay_frames == 0 && + if (netplay_data->stateless_mode && netplay_data->unread_frame_count <= netplay_data->self_frame_count) res = netplay_poll_net_input(netplay_data, true); else @@ -226,9 +226,9 @@ static bool netplay_poll(void) if (netplay_data->frame_run_time_avg) max_ahead = 50000 / netplay_data->frame_run_time_avg; else - max_ahead = netplay_data->delay_frames; - if (max_ahead > netplay_data->delay_frames) - max_ahead = netplay_data->delay_frames; + max_ahead = NETPLAY_MAX_STALL_FRAMES; + if (max_ahead > NETPLAY_MAX_STALL_FRAMES) + max_ahead = NETPLAY_MAX_STALL_FRAMES; /* Are we too far ahead? */ netplay_update_unread_ptr(netplay_data); @@ -846,7 +846,7 @@ bool init_netplay(void *direct_host, const char *server, unsigned port, netplay_is_client ? server : NULL, port ? port : RARCH_DEFAULT_PORT, play_password, spectate_password, - settings->netplay.delay_frames, settings->netplay.check_frames, &cbs, + settings->netplay.stateless_mode, settings->netplay.check_frames, &cbs, settings->netplay.nat_traversal, settings->username, quirks); diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 5494e532a1..4c9dd0ce84 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -230,11 +230,11 @@ static bool init_socket(netplay_t *netplay, void *direct_host, const char *serve static bool netplay_init_socket_buffers(netplay_t *netplay) { - /* Make our packet buffer big enough for a save state and frames-many frames - * of input data, plus the headers for each of them */ + /* 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->delay_frames * WORDS_PER_FRAME + (netplay->delay_frames+1)*3; + NETPLAY_MAX_STALL_FRAMES * WORDS_PER_FRAME + (NETPLAY_MAX_STALL_FRAMES+1)*3; netplay->packet_buffer_size = packet_buffer_size; for (i = 0; i < netplay->connections_size; i++) @@ -366,18 +366,15 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay) return false; } -static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) +static bool netplay_init_buffers(netplay_t *netplay) { size_t packet_buffer_size; if (!netplay) return false; - /* * 2 + 1 because: - * Self sits in the middle, - * Other is allowed to drift as much as 'frames' frames behind - * Read is allowed to drift as much as 'frames' frames ahead */ - netplay->buffer_size = frames * 2 + 1; + /* Enough to get ahead or behind by MAX_STALL_FRAMES frames */ + netplay->buffer_size = NETPLAY_MAX_STALL_FRAMES + 1; netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, sizeof(*netplay->buffer)); @@ -398,7 +395,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * @port : Port of server. * @play_password : Password required to play. * @spectate_password : Password required to connect. - * @delay_frames : Amount of delay frames. + * @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. @@ -412,7 +409,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, const char *play_password, const char *spectate_password, - unsigned delay_frames, unsigned check_frames, + bool stateless_mode, unsigned check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks) { @@ -427,7 +424,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, netplay->player_max = 1; netplay->is_server = server == NULL; netplay->nat_traversal = netplay->is_server ? nat_traversal : false; - netplay->delay_frames = delay_frames; + netplay->stateless_mode = stateless_mode; netplay->check_frames = check_frames; netplay->crc_validity_checked = false; netplay->crcs_valid = true; @@ -458,7 +455,7 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, return NULL; } - if (!netplay_init_buffers(netplay, delay_frames)) + if (!netplay_init_buffers(netplay)) { free(netplay); return NULL; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index aba8cf1179..5a183965a9 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -45,7 +45,8 @@ #define MAX_RETRIES 16 #define RETRY_MS 500 -#define NETPLAY_FRAME_RUN_TIME_WINDOW 128 +#define NETPLAY_MAX_STALL_FRAMES 60 +#define NETPLAY_FRAME_RUN_TIME_WINDOW 120 #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) @@ -178,22 +179,6 @@ enum netplay_cmd_mode_reasons NETPLAY_CMD_MODE_REFUSED_REASON_NO_SLOTS }; -/* These are the configurations sent by NETPLAY_CMD_CFG. */ -enum netplay_cmd_cfg -{ - /* Nickname */ - NETPLAY_CFG_NICK = 0x0001, - - /* input.netplay_client_swap_input */ - NETPLAY_CFG_SWAP_INPUT = 0x0002, - - /* netplay.delay_frames */ - NETPLAY_CFG_DELAY_FRAMES = 0x0004, - - /* For more than 2 players */ - NETPLAY_CFG_PLAYER_SLOT = 0x0008 -}; - enum rarch_netplay_connection_mode { NETPLAY_CONNECTION_NONE = 0, @@ -421,8 +406,8 @@ struct netplay bool local_paused; bool remote_paused; - /* Old-style stalling (to be removed) */ - uint32_t delay_frames; + /* If true, never progress without peer input (stateless/rewindless mode) */ + bool stateless_mode; /* We stall if we're far enough ahead that we couldn't transparently rewind. * To know if we could transparently rewind, we need to know how long @@ -661,7 +646,7 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay); * @port : Port of server. * @play_password : Password required to play. * @spectate_password : Password required to connect. - * @delay_frames : Amount of delay frames. + * @stateless_mode : Shall we run in stateless mode? * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. * @nat_traversal : If true, attempt NAT traversal. @@ -675,7 +660,7 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay); */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, const char *play_password, const char *spectate_password, - unsigned delay_frames, unsigned check_frames, + bool stateless_mode, unsigned check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks); diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 7601b6abc4..94420518b1 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -15,6 +15,7 @@ * If not, see . */ +#include #include #include @@ -203,7 +204,7 @@ bool netplay_sync_pre_frame(netplay_t *netplay) /* 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->delay_frames = 0; + netplay->stateless_mode = true; } /* If we can't transmit savestates, we must stall until the client is ready */ diff --git a/retroarch.c b/retroarch.c index 380c5dbdde..b0b10c8236 100644 --- a/retroarch.c +++ b/retroarch.c @@ -104,6 +104,7 @@ enum { RA_OPT_MENU = 256, /* must be outside the range of a char */ + RA_OPT_STATELESS, RA_OPT_CHECK_FRAMES, RA_OPT_PORT, RA_OPT_SPECTATE, @@ -143,7 +144,7 @@ static bool has_set_state_path = false; static bool has_set_netplay_mode = false; static bool has_set_netplay_ip_address = false; static bool has_set_netplay_ip_port = false; -static bool has_set_netplay_delay_frames = false; +static bool has_set_netplay_stateless_mode = false; static bool has_set_netplay_check_frames = false; static bool has_set_ups_pref = false; static bool has_set_bps_pref = false; @@ -339,7 +340,8 @@ static void retroarch_print_help(const char *arg0) puts(" -H, --host Host netplay as user 1."); puts(" -C, --connect=HOST Connect to netplay server as user 2."); puts(" --port=PORT Port used to netplay. Default is 55435."); - puts(" -F, --frames=NUMBER Delay frames when using netplay."); + puts(" --stateless Use \"stateless\" mode for netplay"); + puts(" (requires a very fast network)."); puts(" --check-frames=NUMBER\n" " Check frames when using netplay."); #if defined(HAVE_NETWORK_CMD) @@ -426,7 +428,7 @@ static void retroarch_parse_input(int argc, char *argv[]) #ifdef HAVE_NETWORKING { "host", 0, NULL, 'H' }, { "connect", 1, NULL, 'C' }, - { "frames", 1, NULL, 'F' }, + { "stateless", 0, NULL, RA_OPT_STATELESS }, { "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES }, { "port", 1, NULL, RA_OPT_PORT }, #if defined(HAVE_NETWORK_CMD) @@ -705,10 +707,10 @@ static void retroarch_parse_input(int argc, char *argv[]) sizeof(settings->netplay.server)); break; - case 'F': - settings->netplay.delay_frames = strtol(optarg, NULL, 0); + case RA_OPT_STATELESS: + settings->netplay.stateless_mode = true; retroarch_override_setting_set( - RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, NULL); + RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL); break; case RA_OPT_CHECK_FRAMES: @@ -1361,8 +1363,8 @@ bool retroarch_override_setting_is_set(enum rarch_override_setting enum_idx, voi return has_set_netplay_ip_address; case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT: return has_set_netplay_ip_port; - case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: - return has_set_netplay_delay_frames; + case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE: + return has_set_netplay_stateless_mode; case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: return has_set_netplay_check_frames; case RARCH_OVERRIDE_SETTING_UPS_PREF: @@ -1418,8 +1420,8 @@ void retroarch_override_setting_set(enum rarch_override_setting enum_idx, void * case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT: has_set_netplay_ip_port = true; break; - case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: - has_set_netplay_delay_frames = true; + case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE: + has_set_netplay_stateless_mode = true; break; case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: has_set_netplay_check_frames = true; @@ -1477,8 +1479,8 @@ void retroarch_override_setting_unset(enum rarch_override_setting enum_idx, void case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT: has_set_netplay_ip_port = false; break; - case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: - has_set_netplay_delay_frames = false; + case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE: + has_set_netplay_stateless_mode = false; break; case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: has_set_netplay_check_frames = false; diff --git a/retroarch.h b/retroarch.h index 15799c69fd..32d9a860aa 100644 --- a/retroarch.h +++ b/retroarch.h @@ -129,7 +129,7 @@ enum rarch_override_setting RARCH_OVERRIDE_SETTING_NETPLAY_MODE, RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS, RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, - RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, + RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, RARCH_OVERRIDE_SETTING_UPS_PREF, RARCH_OVERRIDE_SETTING_BPS_PREF, From a20e79bbb5639f08fbec19755e5ef9c6f2596600 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 16 Dec 2016 09:52:53 -0500 Subject: [PATCH 71/89] Server needs double the buffer frames since two connected clients could be ahead and behind by the max frames --- network/netplay/netplay_init.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 4c9dd0ce84..ceb7b7eb01 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -376,6 +376,11 @@ static bool netplay_init_buffers(netplay_t *netplay) /* Enough to get ahead or behind by MAX_STALL_FRAMES frames */ netplay->buffer_size = NETPLAY_MAX_STALL_FRAMES + 1; + /* 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; + netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, sizeof(*netplay->buffer)); From 11418256054cf2641794aa75d34a7c5002520824 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 16 Dec 2016 09:57:54 -0500 Subject: [PATCH 72/89] Documenting (somewhat) the new sync system. --- network/netplay/README | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/network/netplay/README b/network/netplay/README index 096aaf3ded..a02f4823af 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -27,7 +27,7 @@ local and remote players. Self is where the emulator believes itself to be, which may be ahead or behind of what it's read from the peer. Generally speaking, self progresses at 1 frame -per frame, except when the network stalls, described later. +per frame, but both fast-forwarding and stalling occur and are described later. Other is where it was most recently in perfect sync: i.e., other-1 is the last frame from which both local and remote input have been actioned. As such, other @@ -73,6 +73,10 @@ other and self > other. If so, it first checks whether its simulated remote data was correct. If it was, it simply moves other up. If not, it rewinds to other (by loading the serialized state there) and runs the core in replay mode with the real data up to the least of self and read, then sets other to that. +To avoid latency building up, if the input from the network is too far ahead +(i.e., unread is too far ahead of self), the frame limiter is momentarily +disabled to catch up. Note that since network latency is expected, the normal +case is the opposite: unread is behind self. When in Netplay mode, the callback for receiving input is replaced by input_state_net. It is the role of input_state_net to combine the true local From db2c8de44c7e67f0961a6a65a5c12291c5e27410 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 16 Dec 2016 10:49:31 -0500 Subject: [PATCH 73/89] Slightly changing how catch-up works to avoid spamming the console --- network/netplay/netplay_sync.c | 35 +++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 94420518b1..3eb85af91a 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -344,8 +344,6 @@ process: */ void netplay_sync_post_frame(netplay_t *netplay) { - int catch_up_ct; - netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; @@ -355,7 +353,13 @@ void netplay_sync_post_frame(netplay_t *netplay) { netplay->other_frame_count = netplay->self_frame_count; netplay->other_ptr = netplay->self_ptr; - netplay->catch_up = false; + /* FIXME: Duplication */ + if (netplay->catch_up) + { + netplay->catch_up = false; + input_driver_unset_nonblock_state(); + driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL); + } return; } @@ -480,22 +484,27 @@ void netplay_sync_post_frame(netplay_t *netplay) } /* If we're behind, try to catch up */ - /* FIXME: Any use in interacting with the real fast forwarding? */ if (netplay->catch_up) - catch_up_ct = 0; - else - catch_up_ct = 2; - if (netplay->self_frame_count < netplay->unread_frame_count - catch_up_ct) { - netplay->catch_up = true; - input_driver_set_nonblock_state(); + /* Are we caught up? */ + if (netplay->self_frame_count >= netplay->unread_frame_count) + { + netplay->catch_up = false; + input_driver_unset_nonblock_state(); + driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL); + } + } else { - netplay->catch_up = false; - input_driver_unset_nonblock_state(); + /* Are we falling behind? */ + if (netplay->self_frame_count < netplay->unread_frame_count - 2) + { + netplay->catch_up = true; + input_driver_set_nonblock_state(); + driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL); + } } - driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL); /* If we're supposed to stall, rewind (we shouldn't get this far if we're * stalled, so this is a last resort) */ From 1fa60b396f1f0456e36fceb7b9466a5524bc60d5 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 16 Dec 2016 19:54:50 -0500 Subject: [PATCH 74/89] Improvements to the communication of netplay pausing. --- intl/msg_hash_us.h | 4 +++ msg_hash.h | 1 + network/netplay/README | 9 ++++++- network/netplay/netplay_frontend.c | 26 +++++++++++++++---- network/netplay/netplay_io.c | 41 +++++++++++++++++++++++++++--- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index e8ff6da8c0..45391cb972 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -98,6 +98,10 @@ MSG_HASH( MSG_NETPLAY_CANNOT_PLAY, "Cannot switch to play mode." ) +MSG_HASH( + MSG_NETPLAY_PEER_PAUSED, + "Netplay peer \"%s\" paused." + ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT, "Give hardware-rendered cores their own private context. Avoids having to assume hardware state changes inbetween frames." diff --git a/msg_hash.h b/msg_hash.h index 1bb53899e1..4e57829709 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -160,6 +160,7 @@ enum msg_hash_enums MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED, MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS, MSG_NETPLAY_CANNOT_PLAY, + MSG_NETPLAY_PEER_PAUSED, MSG_AUTODETECT, MSG_AUDIO_VOLUME, MSG_LIBRETRO_FRONTEND, diff --git a/network/netplay/README b/network/netplay/README index a02f4823af..ad6932bc3e 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -255,11 +255,18 @@ Description: serialized state is zlib compressed. Otherwise it is uncompressed. Command: PAUSE -Payload: None +Payload: + { + nickname: char[32] + } +Description: Indicates that the core is paused. The receiving peer should also pause. + The server should pass it on, using the known correct name rather than the + provided name. Command: RESUME Payload: None +Description: Indicates that the core is no longer paused. Command: CHEATS diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 524c5e814f..ff68b1daee 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -467,6 +467,7 @@ static void netplay_flip_users(netplay_t *netplay) static void netplay_frontend_paused(netplay_t *netplay, bool paused) { size_t i; + uint32_t paused_ct; /* Nothing to do if we already knew this */ if (netplay->local_paused == paused) @@ -474,18 +475,33 @@ static void netplay_frontend_paused(netplay_t *netplay, bool paused) netplay->local_paused = paused; - /* If other connections are paused, nothing to say */ - if (netplay->remote_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. */ + paused_ct = 0; + 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; - /* Have to send manually because every buffer must be flushed immediately */ + /* 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) { - netplay_send_raw_cmd(netplay, connection, - paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); + 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); diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 781f2f5b13..fdeda81f1d 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -1095,10 +1095,43 @@ static bool netplay_get_cmd(netplay_t *netplay, } case NETPLAY_CMD_PAUSE: - connection->paused = true; - netplay->remote_paused = true; - netplay_send_raw_cmd_all(netplay, connection, NETPLAY_CMD_PAUSE, NULL, 0); - break; + { + char msg[512], nick[NETPLAY_NICK_LEN]; + msg[sizeof(msg)-1] = '\0'; + + /* 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)) + { + RARCH_ERR("Failed to receive paused nickname.\n"); + return netplay_cmd_nak(netplay, connection); + } + nick[sizeof(nick)-1] = '\0'; + + /* We outright ignore pausing from spectators */ + if (connection->mode != NETPLAY_CONNECTION_PLAYING) + break; + + connection->paused = true; + netplay->remote_paused = true; + if (netplay->is_server) + { + snprintf(msg, sizeof(msg)-1, 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); + } + else + { + snprintf(msg, sizeof(msg)-1, msg_hash_to_str(MSG_NETPLAY_PEER_PAUSED), nick); + } + RARCH_LOG("%s\n", msg); + runloop_msg_queue_push(msg, 1, 180, false); + break; + } case NETPLAY_CMD_RESUME: remote_unpaused(netplay, connection); From 8b21014d07e578b5f5a0f28b8434ede0e9dfe9b1 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 14:48:07 -0500 Subject: [PATCH 75/89] Improvements to handshake protocol Making the netplay handshake protocol send the core and content as an explicit command, so that the other side can (notionally) choose to load it. That isn't implemented, of course. --- network/netplay/README | 32 ++++ network/netplay/netplay_handshake.c | 263 +++++++++++++++++++++------- network/netplay/netplay_io.c | 36 +--- network/netplay/netplay_private.h | 48 ++--- 4 files changed, 245 insertions(+), 134 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index ad6932bc3e..02e347cb3e 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -102,6 +102,24 @@ early (i.e., it only forwards data on the frame it's reached), it must also inform all clients of its own current frame even if it has no input. The NOINPUT command is provided for that purpose. +The handshake procedure (this part is done by both server and client): +1) Send connection header +2) Receive and verify connection header +3) Send nickname +4) Receive nickname + +For the client: +5) Send PASSWORD if applicable +4) Receive INFO +5) Send INFO +6) Receive SYNC + +For the server: +5) Receive PASSWORD if applicable +6) Send INFO +7) Receive INFO +8) Send SYNC + * Guarantee not actually a guarantee. @@ -172,6 +190,20 @@ Description: Send hashed password to server. Mandatory handshake command for clients if the server demands a password. +Command: INFO +Payload + { + core name: char[32] + core version: char[32] + content CRC: uint32 + } +Description: + Send core/content info. Mandatory handshake command. Sent by server first, + then by client, and must match. Server may send INFO with no payload, in + which case the client sends its own info and expects the server to load the + appropriate core and content then send a new INFO command. If mutual + agreement cannot be achieved, the correct solution is to simply disconnect. + Command: SYNC Payload: { diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 337dc033bf..6584140416 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -125,51 +125,20 @@ void netplay_log_connection(const struct sockaddr_storage *their_addr, /** * netplay_impl_magic: * - * Not really a hash, but should be enough to differentiate - * implementations from each other. - * - * Subtle differences in the implementation will not be possible to spot. - * The alternative would have been checking serialization sizes, but it - * was troublesome for cross platform compat. - **/ + * A pseudo-hash of the RetroArch and Netplay version, so only compatible + * versions play together. + */ uint32_t netplay_impl_magic(void) { size_t i, len; - retro_ctx_api_info_t api_info; - unsigned api; uint32_t res = 0; - rarch_system_info_t *info = NULL; - const char *lib = NULL; const char *ver = PACKAGE_VERSION; - core_api_version(&api_info); - - api = api_info.version; - - runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); - - res |= api; - - if (info) - { - lib = info->info.library_name; - - len = strlen(lib); - for (i = 0; i < len; i++) - res ^= lib[i] << (i & 0xf); - - lib = info->info.library_version; - len = strlen(lib); - - for (i = 0; i < len; i++) - res ^= lib[i] << (i & 0xf); - } - len = strlen(ver); for (i = 0; i < len; i++) - res ^= ver[i] << ((i & 0xf) + 16); + res ^= ver[i] << (i & 0xf); - res ^= NETPLAY_PROTOCOL_VERSION << 24; + res ^= NETPLAY_PROTOCOL_VERSION << (i & 0xf); return res; } @@ -237,15 +206,11 @@ static uint32_t simple_rand_uint32() bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection) { - uint32_t *content_crc_ptr = NULL; - uint32_t header[5] = {0}; - - content_get_crc(&content_crc_ptr); + uint32_t header[4] = {0}; header[0] = htonl(netplay_impl_magic()); - header[1] = htonl(*content_crc_ptr); - header[2] = htonl(netplay_platform_magic()); - header[3] = htonl(NETPLAY_COMPRESSION_SUPPORTED); + header[1] = htonl(netplay_platform_magic()); + header[2] = htonl(NETPLAY_COMPRESSION_SUPPORTED); if (netplay->is_server && (netplay->play_password[0] || netplay->spectate_password[0])) { /* Demand a password */ @@ -253,11 +218,11 @@ bool netplay_handshake_init_send(netplay_t *netplay, simple_srand(time(NULL)); connection->salt = simple_rand_uint32(); if (connection->salt == 0) connection->salt = 1; - header[4] = htonl(connection->salt); + header[3] = htonl(connection->salt); } else { - header[4] = htonl(0); + header[3] = htonl(0); } if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, @@ -280,6 +245,14 @@ struct password_buf_s char password[NETPLAY_PASS_HASH_LEN]; }; +struct info_buf_s +{ + uint32_t cmd[2]; + char core_name[NETPLAY_NICK_LEN]; + char core_version[NETPLAY_NICK_LEN]; + uint32_t content_crc; +}; + #define RECV(buf, sz) \ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), (sz), false); \ if (recvd >= 0 && recvd < (sz)) \ @@ -322,11 +295,10 @@ static void handshake_password(void *ignore, const char *line) bool netplay_handshake_init(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) { - uint32_t header[5] = {0}; + uint32_t header[4] = {0}; ssize_t recvd; const char *dmsg; struct nick_buf_s nick_buf; - uint32_t *content_crc_ptr = NULL; uint32_t local_pmagic, remote_pmagic; uint32_t compression; @@ -344,16 +316,9 @@ bool netplay_handshake_init(netplay_t *netplay, goto error; } - content_get_crc(&content_crc_ptr); - if (*content_crc_ptr != ntohl(header[1])) - { - dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); - goto error; - } - /* We only care about platform magic if our core is quirky */ local_pmagic = netplay_platform_magic(); - remote_pmagic = ntohl(header[2]); + remote_pmagic = ntohl(header[1]); if ((netplay->quirks & NETPLAY_QUIRK_ENDIAN_DEPENDENT) && netplay_endian_mismatch(local_pmagic, remote_pmagic)) { @@ -376,7 +341,7 @@ bool netplay_handshake_init(netplay_t *netplay, netplay->decompression_backend->stream_free(netplay->decompression_stream); /* Check what compression is supported */ - compression = ntohl(header[3]); + compression = ntohl(header[2]); compression &= NETPLAY_COMPRESSION_SUPPORTED; if (compression & NETPLAY_COMPRESSION_ZLIB) { @@ -400,7 +365,7 @@ bool netplay_handshake_init(netplay_t *netplay, } /* If a password is demanded, ask for it */ - if (!netplay->is_server && (connection->salt = ntohl(header[4]))) + if (!netplay->is_server && (connection->salt = ntohl(header[3]))) { menu_input_ctx_line_t line; rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL); @@ -466,6 +431,57 @@ static void netplay_handshake_ready(netplay_t *netplay, struct netplay_connectio netplay->stall = NETPLAY_STALL_NONE; } +/** + * netplay_handshake_info + * + * Send an INFO command. + */ +bool netplay_handshake_info(netplay_t *netplay, struct netplay_connection *connection) +{ + struct info_buf_s info_buf; + rarch_system_info_t *core_info; + uint32_t *content_crc_ptr; + + 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 */ + core_info = NULL; + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &core_info); + if (core_info) + { + strlcpy(info_buf.core_name, core_info->info.library_name, sizeof(info_buf.core_name)); + strlcpy(info_buf.core_version, core_info->info.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_ptr = NULL; + content_get_crc(&content_crc_ptr); + if (content_crc_ptr) + info_buf.content_crc = htonl(*content_crc_ptr); + + /* 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. + */ bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *connection) { /* If we're the server, now we send sync info */ @@ -567,15 +583,16 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, else { connection->can_play = true; - if (!netplay_handshake_sync(netplay, connection)) + if (!netplay_handshake_info(netplay, connection)) return false; + connection->mode = NETPLAY_CONNECTION_PRE_INFO; } } else { - /* Client needs to wait for sync info */ - connection->mode = NETPLAY_CONNECTION_PRE_SYNC; + /* Client needs to wait for INFO */ + connection->mode = NETPLAY_CONNECTION_PRE_INFO; } @@ -588,7 +605,7 @@ bool netplay_handshake_pre_nick(netplay_t *netplay, * netplay_handshake_pre_password * * Data receiver for the third, optional stage of server handshake, receiving - * the password. + * the password and sending core/content info. */ bool netplay_handshake_pre_password(netplay_t *netplay, struct netplay_connection *connection, bool *had_input) @@ -645,15 +662,92 @@ bool netplay_handshake_pre_password(netplay_t *netplay, if (!correct) return false; - /* Otherwise, we're ready! */ - if (!netplay_handshake_sync(netplay, connection)) + /* 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. + */ +bool netplay_handshake_pre_info(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + struct info_buf_s info_buf; + ssize_t recvd; + rarch_system_info_t *core_info; + uint32_t *content_crc_ptr; + const char *dmsg = NULL; + + RECV(&info_buf, sizeof(info_buf)); + + if (recvd < 0 || + ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO || + ntohl(info_buf.cmd[1]) != sizeof(info_buf) - 2*sizeof(uint32_t)) + { + RARCH_ERR("Failed to receive netplay info.\n"); + return false; + } + + /* Check the core info */ + core_info = NULL; + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &core_info); + if (core_info) + { + if (strncmp(info_buf.core_name, core_info->info.library_name, sizeof(info_buf.core_name)) || + strncmp(info_buf.core_version, core_info->info.library_version, sizeof(info_buf.core_version))) + { + dmsg = msg_hash_to_str(MSG_NETPLAY_IMPLEMENTATIONS_DIFFER); + goto error; + } + } + + /* Check the content CRC */ + content_crc_ptr = NULL; + content_get_crc(&content_crc_ptr); + if (content_crc_ptr) + { + if (ntohl(info_buf.content_crc) != *content_crc_ptr) + { + dmsg = msg_hash_to_str(MSG_CONTENT_CRC32S_DIFFER); + goto error; + } + } + + /* 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; + +error: + if (dmsg) + { + RARCH_ERR("%s\n", dmsg); + runloop_msg_queue_push(dmsg, 1, 180, false); + } + return false; +} /** * netplay_handshake_pre_sync * @@ -784,12 +878,53 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, } /* We're ready! */ + *had_input = true; netplay->self_mode = NETPLAY_CONNECTION_SPECTATING; connection->mode = NETPLAY_CONNECTION_PLAYING; netplay_handshake_ready(netplay, connection); - *had_input = true; netplay_recv_flush(&connection->recv_packet_buffer); /* Ask to go to player mode */ return netplay_cmd_mode(netplay, connection, NETPLAY_CONNECTION_PLAYING); } + +/** + * netplay_handshake + * + * Data receiver for all handshake states. + */ +bool netplay_handshake(netplay_t *netplay, + struct netplay_connection *connection, bool *had_input) +{ + bool ret; + + switch (connection->mode) + { + case NETPLAY_CONNECTION_NONE: + /* Huh?! */ + return false; + 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; + default: + return false; + } + + if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && + !netplay_send_cur_input(netplay, connection)) + return false; + + return ret; +} diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index fdeda81f1d..61a519463e 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -359,40 +359,8 @@ static bool netplay_get_cmd(netplay_t *netplay, char msg[512]; /* We don't handle the initial handshake here */ - switch (connection->mode) - { - case NETPLAY_CONNECTION_NONE: - /* Huh?! */ - return false; - case NETPLAY_CONNECTION_INIT: - return netplay_handshake_init(netplay, connection, had_input); - case NETPLAY_CONNECTION_PRE_NICK: - { - bool ret = netplay_handshake_pre_nick(netplay, connection, had_input); - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - return ret; - } - case NETPLAY_CONNECTION_PRE_PASSWORD: - { - bool ret = netplay_handshake_pre_password(netplay, connection, had_input); - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - return ret; - } - case NETPLAY_CONNECTION_PRE_SYNC: - { - bool ret = netplay_handshake_pre_sync(netplay, connection, had_input); - if (connection->mode >= NETPLAY_CONNECTION_CONNECTED && - !netplay_send_cur_input(netplay, connection)) - return false; - return ret; - } - default: - break; - } + if (connection->mode < NETPLAY_CONNECTION_CONNECTED) + return netplay_handshake(netplay, connection, had_input); /* FIXME: This depends on delta_frame_ready */ diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 5a183965a9..05f65039bb 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -112,20 +112,23 @@ enum netplay_cmd /* Give the connection password */ NETPLAY_CMD_PASSWORD = 0x0021, + /* Give core/content info */ + NETPLAY_CMD_INFO = 0x0022, + /* Initial synchronization info (frame, sram, player info) */ - NETPLAY_CMD_SYNC = 0x0022, + NETPLAY_CMD_SYNC = 0x0023, /* Join spectator mode */ - NETPLAY_CMD_SPECTATE = 0x0023, + NETPLAY_CMD_SPECTATE = 0x0024, /* Join play mode */ - NETPLAY_CMD_PLAY = 0x0024, + NETPLAY_CMD_PLAY = 0x0025, /* Report player mode */ - NETPLAY_CMD_MODE = 0x0025, + NETPLAY_CMD_MODE = 0x0026, /* Report player mode refused */ - NETPLAY_CMD_MODE_REFUSED = 0x0026, + NETPLAY_CMD_MODE_REFUSED = 0x0027, /* Loading and synchronization */ @@ -187,6 +190,7 @@ enum rarch_netplay_connection_mode NETPLAY_CONNECTION_INIT, /* Waiting for header */ NETPLAY_CONNECTION_PRE_NICK, /* Waiting for nick */ NETPLAY_CONNECTION_PRE_PASSWORD, /* Waiting for password */ + NETPLAY_CONNECTION_PRE_INFO, /* Waiting for core/content info */ NETPLAY_CONNECTION_PRE_SYNC, /* Waiting for sync */ /* Ready: */ @@ -580,39 +584,11 @@ bool netplay_handshake_init_send(netplay_t *netplay, struct netplay_connection *connection); /** - * netplay_handshake_init + * netplay_handshake * - * Data receiver for the initial part of the handshake, i.e., waiting for the - * netplay header. + * Data receiver for all handshake states. */ -bool netplay_handshake_init(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input); - -/** - * netplay_handshake_pre_nick - * - * Data receiver for the second stage of handshake, receiving the other side's - * nickname. - */ -bool netplay_handshake_pre_nick(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input); - -/** - * netplay_handshake_pre_password - * - * Data receiver for the third, optional stage of server handshake, receiving - * the password. - */ -bool netplay_handshake_pre_password(netplay_t *netplay, - struct netplay_connection *connection, bool *had_input); - -/** - * netplay_handshake_pre_sync - * - * Data receiver for the client's third handshake stage, receiving the - * synchronization information. - */ -bool netplay_handshake_pre_sync(netplay_t *netplay, +bool netplay_handshake(netplay_t *netplay, struct netplay_connection *connection, bool *had_input); From d6d96704fd01da9e4f9855f520a501b74518554f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 15:45:29 -0500 Subject: [PATCH 76/89] Don't panic about delta frames not being ready if we're not even fully connected yet. --- network/netplay/netplay_io.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 61a519463e..48db63fc94 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -1152,15 +1152,24 @@ int netplay_poll_net_input(netplay_t *netplay, bool block) netplay->timeout_cnt++; /* Make sure we're actually ready for data */ - netplay_update_unread_ptr(netplay); - if (!netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) - break; - if (!netplay->is_server && - !netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->server_ptr], - netplay->server_frame_count)) - break; + if (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED) + { + netplay_update_unread_ptr(netplay); + if (!netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) + { + fprintf(stderr, "CATASTROPHE: Cannot load %u (%lu) while at %u (%lu)\n", netplay->self_frame_count, netplay->self_ptr, netplay->unread_frame_count, netplay->unread_ptr); + break; + } + if (!netplay->is_server && + !netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->server_ptr], + netplay->server_frame_count)) + { + fprintf(stderr, "CATASTROPHE DEUX\n"); + break; + } + } /* Read input from each connection */ for (i = 0; i < netplay->connections_size; i++) From 04266cf4f74238ada7c3f42bfc86cff981b52ff8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 16:50:06 -0500 Subject: [PATCH 77/89] Run synchronization even when stalled Previously, we could be stalled by one player but still reading data from another, which would wedge the client because we would never act upon the newly-read data. Now we act upon data even if we're stalled. Fixes bugs in initial connection with high latency. --- network/netplay/netplay_frontend.c | 19 +++-- network/netplay/netplay_io.c | 114 ++++++++++++++++++++++++----- network/netplay/netplay_private.h | 3 +- network/netplay/netplay_sync.c | 26 ++----- 4 files changed, 118 insertions(+), 44 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index ff68b1daee..8f5c0300cc 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -521,6 +521,8 @@ static void netplay_frontend_paused(netplay_t *netplay, bool paused) **/ bool netplay_pre_frame(netplay_t *netplay) { + bool sync_stalled; + retro_assert(netplay); /* FIXME: This is an ugly way to learn we're not paused anymore */ @@ -556,11 +558,18 @@ bool netplay_pre_frame(netplay_t *netplay) } } - if (!netplay_sync_pre_frame(netplay)) - return false; + sync_stalled = !netplay_sync_pre_frame(netplay); - return (!netplay->connected_players || - (!netplay->stall && !netplay->remote_paused)); + if (sync_stalled || + (netplay->connected_players && + (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; } /** @@ -576,7 +585,7 @@ void netplay_post_frame(netplay_t *netplay) size_t i; retro_assert(netplay); netplay_update_unread_ptr(netplay); - netplay_sync_post_frame(netplay); + netplay_sync_post_frame(netplay, false); for (i = 0; i < netplay->connections_size; i++) { diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 48db63fc94..b5419ba7f1 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -26,6 +26,33 @@ #include "../../runloop.h" +#if 1 +#define DEBUG_NETPLAY_STEPS 1 + +static void print_state(netplay_t *netplay) +{ + char msg[512]; + size_t cur = 0; + uint32_t player; + +#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 (player = 0; player < MAX_USERS; player++) + { + if ((netplay->connected_players & (1<read_frame_count[player])); + } + APPEND((M, "\n")); + msg[sizeof(msg)-1] = '\0'; + + RARCH_LOG("%s\n", msg); +} +#endif + /** * remote_unpaused * @@ -362,8 +389,6 @@ static bool netplay_get_cmd(netplay_t *netplay, if (connection->mode < NETPLAY_CONNECTION_CONNECTED) return netplay_handshake(netplay, connection, had_input); - /* FIXME: This depends on delta_frame_ready */ - #define RECV(buf, sz) \ recvd = netplay_recv(&connection->recv_packet_buffer, connection->fd, (buf), \ (sz), false); \ @@ -452,7 +477,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player])) { /* FIXME: Catastrophe! */ - RARCH_ERR("Netplay input without a ready delta frame!\n"); + RARCH_ERR("Netplay input from %u without a ready delta frame!\n", player); return netplay_cmd_nak(netplay, connection); } memcpy(dframe->real_input_state[player], buffer + 2, @@ -475,6 +500,11 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->server_ptr = netplay->read_ptr[player]; netplay->server_frame_count = netplay->read_frame_count[player]; } + +#ifdef DEBUG_NETPLAY_STEPS + RARCH_LOG("Received input from %u\n", player); + print_state(netplay); +#endif break; } @@ -737,14 +767,33 @@ static bool netplay_get_cmd(netplay_t *netplay, } else { + uint32_t frame_count; + /* It wants future frames, make sure we don't capture or send intermediate ones */ START(netplay->self_ptr); - while (dframe->used && dframe->frame < frame) + frame_count = netplay->self_frame_count; + while (true) { + 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); + } + } + memset(dframe->self_state, 0, sizeof(dframe->self_state)); memset(dframe->real_input_state[player], 0, sizeof(dframe->self_state)); dframe->have_local = true; + + /* Go on to the next delta frame */ NEXT(); + frame_count++; + + if (frame_count >= frame) + break; } } @@ -755,6 +804,11 @@ static bool netplay_get_cmd(netplay_t *netplay, RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false); +#ifdef DEBUG_NETPLAY_STEPS + RARCH_LOG("Received mode change self->%u\n", player); + print_state(netplay); +#endif + } else /* YOU && !PLAYING */ { @@ -770,6 +824,11 @@ static bool netplay_get_cmd(netplay_t *netplay, RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false); +#ifdef DEBUG_NETPLAY_STEPS + RARCH_LOG("Received mode change %u self->spectating\n", netplay->self_player); + print_state(netplay); +#endif + } } @@ -795,6 +854,11 @@ static bool netplay_get_cmd(netplay_t *netplay, RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false); +#ifdef DEBUG_NETPLAY_STEPS + RARCH_LOG("Received mode change spectator->%u\n", player); + print_state(netplay); +#endif + } else { @@ -805,6 +869,12 @@ static bool netplay_get_cmd(netplay_t *netplay, snprintf(msg, sizeof(msg)-1, "Player %d has left", player+1); RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, false); + +#ifdef DEBUG_NETPLAY_STEPS + RARCH_LOG("Received mode change %u->spectator\n", player); + print_state(netplay); +#endif + } } @@ -1003,6 +1073,12 @@ static bool netplay_get_cmd(netplay_t *netplay, return netplay_cmd_nak(netplay, connection); } + if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr[connection->player]], frame)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE with unready delta frame.\n"); + return netplay_cmd_nak(netplay, connection); + } + RECV(&isize, sizeof(isize)) { RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n"); @@ -1059,6 +1135,12 @@ static bool netplay_get_cmd(netplay_t *netplay, netplay->savestate_request_outstanding = false; netplay->other_ptr = netplay->read_ptr[connection->player]; netplay->other_frame_count = frame; + +#ifdef DEBUG_NETPLAY_STEPS + RARCH_LOG("Loading state at %u\n", frame); + print_state(netplay); +#endif + break; } @@ -1154,21 +1236,15 @@ int netplay_poll_net_input(netplay_t *netplay, bool block) /* Make sure we're actually ready for data */ if (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED) { - netplay_update_unread_ptr(netplay); - if (!netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) - { - fprintf(stderr, "CATASTROPHE: Cannot load %u (%lu) while at %u (%lu)\n", netplay->self_frame_count, netplay->self_ptr, netplay->unread_frame_count, netplay->unread_ptr); - break; - } - if (!netplay->is_server && - !netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->server_ptr], - netplay->server_frame_count)) - { - fprintf(stderr, "CATASTROPHE DEUX\n"); - break; - } + netplay_update_unread_ptr(netplay); + if (!netplay_delta_frame_ready(netplay, + &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) + { + fprintf(stderr, "CATASTROPHE: Cannot load %u (%lu=%u) while at %u (%lu)\n", + netplay->unread_frame_count, netplay->unread_ptr, netplay->buffer[netplay->unread_ptr].frame, + netplay->self_frame_count, netplay->self_ptr); + break; + } } /* Read input from each connection */ diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 05f65039bb..cf94284b53 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -777,10 +777,11 @@ bool netplay_sync_pre_frame(netplay_t *netplay); /** * netplay_sync_post_frame * @netplay : pointer to netplay object + * @stalled : true if we're currently stalled * * 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); +void netplay_sync_post_frame(netplay_t *netplay, bool stalled); #endif diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 3eb85af91a..8e572ef6ef 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -342,10 +342,14 @@ process: * 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) +void netplay_sync_post_frame(netplay_t *netplay, bool stalled) { - netplay->self_ptr = NEXT_PTR(netplay->self_ptr); - netplay->self_frame_count++; + /* Unless we're stalling, we've just finished running a frame */ + if (!stalled) + { + netplay->self_ptr = NEXT_PTR(netplay->self_ptr); + netplay->self_frame_count++; + } /* Only relevant if we're connected */ if ((netplay->is_server && !netplay->connected_players) || @@ -505,20 +509,4 @@ void netplay_sync_post_frame(netplay_t *netplay) driver_ctl(RARCH_DRIVER_CTL_SET_NONBLOCK_STATE, NULL); } } - - /* If we're supposed to stall, rewind (we shouldn't get this far if we're - * stalled, so this is a last resort) */ - if (netplay->stall) - { - retro_ctx_serialize_info_t serial_info; - - netplay->self_ptr = PREV_PTR(netplay->self_ptr); - netplay->self_frame_count--; - - serial_info.data = NULL; - serial_info.data_const = netplay->buffer[netplay->self_ptr].state; - serial_info.size = netplay->state_size; - - core_unserialize(&serial_info); - } } From 7c2f12fbd97e0cdfb1323f254cf37f7214ab6ad0 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 16:52:02 -0500 Subject: [PATCH 78/89] Removign some leftover debug code --- network/netplay/netplay_io.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index b5419ba7f1..e710992866 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -26,7 +26,7 @@ #include "../../runloop.h" -#if 1 +#if 0 #define DEBUG_NETPLAY_STEPS 1 static void print_state(netplay_t *netplay) @@ -50,6 +50,9 @@ static void print_state(netplay_t *netplay) msg[sizeof(msg)-1] = '\0'; RARCH_LOG("%s\n", msg); + +#undef APPEND +#undef M } #endif @@ -1239,12 +1242,7 @@ int netplay_poll_net_input(netplay_t *netplay, bool block) netplay_update_unread_ptr(netplay); if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) - { - fprintf(stderr, "CATASTROPHE: Cannot load %u (%lu=%u) while at %u (%lu)\n", - netplay->unread_frame_count, netplay->unread_ptr, netplay->buffer[netplay->unread_ptr].frame, - netplay->self_frame_count, netplay->self_ptr); break; - } } /* Read input from each connection */ From abce07db17bd5b0afdc3e06abdd30c934c5f0d4a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 17:10:56 -0500 Subject: [PATCH 79/89] Correcting stateless mode in menu. --- menu/menu_displaylist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index f59782c09c..3689071fd3 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4699,7 +4699,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_STATELESS_MODE, - PARSE_ONLY_UINT, false) != -1) + PARSE_ONLY_BOOL, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, From 43fdccd89cd546b4287fe01e0843558648157373 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 18:59:19 -0500 Subject: [PATCH 80/89] Making some messages grammatically similar to others --- intl/msg_hash_us.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 45391cb972..9eadf80393 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -48,11 +48,11 @@ MSG_HASH( ) MSG_HASH( MSG_NETPLAY_YOU_HAVE_LEFT_THE_GAME, - "You have left the game." + "You have left the game" ) MSG_HASH( MSG_NETPLAY_YOU_HAVE_JOINED_AS_PLAYER_N, - "You have joined as player %d." + "You have joined as player %d" ) MSG_HASH( MSG_NETPLAY_IMPLEMENTATIONS_DIFFER, @@ -60,11 +60,11 @@ MSG_HASH( ) MSG_HASH( MSG_NETPLAY_ENDIAN_DEPENDENT, - "This core does not support inter-architecture netplay between these systems." + "This core does not support inter-architecture netplay between these systems" ) MSG_HASH( MSG_NETPLAY_PLATFORM_DEPENDENT, - "This core does not support inter-architecture netplay." + "This core does not support inter-architecture netplay" ) MSG_HASH( MSG_NETPLAY_ENTER_PASSWORD, @@ -72,35 +72,35 @@ MSG_HASH( ) MSG_HASH( MSG_NETPLAY_INCORRECT_PASSWORD, - "Incorrect password." + "Incorrect password" ) MSG_HASH( MSG_NETPLAY_SERVER_NAMED_HANGUP, - "\"%s\" has disconnected." + "\"%s\" has disconnected" ) MSG_HASH( MSG_NETPLAY_SERVER_HANGUP, - "A netplay client has disconnected." + "A netplay client has disconnected" ) MSG_HASH( MSG_NETPLAY_CLIENT_HANGUP, - "Netplay disconnected." + "Netplay disconnected" ) MSG_HASH( MSG_NETPLAY_CANNOT_PLAY_UNPRIVILEGED, - "You do not have permission to play." + "You do not have permission to play" ) MSG_HASH( MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS, - "There are no free player slots." + "There are no free player slots" ) MSG_HASH( MSG_NETPLAY_CANNOT_PLAY, - "Cannot switch to play mode." + "Cannot switch to play mode" ) MSG_HASH( MSG_NETPLAY_PEER_PAUSED, - "Netplay peer \"%s\" paused." + "Netplay peer \"%s\" paused" ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT, From b535412914fd86226990aa0067e566f81c68bd17 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 18:59:42 -0500 Subject: [PATCH 81/89] Adding nick-changing This is just to disambiguate multiple connections with the same nick (usually Anonymous), which will now become e.g. Anonymous (2). --- intl/msg_hash_us.h | 4 ++ msg_hash.h | 1 + network/netplay/README | 10 +++-- network/netplay/netplay_handshake.c | 62 +++++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 9eadf80393..8bebf08327 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -102,6 +102,10 @@ MSG_HASH( MSG_NETPLAY_PEER_PAUSED, "Netplay peer \"%s\" paused" ) +MSG_HASH( + MSG_NETPLAY_CHANGED_NICK, + "Your nickname changed to \"%s\"" + ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_SHARED_CONTEXT, "Give hardware-rendered cores their own private context. Avoids having to assume hardware state changes inbetween frames." diff --git a/msg_hash.h b/msg_hash.h index 4e57829709..5f080ca84d 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -161,6 +161,7 @@ enum msg_hash_enums MSG_NETPLAY_CANNOT_PLAY_NO_SLOTS, MSG_NETPLAY_CANNOT_PLAY, MSG_NETPLAY_PEER_PAUSED, + MSG_NETPLAY_CHANGED_NICK, MSG_AUTODETECT, MSG_AUDIO_VOLUME, MSG_LIBRETRO_FRONTEND, diff --git a/network/netplay/README b/network/netplay/README index 02e347cb3e..5220f35e49 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -211,14 +211,16 @@ Payload: connected players: uint32 flip frame: uint32 controller devices: uint32[16] + client nick: char[32] sram: variable } Description: Initial state synchronization. Mandatory handshake command from server to - client only. Sent after receiving client's NICK. Connected players is a - bitmap with the lowest bit being player 0. Flip frame is 0 if players - aren't flipped. Controller devices are the devices plugged into each - controller port, and 16 is really MAX_USERS. + client only. Connected players is a bitmap with the lowest bit being player + 0. Flip frame is 0 if players aren't flipped. Controller devices are the + devices plugged into each controller port, and 16 is really MAX_USERS. + Client is forced to have a different nick if multiple clients have the same + nick. Command: SPECTATE Payload: None diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 6584140416..ed4d345693 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -491,13 +491,17 @@ bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *conne size_t i; uint32_t device; retro_ctx_memory_info_t mem_info; + size_t nicklen, nickmangle; + int matchct; + bool nick_matched; mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); /* Send basic sync info */ cmd[0] = htonl(NETPLAY_CMD_SYNC); - cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + mem_info.size); + cmd[1] = htonl(3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + + NETPLAY_NICK_LEN + mem_info.size); cmd[2] = htonl(netplay->self_frame_count); connected_players = netplay->connected_players; if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) @@ -521,6 +525,43 @@ bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *conne 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 */ if (!netplay_send(&connection->send_packet_buffer, connection->fd, mem_info.data, mem_info.size) || @@ -765,6 +806,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, ssize_t recvd; settings_t *settings = config_get_ptr(); retro_ctx_controller_info_t pad; + char new_nick[NETPLAY_NICK_LEN]; retro_ctx_memory_info_t mem_info; RECV(cmd, sizeof(cmd)) @@ -777,7 +819,8 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, /* Only expecting a sync command */ if (ntohl(cmd[0]) != NETPLAY_CMD_SYNC || - ntohl(cmd[1]) < 3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t)) + ntohl(cmd[1]) < 3*sizeof(uint32_t) + MAX_USERS*sizeof(uint32_t) + + NETPLAY_NICK_LEN) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_RECEIVE_SRAM_DATA_FROM_HOST)); @@ -840,12 +883,25 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, core_set_controller_port_device(&pad); } + /* Get our nick */ + RECV(new_nick, NETPLAY_NICK_LEN) + return false; + if (strncmp(netplay->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); + } + /* Now check the SRAM */ mem_info.id = RETRO_MEMORY_SAVE_RAM; core_get_memory(&mem_info); local_sram_size = mem_info.size; - remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t) - MAX_USERS*sizeof(uint32_t); + remote_sram_size = ntohl(cmd[1]) - 3*sizeof(uint32_t) - + MAX_USERS*sizeof(uint32_t) - NETPLAY_NICK_LEN; if (local_sram_size != 0 && local_sram_size == remote_sram_size) { From 8195a5bcee43f4fe6704747e367c1b71f9692659 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 20:08:13 -0500 Subject: [PATCH 82/89] Try to catch up if we're falling behind our peer, not anyone In the previous catch-up system, we would only try to catch up if we were falling behind the farthest-behind peer. However, as they would also only try to catch up to us, everyone basically agreed to the worst-case latency. It makes more sense to try to be in parity with your direct peer than with indirect connections. --- network/netplay/netplay_sync.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 8e572ef6ef..4652b5d1fe 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -344,6 +344,8 @@ process: */ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) { + uint32_t cmp_frame_count; + /* Unless we're stalling, we've just finished running a frame */ if (!stalled) { @@ -487,11 +489,16 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) netplay->force_rewind = false; } + if (netplay->is_server) + cmp_frame_count = netplay->unread_frame_count; + else + cmp_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 >= netplay->unread_frame_count) + if (netplay->self_frame_count >= cmp_frame_count) { netplay->catch_up = false; input_driver_unset_nonblock_state(); @@ -499,10 +506,10 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled) } } - else + else if (!stalled) { /* Are we falling behind? */ - if (netplay->self_frame_count < netplay->unread_frame_count - 2) + if (netplay->self_frame_count < cmp_frame_count - 2) { netplay->catch_up = true; input_driver_set_nonblock_state(); From e79f30604f4fe77cba65dc6094384f033cca1328 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 20:10:51 -0500 Subject: [PATCH 83/89] Better stall timing We now stall not until we've reached parity (which makes no sense since we expect latency), but only until we're not likely to stall again. --- network/netplay/netplay_frontend.c | 39 ++++++++++++++++++++---------- network/netplay/netplay_io.c | 18 +++----------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 8f5c0300cc..c5571b0493 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -152,6 +152,26 @@ static bool get_self_input_state(netplay_t *netplay) return true; } +static uint32_t netplay_max_ahead(netplay_t *netplay) +{ + uint32_t max_ahead; + + /* Figure out how many frames we're allowed to be ahead: Ideally we need to be + * able to run our entire stall worth of frames in one real frame. In + * practice, we'll allow a couple jitter frames. (FIXME: hard coded + * as three 60FPS frames) */ + if (netplay_data->frame_run_time_avg) + max_ahead = 50000 / netplay_data->frame_run_time_avg; + else + max_ahead = NETPLAY_MAX_STALL_FRAMES; + if (max_ahead > NETPLAY_MAX_STALL_FRAMES) + max_ahead = NETPLAY_MAX_STALL_FRAMES; + if (max_ahead < 2) + max_ahead = 2; + + return max_ahead; +} + /** * netplay_poll: * @netplay : pointer to netplay object @@ -198,8 +218,11 @@ static bool netplay_poll(void) switch (netplay_data->stall) { case NETPLAY_STALL_RUNNING_FAST: + { + uint32_t max_ahead = netplay_max_ahead(netplay_data); netplay_update_unread_ptr(netplay_data); - if (netplay_data->unread_frame_count >= netplay_data->self_frame_count) + if (netplay_data->unread_frame_count + max_ahead - 2 + > netplay_data->self_frame_count) { netplay_data->stall = NETPLAY_STALL_NONE; for (i = 0; i < netplay_data->connections_size; i++) @@ -210,6 +233,7 @@ static bool netplay_poll(void) } } break; + } case NETPLAY_STALL_NO_CONNECTION: /* We certainly haven't fixed this */ @@ -217,18 +241,7 @@ static bool netplay_poll(void) default: /* not stalling */ { - retro_time_t max_ahead; - - /* Figure out how many frames we're allowed to be ahead: Ideally we need to be - * able to run our entire stall worth of frames in one real frame. In - * practice, we'll allow a couple jitter frames. (FIXME: hard coded - * as three 60FPS frame) */ - if (netplay_data->frame_run_time_avg) - max_ahead = 50000 / netplay_data->frame_run_time_avg; - else - max_ahead = NETPLAY_MAX_STALL_FRAMES; - if (max_ahead > NETPLAY_MAX_STALL_FRAMES) - max_ahead = NETPLAY_MAX_STALL_FRAMES; + uint32_t max_ahead = netplay_max_ahead(netplay_data); /* Are we too far ahead? */ netplay_update_unread_ptr(netplay_data); diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index e710992866..76f2f3b56a 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -479,9 +479,8 @@ static bool netplay_get_cmd(netplay_t *netplay, dframe = &netplay->buffer[netplay->read_ptr[player]]; if (!netplay_delta_frame_ready(netplay, dframe, netplay->read_frame_count[player])) { - /* FIXME: Catastrophe! */ - RARCH_ERR("Netplay input from %u without a ready delta frame!\n", player); - return netplay_cmd_nak(netplay, connection); + /* Hopefully we'll be ready after another round of input */ + goto shrt; } memcpy(dframe->real_input_state[player], buffer + 2, WORDS_PER_INPUT*sizeof(uint32_t)); @@ -1078,8 +1077,8 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr[connection->player]], frame)) { - RARCH_ERR("CMD_LOAD_SAVESTATE with unready delta frame.\n"); - return netplay_cmd_nak(netplay, connection); + /* Hopefully it will be after another round of input */ + goto shrt; } RECV(&isize, sizeof(isize)) @@ -1236,15 +1235,6 @@ int netplay_poll_net_input(netplay_t *netplay, bool block) netplay->timeout_cnt++; - /* Make sure we're actually ready for data */ - if (netplay->self_mode >= NETPLAY_CONNECTION_CONNECTED) - { - netplay_update_unread_ptr(netplay); - if (!netplay_delta_frame_ready(netplay, - &netplay->buffer[netplay->unread_ptr], netplay->unread_frame_count)) - break; - } - /* Read input from each connection */ for (i = 0; i < netplay->connections_size; i++) { From 60b81e3a5118bbfb8a37e169f305a7b81fcf12d1 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 20:33:02 -0500 Subject: [PATCH 84/89] Nominal support for server sending blank INFO --- network/netplay/netplay_handshake.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index ed4d345693..33fd65f8d0 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -723,6 +723,7 @@ 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; rarch_system_info_t *core_info; uint32_t *content_crc_ptr; @@ -731,13 +732,32 @@ bool netplay_handshake_pre_info(netplay_t *netplay, RECV(&info_buf, sizeof(info_buf)); if (recvd < 0 || - ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO || - ntohl(info_buf.cmd[1]) != sizeof(info_buf) - 2*sizeof(uint32_t)) + ntohl(info_buf.cmd[0]) != NETPLAY_CMD_INFO) { RARCH_ERR("Failed to receive netplay info.\n"); return false; } + cmd_size = ntohl(info_buf.cmd[1]); + if (cmd_size != sizeof(info_buf) - 2*sizeof(uint32_t)) + { + /* Either the host doesn't have anything loaded, or this is just screwy */ + if (cmd_size != 0) + { + /* Huh? */ + RARCH_ERR("Invalid NETPLAY_CMD_INFO payload size.\n"); + return false; + } + + /* Send our info and hope they load it! */ + if (!netplay_handshake_info(netplay, connection)) + return false; + + *had_input = true; + netplay_recv_flush(&connection->recv_packet_buffer); + return true; + } + /* Check the core info */ core_info = NULL; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &core_info); From 84c33634a661949d1317d8d09f786cd744c3f373 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sat, 17 Dec 2016 22:41:34 -0500 Subject: [PATCH 85/89] Communicate paused-ness during initial connection SYNC. --- network/netplay/README | 3 ++- network/netplay/netplay_handshake.c | 9 ++++++++- network/netplay/netplay_private.h | 1 + network/netplay/netplay_sync.c | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 5220f35e49..9617cfd8f7 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -208,7 +208,8 @@ Command: SYNC Payload: { frame number: uint32 - connected players: uint32 + paused?: 1 bit + connected players: 31 bits flip frame: uint32 controller devices: uint32[16] client nick: char[32] diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 33fd65f8d0..0c5fbac687 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -506,6 +506,8 @@ bool netplay_handshake_sync(netplay_t *netplay, struct netplay_connection *conne connected_players = netplay->connected_players; if (netplay->self_mode == NETPLAY_CONNECTION_PLAYING) connected_players |= 1<self_player; + if (netplay->local_paused || netplay->remote_paused) + connected_players |= NETPLAY_CMD_SYNC_BIT_PAUSED; cmd[3] = htonl(connected_players); if (netplay->flip) cmd[4] = htonl(netplay->flip_frame); @@ -852,10 +854,15 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, return false; new_frame_count = ntohl(new_frame_count); - /* Get the connected players */ + /* Get the connected players and pause mode */ RECV(&connected_players, sizeof(connected_players)) return false; connected_players = ntohl(connected_players); + if (connected_players & NETPLAY_CMD_SYNC_BIT_PAUSED) + { + netplay->remote_paused = true; + connected_players ^= NETPLAY_CMD_SYNC_BIT_PAUSED; + } netplay->connected_players = connected_players; /* And the flip state */ diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index cf94284b53..f43b2d6c11 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -166,6 +166,7 @@ enum netplay_cmd }; #define NETPLAY_CMD_INPUT_BIT_SERVER (1U<<31) +#define NETPLAY_CMD_SYNC_BIT_PAUSED (1U<<31) #define NETPLAY_CMD_MODE_BIT_PLAYING (1U<<17) #define NETPLAY_CMD_MODE_BIT_YOU (1U<<16) diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 4652b5d1fe..72b2e2125d 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -191,7 +191,7 @@ bool netplay_sync_pre_frame(netplay_t *netplay) } else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info)) { - if (netplay->force_send_savestate && !netplay->stall) + if (netplay->force_send_savestate && !netplay->stall && !netplay->remote_paused) { /* Send this along to the other side */ serial_info.data_const = netplay->buffer[netplay->self_ptr].state; From 677ffa9ebd0dd19575cb0053dd1853a70f7207a2 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 18 Dec 2016 16:52:21 -0500 Subject: [PATCH 86/89] Support different forms of compression from different clients. --- network/netplay/netplay_frontend.c | 95 ++++++++++++++++++----------- network/netplay/netplay_handshake.c | 39 +++++++----- network/netplay/netplay_init.c | 12 +++- network/netplay/netplay_io.c | 15 ++++- network/netplay/netplay_private.h | 18 ++++-- 5 files changed, 120 insertions(+), 59 deletions(-) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index c5571b0493..63a75c3dae 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -610,6 +610,59 @@ void netplay_post_frame(netplay_t *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. + */ +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, serial_info->size); + z->compression_backend->set_out(z->compression_stream, + netplay->zbuffer, 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->self_frame_count); + header[3] = htonl(serial_info->size); + + for (i = 0; i < netplay->connections_size; i++) + { + struct netplay_connection *connection = &netplay->connections[i]; + if (!connection->active || + connection->mode < NETPLAY_CONNECTION_CONNECTED || + connection->compression_supported != cx) continue; + + if (!netplay_send(&connection->send_packet_buffer, connection->fd, header, + sizeof(header)) || + !netplay_send(&connection->send_packet_buffer, connection->fd, + netplay->zbuffer, wn)) + netplay_hangup(netplay, connection); + } +} + /** * netplay_load_savestate * @netplay : pointer to netplay object @@ -623,10 +676,7 @@ void netplay_post_frame(netplay_t *netplay) void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save) { - uint32_t header[4]; retro_ctx_serialize_info_t tmp_serial_info; - uint32_t rd, wn; - size_t i; /* Record it in our own buffer */ if (save || !serial_info) @@ -693,39 +743,12 @@ void netplay_load_savestate(netplay_t *netplay, | NETPLAY_QUIRK_NO_TRANSMISSION)) return; - /* Compress it */ - if (!netplay->compression_backend) - return; - netplay->compression_backend->set_in(netplay->compression_stream, - (const uint8_t*)serial_info->data_const, serial_info->size); - netplay->compression_backend->set_out(netplay->compression_stream, - netplay->zbuffer, netplay->zbuffer_size); - if (!netplay->compression_backend->trans(netplay->compression_stream, - true, &rd, &wn, NULL)) - { - /* Catastrophe! */ - for (i = 0; i < netplay->connections_size; i++) - netplay_hangup(netplay, &netplay->connections[i]); - return; - } - - /* And send it to the peers */ - header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); - header[1] = htonl(wn + 2*sizeof(uint32_t)); - header[2] = htonl(netplay->self_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) 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); - } + /* 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); } /** diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index 0c5fbac687..7b57aa49e0 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -301,6 +301,7 @@ bool netplay_handshake_init(netplay_t *netplay, struct nick_buf_s nick_buf; uint32_t local_pmagic, remote_pmagic; uint32_t compression; + struct compression_transcoder *ctrans; dmsg = NULL; @@ -334,31 +335,41 @@ bool netplay_handshake_init(netplay_t *netplay, goto error; } - /* Clear any existing compression */ - if (netplay->compression_stream) - netplay->compression_backend->stream_free(netplay->compression_stream); - if (netplay->decompression_stream) - netplay->decompression_backend->stream_free(netplay->decompression_stream); - /* Check what compression is supported */ compression = ntohl(header[2]); compression &= NETPLAY_COMPRESSION_SUPPORTED; if (compression & NETPLAY_COMPRESSION_ZLIB) { - netplay->compression_backend = trans_stream_get_zlib_deflate_backend(); - if (!netplay->compression_backend) - netplay->compression_backend = trans_stream_get_pipe_backend(); + 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 { - netplay->compression_backend = trans_stream_get_pipe_backend(); + ctrans = &netplay->compress_nil; + if (!ctrans->compression_backend) + { + ctrans->compression_backend = + trans_stream_get_pipe_backend(); + } + connection->compression_supported = 0; } - netplay->decompression_backend = netplay->compression_backend->reverse; + if (!ctrans->decompression_backend) + ctrans->decompression_backend = ctrans->compression_backend->reverse; /* Allocate our compression stream */ - netplay->compression_stream = netplay->compression_backend->stream_new(); - netplay->decompression_stream = netplay->decompression_backend->stream_new(); - if (!netplay->compression_stream || !netplay->decompression_stream) + if (!ctrans->compression_stream) + { + ctrans->compression_stream = ctrans->compression_backend->stream_new(); + ctrans->decompression_stream = ctrans->decompression_backend->stream_new(); + } + if (!ctrans->compression_stream || !ctrans->decompression_stream) { RARCH_ERR("Failed to allocate compression transcoder!\n"); return false; diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index ceb7b7eb01..650d9f3c78 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -540,8 +540,16 @@ void netplay_free(netplay_t *netplay) if (netplay->zbuffer) free(netplay->zbuffer); - if (netplay->compression_stream) - netplay->compression_backend->stream_free(netplay->compression_stream); + if (netplay->compress_nil.compression_stream) + { + netplay->compress_nil.compression_backend->stream_free(netplay->compress_nil.compression_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); + netplay->compress_zlib.decompression_backend->stream_free(netplay->compress_zlib.decompression_stream); + } if (netplay->addr) freeaddrinfo_retro(netplay->addr); diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 76f2f3b56a..cb92a16eff 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -1013,6 +1013,7 @@ static bool netplay_get_cmd(netplay_t *netplay, uint32_t isize; uint32_t rd, wn; uint32_t player; + struct compression_transcoder *ctrans; /* Make sure we're ready for it */ if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION) @@ -1101,12 +1102,20 @@ static bool netplay_get_cmd(netplay_t *netplay, } /* And decompress it */ - netplay->decompression_backend->set_in(netplay->decompression_stream, + 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)); - netplay->decompression_backend->set_out(netplay->decompression_stream, + ctrans->decompression_backend->set_out(ctrans->decompression_stream, (uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state, netplay->state_size); - netplay->decompression_backend->trans(netplay->decompression_stream, + ctrans->decompression_backend->trans(ctrans->decompression_stream, true, &rd, &wn, NULL); /* Skip ahead if it's past where we are */ diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index f43b2d6c11..3133a07e4e 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -274,6 +274,9 @@ struct netplay_connection /* Player # of connected player */ int player; + /* What compression does this peer support? */ + uint32_t compression_supported; + /* Is this player paused? */ bool paused; @@ -282,6 +285,15 @@ struct netplay_connection retro_time_t stall_time; }; +/* Compression transcoder */ +struct compression_transcoder +{ + const struct trans_stream_backend *compression_backend; + void *compression_stream; + const struct trans_stream_backend *decompression_backend; + void *decompression_stream; +}; + struct netplay { /* Are we the server? */ @@ -330,10 +342,8 @@ struct netplay size_t buffer_size; /* Compression transcoder */ - const struct trans_stream_backend *compression_backend; - void *compression_stream; - const struct trans_stream_backend *decompression_backend; - void *decompression_stream; + struct compression_transcoder compress_nil, + compress_zlib; /* A buffer into which to compress frames for transfer */ uint8_t *zbuffer; From dcd4b3046be967dd990458fc27ae8d0857fbc7d3 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 18 Dec 2016 19:27:51 -0500 Subject: [PATCH 87/89] Making negative check_frames be "check only" mode --- configuration.h | 2 +- menu/menu_displaylist.c | 2 +- menu/menu_setting.c | 12 +++--------- network/netplay/netplay_init.c | 2 +- network/netplay/netplay_private.h | 4 ++-- network/netplay/netplay_sync.c | 12 ++++++++++-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/configuration.h b/configuration.h index 4f32cef7ff..8b77811b82 100644 --- a/configuration.h +++ b/configuration.h @@ -402,7 +402,7 @@ typedef struct settings char server[255]; unsigned port; bool stateless_mode; - unsigned check_frames; + int check_frames; bool swap_input; bool nat_traversal; char password[128]; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 3689071fd3..e056ff6a32 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4703,7 +4703,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, - PARSE_ONLY_UINT, false) != -1) + PARSE_ONLY_INT, false) != -1) count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 1a8499c44f..01c874b680 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -1728,15 +1728,9 @@ void general_write_handler(void *data) break; case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: #ifdef HAVE_NETWORKING - { - bool val = (settings->netplay.check_frames > 0); - - if (val) - retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL); - else - retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL); - } + retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL); #endif + break; default: break; } @@ -5624,7 +5618,7 @@ static bool setting_append_list( parent_group, general_write_handler, general_read_handler); - menu_settings_list_current_add_range(list, list_info, 0, 600, 1, true, false); + menu_settings_list_current_add_range(list, list_info, -600, 600, 1, true, false); settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); CONFIG_BOOL( diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 650d9f3c78..63609a10a4 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -414,7 +414,7 @@ static bool netplay_init_buffers(netplay_t *netplay) */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, const char *play_password, const char *spectate_password, - bool stateless_mode, unsigned check_frames, + bool stateless_mode, int check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 3133a07e4e..e0794f0faa 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -442,7 +442,7 @@ struct netplay bool catch_up; /* Frequency with which to check CRCs */ - uint32_t check_frames; + int check_frames; /* Have we checked whether CRCs are valid at all? */ bool crc_validity_checked; @@ -647,7 +647,7 @@ bool netplay_wait_and_init_serialization(netplay_t *netplay); */ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, const char *play_password, const char *spectate_password, - bool stateless_mode, unsigned check_frames, + bool stateless_mode, int check_frames, const struct retro_callbacks *cb, bool nat_traversal, const char *nick, uint64_t quirks); diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 72b2e2125d..fe163c50f4 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -137,7 +137,7 @@ static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *de if (netplay->is_server) { if (netplay->check_frames && - delta->frame % netplay->check_frames == 0) + delta->frame % abs(netplay->check_frames) == 0) { delta->crc = netplay_delta_frame_crc(netplay, delta); netplay_cmd_crc(netplay, delta); @@ -158,7 +158,15 @@ static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *de else if (netplay->crcs_valid) { /* Fix this! */ - netplay_cmd_request_savestate(netplay); + 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) From 8f8e6dfc5f7764b359940ed978cb6359e33d60f8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 18 Dec 2016 19:54:01 -0500 Subject: [PATCH 88/89] Unset the netplay callbacks when we deinit netplay This is a bugfix to master that hadn't migrated to my branch. --- network/netplay/netplay_frontend.c | 1 + 1 file changed, 1 insertion(+) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 63a75c3dae..746645096a 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -843,6 +843,7 @@ void deinit_netplay(void) if (netplay_data) netplay_free(netplay_data); netplay_data = NULL; + core_unset_netplay_callbacks(); } /** From 11a3063fc6aba4dacb51b582894974b7c6455add Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 18 Dec 2016 20:08:21 -0500 Subject: [PATCH 89/89] Forgot the other half of that last bugfix --- network/netplay/netplay_frontend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 746645096a..854d0db73f 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -872,6 +872,8 @@ bool init_netplay(void *direct_host, const char *server, unsigned port, 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();