mirror of
https://github.com/libretro/RetroArch
synced 2025-02-06 18:40:49 +00:00
Added Netplay discovery code (not yet in menu)
This commit is contained in:
parent
addff325d0
commit
4c18cec752
@ -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)
|
||||
|
@ -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 &&
|
||||
|
@ -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;
|
||||
}
|
||||
|
321
network/netplay/netplay_discovery.c
Normal file
321
network/netplay/netplay_discovery.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <net/net_compat.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user