diff --git a/Makefile.common b/Makefile.common
index 40fe16f938..7de2ceb6ad 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1121,6 +1121,7 @@ ifeq ($(HAVE_NETWORKING), 1)
OBJ += network/netplay/netplay_net.o \
network/netplay/netplay_spectate.o \
network/netplay/netplay_common.o \
+ network/netplay/netplay_discovery.o \
network/netplay/netplay.o
# Retro Achievements (also depends on threads)
diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c
index c63285be71..8c0e25270e 100644
--- a/network/netplay/netplay.c
+++ b/network/netplay/netplay.c
@@ -54,9 +54,6 @@ enum
static bool netplay_enabled = false;
static bool netplay_is_client = false;
-/* Used to advertise or request advertisement of Netplay */
-static int netplay_ad_fd = -1;
-
/* Used while Netplay is running */
static netplay_t *netplay_data = NULL;
@@ -204,28 +201,6 @@ static void init_nat_traversal(netplay_t *netplay)
announce_nat_traversal(netplay);
}
-static bool init_ad_socket(netplay_t *netplay, uint16_t port)
-{
- int fd = socket_init((void**)&netplay->addr, port, NULL, SOCKET_TYPE_DATAGRAM);
-
- if (fd < 0)
- goto error;
-
- if (!socket_bind(fd, (void*)netplay->addr))
- {
- socket_close(fd);
- goto error;
- }
-
- netplay_ad_fd = fd;
-
- return true;
-
-error:
- RARCH_ERR("Failed to initialize netplay advertisement socket.\n");
- return false;
-}
-
static bool init_socket(netplay_t *netplay, const char *server, uint16_t port)
{
if (!network_init())
@@ -1446,9 +1421,8 @@ bool netplay_pre_frame(netplay_t *netplay)
if (netplay->is_server)
{
- /* Advertise our server if applicable */
- if (netplay_ad_fd >= 0 || init_ad_socket(netplay, RARCH_DEFAULT_PORT))
- netplay_ad_server(netplay, netplay_ad_fd);
+ /* Advertise our server */
+ netplay_lan_ad_server(netplay);
/* NAT traversal if applicable */
if (netplay->nat_traversal &&
diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c
index 67b07a70d8..16779d54d7 100644
--- a/network/netplay/netplay_common.c
+++ b/network/netplay/netplay_common.c
@@ -420,104 +420,3 @@ uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
return 0;
return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size);
}
-
-/*
- * AD PACKET FORMAT:
- *
- * Request:
- * 1 word: RANQ (RetroArch Netplay Query)
- * 1 word: Netplay protocol version
- *
- * Reply:
- * 1 word : RANS (RetroArch Netplay Server)
- * 1 word : Netplay protocol version
- * 1 word : Port
- * 8 words: RetroArch version
- * 8 words: Nick
- * 8 words: Core name
- * 8 words: Core version
- * 8 words: Content name (currently always blank)
- */
-
-#define AD_PACKET_MAX_SIZE 512
-#define AD_PACKET_STRING_SIZE 32
-#define AD_PACKET_STRING_WORDS (AD_PACKET_STRING_SIZE/sizeof(uint32_t))
-static uint32_t *ad_packet_buffer = NULL;
-
-bool netplay_ad_server(netplay_t *netplay, int ad_fd)
-{
- fd_set fds;
- struct timeval tmp_tv = {0};
- struct sockaddr their_addr;
- socklen_t addr_size;
- rarch_system_info_t *info = NULL;
- size_t bufloc;
-
- if (!ad_packet_buffer)
- {
- ad_packet_buffer = (uint32_t *) malloc(AD_PACKET_MAX_SIZE);
- if (!ad_packet_buffer)
- return false;
- }
-
- /* Check for any ad queries */
- while (1)
- {
- FD_ZERO(&fds);
- FD_SET(ad_fd, &fds);
- if (socket_select(ad_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
- break;
- if (!FD_ISSET(ad_fd, &fds))
- break;
-
- /* Somebody queried, so check that it's valid */
- addr_size = sizeof(their_addr);
- if (recvfrom(ad_fd, (char*)ad_packet_buffer, AD_PACKET_MAX_SIZE, 0,
- &their_addr, &addr_size) >= (ssize_t) (2*sizeof(uint32_t)))
- {
- /* Make sure it's a valid query */
- if (memcmp(ad_packet_buffer, "RANQ", 4))
- continue;
-
- /* For this version */
- if (ntohl(ad_packet_buffer[1]) != NETPLAY_PROTOCOL_VERSION)
- continue;
-
- runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
-
- /* Now build our response */
- memset(ad_packet_buffer, 0, AD_PACKET_MAX_SIZE);
- memcpy(ad_packet_buffer, "RANS", 4);
- ad_packet_buffer[1] = htonl(NETPLAY_PROTOCOL_VERSION);
- ad_packet_buffer[2] = htonl(netplay->tcp_port);
- bufloc = 3;
- strncpy((char *) (ad_packet_buffer + bufloc),
- PACKAGE_VERSION, AD_PACKET_STRING_SIZE);
- bufloc += AD_PACKET_STRING_WORDS;
- strncpy((char *) (ad_packet_buffer + bufloc),
- netplay->nick, AD_PACKET_STRING_SIZE);
- bufloc += AD_PACKET_STRING_WORDS;
- if (info)
- {
- strncpy((char *) (ad_packet_buffer + bufloc),
- info->info.library_name, AD_PACKET_STRING_SIZE);
- bufloc += AD_PACKET_STRING_WORDS;
- strncpy((char *) (ad_packet_buffer + bufloc),
- info->info.library_version, AD_PACKET_STRING_SIZE);
- bufloc += AD_PACKET_STRING_WORDS;
- /* Blank content */
- bufloc += AD_PACKET_STRING_WORDS;
- }
- else
- {
- bufloc += 3*AD_PACKET_STRING_WORDS;
- }
-
- /* And send it */
- sendto(ad_fd, (const char*)ad_packet_buffer, bufloc*sizeof(uint32_t), 0,
- &their_addr, addr_size);
- }
- }
-
- return true;
-}
diff --git a/network/netplay/netplay_discovery.c b/network/netplay/netplay_discovery.c
new file mode 100644
index 0000000000..9074609ebf
--- /dev/null
+++ b/network/netplay/netplay_discovery.c
@@ -0,0 +1,321 @@
+/* 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 .
+ */
+
+/*
+ * AD PACKET FORMAT:
+ *
+ * Request:
+ * 1 word: RANQ (RetroArch Netplay Query)
+ * 1 word: Netplay protocol version
+ *
+ * Reply:
+ * 1 word : RANS (RetroArch Netplay Server)
+ * 1 word : Netplay protocol version
+ * 1 word : Port
+ * 8 words: RetroArch version
+ * 8 words: Nick
+ * 8 words: Core name
+ * 8 words: Core version
+ * 8 words: Content name (currently always blank)
+ */
+
+#include
+#include
+
+#include
+
+#include "../../runloop.h"
+#include "../../version.h"
+#include "netplay.h"
+#include "netplay_discovery.h"
+#include "netplay_private.h"
+
+struct ad_packet
+{
+ uint32_t header;
+ uint32_t protocol_version;
+ uint32_t port;
+ char retroarch_version[NETPLAY_HOST_STR_LEN];
+ char nick[NETPLAY_HOST_STR_LEN];
+ char core[NETPLAY_HOST_STR_LEN];
+ char core_version[NETPLAY_HOST_STR_LEN];
+ char content[NETPLAY_HOST_STR_LEN];
+};
+
+bool netplay_lan_ad_client(void);
+
+/* LAN discovery sockets */
+static int lan_ad_server_fd = -1;
+static int lan_ad_client_fd = -1;
+
+/* Packet buffer for advertisement and responses */
+static struct ad_packet ad_packet_buffer;
+
+/* List of discovered hosts */
+static struct netplay_host_list discovered_hosts;
+static size_t discovered_hosts_allocated;
+
+/** Initialize Netplay discovery (client) */
+bool init_netplay_discovery(void)
+{
+ struct addrinfo *addr = NULL;
+ int fd = socket_init((void **) &addr, 0, NULL, SOCKET_TYPE_DATAGRAM);
+
+ if (fd < 0)
+ goto error;
+
+ if (!socket_bind(fd, (void*)addr))
+ {
+ socket_close(fd);
+ goto error;
+ }
+
+ lan_ad_client_fd = fd;
+ freeaddrinfo_retro(addr);
+ return true;
+
+error:
+ if (addr)
+ freeaddrinfo_retro(addr);
+ RARCH_ERR("Failed to initialize netplay advertisement client socket.\n");
+ return false;
+}
+
+/** Deinitialize and free Netplay discovery */
+void deinit_netplay_discovery(void)
+{
+ if (lan_ad_client_fd >= 0)
+ {
+ socket_close(lan_ad_client_fd);
+ lan_ad_client_fd = -1;
+ }
+}
+
+/** Discovery control */
+bool netplay_discovery_driver_ctl(enum rarch_netplay_discovery_ctl_state state, void *data)
+{
+ char port_str[6];
+
+ if (lan_ad_client_fd < 0)
+ return false;
+
+ switch (state)
+ {
+ case RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY:
+ {
+ struct addrinfo hints = {0}, *addr;
+
+ /* Get the broadcast address (IPv4 only for now) */
+ snprintf(port_str, 6, "%hu", RARCH_DEFAULT_PORT);
+ if (getaddrinfo_retro("255.255.255.255", port_str, &hints, &addr) < 0)
+ return false;
+
+ /* Put together the request */
+ memcpy((void *) &ad_packet_buffer, "RANQ", 4);
+ ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION);
+
+ /* And send it off */
+ sendto(lan_ad_client_fd, &ad_packet_buffer, 2*sizeof(uint32_t), 0,
+ addr->ai_addr, addr->ai_addrlen);
+ break;
+ }
+
+ case RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES:
+ return netplay_lan_ad_client();
+
+ case RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES:
+ discovered_hosts.size = 0;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool init_lan_ad_server_socket(netplay_t *netplay, uint16_t port)
+{
+ struct addrinfo *addr = NULL;
+ int fd = socket_init((void **) &addr, port, NULL, SOCKET_TYPE_DATAGRAM);
+
+ if (fd < 0)
+ goto error;
+
+ if (!socket_bind(fd, (void*)addr))
+ {
+ socket_close(fd);
+ goto error;
+ }
+
+ lan_ad_server_fd = fd;
+ freeaddrinfo_retro(addr);
+
+ return true;
+
+error:
+ if (addr)
+ freeaddrinfo_retro(addr);
+ RARCH_ERR("Failed to initialize netplay advertisement socket.\n");
+ return false;
+}
+
+bool netplay_lan_ad_server(netplay_t *netplay)
+{
+ fd_set fds;
+ struct timeval tmp_tv = {0};
+ struct sockaddr their_addr;
+ socklen_t addr_size;
+ rarch_system_info_t *info = NULL;
+ size_t bufloc;
+
+ if (lan_ad_server_fd < 0 && !init_lan_ad_server_socket(netplay, RARCH_DEFAULT_PORT))
+ return false;
+
+ /* Check for any ad queries */
+ while (1)
+ {
+ FD_ZERO(&fds);
+ FD_SET(lan_ad_server_fd, &fds);
+ if (socket_select(lan_ad_server_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
+ break;
+ if (!FD_ISSET(lan_ad_server_fd, &fds))
+ break;
+
+ /* Somebody queried, so check that it's valid */
+ addr_size = sizeof(their_addr);
+ if (recvfrom(lan_ad_server_fd, (char*)&ad_packet_buffer,
+ sizeof(struct ad_packet), 0, &their_addr, &addr_size) >=
+ (ssize_t) (2*sizeof(uint32_t)))
+ {
+ /* Make sure it's a valid query */
+ if (memcmp((void *) &ad_packet_buffer, "RANQ", 4))
+ continue;
+
+ /* For this version */
+ if (ntohl(ad_packet_buffer.protocol_version) !=
+ NETPLAY_PROTOCOL_VERSION)
+ continue;
+
+ runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info);
+
+ /* Now build our response */
+ memset(&ad_packet_buffer, 0, sizeof(struct ad_packet));
+ memcpy(&ad_packet_buffer, "RANS", 4);
+ ad_packet_buffer.protocol_version =
+ htonl(NETPLAY_PROTOCOL_VERSION);
+ ad_packet_buffer.port = htonl(netplay->tcp_port);
+ strncpy(ad_packet_buffer.retroarch_version, PACKAGE_VERSION,
+ NETPLAY_HOST_STR_LEN);
+ strncpy(ad_packet_buffer.nick, netplay->nick, NETPLAY_HOST_STR_LEN);
+ if (info)
+ {
+ strncpy(ad_packet_buffer.core, info->info.library_name,
+ NETPLAY_HOST_STR_LEN);
+ strncpy(ad_packet_buffer.core_version, info->info.library_version,
+ NETPLAY_HOST_STR_LEN);
+ }
+
+ /* And send it */
+ sendto(lan_ad_server_fd, (const char*)&ad_packet_buffer,
+ sizeof(struct ad_packet), 0, &their_addr, addr_size);
+ }
+ }
+
+ return true;
+}
+
+bool netplay_lan_ad_client(void)
+{
+ fd_set fds;
+ struct timeval tmp_tv = {0};
+ struct sockaddr their_addr;
+ socklen_t addr_size;
+ rarch_system_info_t *info = NULL;
+ size_t bufloc;
+
+ if (lan_ad_client_fd < 0)
+ return false;
+
+ /* Check for any ad queries */
+ while (1)
+ {
+ FD_ZERO(&fds);
+ FD_SET(lan_ad_client_fd, &fds);
+ if (socket_select(lan_ad_client_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
+ break;
+ if (!FD_ISSET(lan_ad_client_fd, &fds))
+ break;
+
+ /* Somebody queried, so check that it's valid */
+ addr_size = sizeof(their_addr);
+ if (recvfrom(lan_ad_client_fd, (char*)&ad_packet_buffer,
+ sizeof(struct ad_packet), 0, &their_addr, &addr_size) >=
+ sizeof(struct ad_packet))
+ {
+ struct netplay_host *host;
+
+ /* Make sure it's a valid response */
+ if (memcmp((void *) &ad_packet_buffer, "RANS", 4))
+ continue;
+
+ /* For this version */
+ if (ntohl(ad_packet_buffer.protocol_version) != NETPLAY_PROTOCOL_VERSION)
+ continue;
+
+ /* Allocate space for it */
+ if (discovered_hosts.size >= discovered_hosts_allocated)
+ {
+ size_t allocated = discovered_hosts_allocated;
+ struct netplay_host *new_hosts;
+
+ if (allocated == 0) allocated = 2;
+ else allocated *= 2;
+
+ if (discovered_hosts.hosts)
+ new_hosts = realloc(discovered_hosts.hosts, allocated * sizeof(struct netplay_host));
+ else
+ /* Should be equivalent to realloc, but I don't trust screwy libcs */
+ new_hosts = malloc(allocated * sizeof(struct netplay_host));
+
+ if (!new_hosts)
+ return false;
+
+ discovered_hosts.hosts = new_hosts;
+ discovered_hosts_allocated = allocated;
+ }
+
+ /* Get our host structure */
+ host = &discovered_hosts.hosts[discovered_hosts.size++];
+
+ /* Copy in the response */
+ memset(host, 0, sizeof(struct netplay_host));
+ host->addr = their_addr;
+ host->addrlen = addr_size;
+ strncpy(host->nick, ad_packet_buffer.nick, NETPLAY_HOST_STR_LEN);
+ strncpy(host->core, ad_packet_buffer.core, NETPLAY_HOST_STR_LEN);
+ strncpy(host->core_version, ad_packet_buffer.core_version,
+ NETPLAY_HOST_STR_LEN);
+ strncpy(host->content, ad_packet_buffer.content,
+ NETPLAY_HOST_STR_LEN);
+ host->nick[NETPLAY_HOST_STR_LEN-1] =
+ host->core[NETPLAY_HOST_STR_LEN-1] =
+ host->core_version[NETPLAY_HOST_STR_LEN-1] =
+ host->content[NETPLAY_HOST_STR_LEN-1] = '\0';
+ }
+ }
+
+ return true;
+}
diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h
index b24c8cccdf..3b72e52160 100644
--- a/network/netplay/netplay_private.h
+++ b/network/netplay/netplay_private.h
@@ -258,6 +258,8 @@ bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta);
bool netplay_cmd_request_savestate(netplay_t *netplay);
-bool netplay_ad_server(netplay_t *netplay, int ad_fd);
+/* DISCOVERY: */
+
+bool netplay_lan_ad_server(netplay_t *netplay);
#endif