From a0ec6da2a824b211a5cca985b5eb4a3aef8e753b Mon Sep 17 00:00:00 2001 From: Themaister Date: Fri, 1 Jun 2012 15:15:06 +0200 Subject: [PATCH] Allow sending UDP commands from RetroArch. --- docs/retroarch.1 | 10 +++++ network_cmd.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++- network_cmd.h | 2 + retroarch.c | 18 +++++++- 4 files changed, 142 insertions(+), 3 deletions(-) diff --git a/docs/retroarch.1 b/docs/retroarch.1 index 076944ed19..13192b6973 100644 --- a/docs/retroarch.1 +++ b/docs/retroarch.1 @@ -177,6 +177,16 @@ Clients can connect and disconnect at any time. Clients thus cannot interact as player 2. For spectating mode to work, both host and clients will need to use this flag. +.TP +\fB--command CMD\fR +Sends a command over UDP to an already running RetroArch application, and exit. +The command is formatted as "COMMAND:HOST:PORT". +HOST and PORT are both optional. "COMMAND:HOST" will set PORT to +"network_cmd_port" default setting. +If only "COMMAND" is used, HOST and PORT will be assumed to be "localhost" and "network_cmd_port" respectively. + +The available commands are listed if "COMMAND" is invalid. + .TP \fB--nick NICK\fR Pick a nickname for use with netplay. diff --git a/network_cmd.c b/network_cmd.c index 258cd31a41..10d745875d 100644 --- a/network_cmd.c +++ b/network_cmd.c @@ -17,9 +17,13 @@ #include "network_cmd.h" #include "driver.h" #include "general.h" +#include "compat/strl.h" +#include "compat/posix_string.h" #include #include +#define DEFAULT_NETWORK_CMD_PORT 55355 + struct network_cmd { int fd; @@ -42,6 +46,8 @@ network_cmd_t *network_cmd_new(uint16_t port) if (!handle) return NULL; + RARCH_LOG("Bringing up command interface on port %hu.\n", (unsigned short)port); + handle->fd = -1; struct addrinfo hints, *res = NULL; @@ -76,7 +82,6 @@ network_cmd_t *network_cmd_new(uint16_t port) } freeaddrinfo(res); - return handle; error: @@ -190,3 +195,111 @@ void network_cmd_pre_frame(network_cmd_t *handle) } } +static bool send_udp_packet(const char *host, uint16_t port, const char *msg) +{ + struct addrinfo hints, *res = NULL; + memset(&hints, 0, sizeof(hints)); +#if defined(_WIN32) || defined(HAVE_SOCKET_LEGACY) + hints.ai_family = AF_INET; +#else + hints.ai_family = AF_UNSPEC; +#endif + hints.ai_socktype = SOCK_DGRAM; + + int fd = -1; + bool ret = true; + char port_buf[16]; + + snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port); + if (getaddrinfo(host, port_buf, &hints, &res) < 0) + return false; + + // Send to all possible targets. + // "localhost" might resolve to several different IPs. + const struct addrinfo *tmp = res; + while (tmp) + { + fd = socket(tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol); + if (fd < 0) + { + ret = false; + goto end; + } + + ssize_t len = strlen(msg); + ssize_t ret = sendto(fd, msg, len, 0, tmp->ai_addr, tmp->ai_addrlen); + if (ret < len) + { + ret = false; + goto end; + } + + close(fd); + fd = -1; + tmp = tmp->ai_next; + } + +end: + freeaddrinfo(res); + if (fd >= 0) + close(fd); + return ret; +} + +static bool verify_command(const char *cmd) +{ + for (unsigned i = 0; i < sizeof(map) / sizeof(map[0]); i++) + { + if (strcmp(map[i].str, cmd) == 0) + return true; + } + + RARCH_ERR("Command \"%s\" is not recognized by RetroArch.\n", cmd); + RARCH_ERR("\tValid commands:\n"); + for (unsigned i = 0; i < sizeof(map) / sizeof(map[0]); i++) + RARCH_ERR("\t\t%s\n", map[i].str); + + return false; +} + +bool network_cmd_send(const char *cmd_) +{ + char *command = strdup(cmd_); + if (!command) + return false; + + bool old_verbose = g_extern.verbose; + g_extern.verbose = true; + + const char *cmd = NULL; + const char *host = NULL; + const char *port_ = NULL; + uint16_t port = DEFAULT_NETWORK_CMD_PORT; + + cmd = strtok(command, ":"); + if (cmd) + host = strtok(NULL, ":"); + if (host) + port_ = strtok(NULL, ":"); + + if (!host) + { +#ifdef _WIN32 + host = "127.0.0.1"; +#else + host = "localhost"; +#endif + } + + if (port_) + port = strtoul(port_, NULL, 0); + + RARCH_LOG("Sending command: \"%s\" to %s:%hu\n", cmd, host, (unsigned short)port); + + bool ret = verify_command(cmd) && send_udp_packet(host, port, cmd); + free(command); + + g_extern.verbose = old_verbose; + return ret; +} + diff --git a/network_cmd.h b/network_cmd.h index 696f5c5a2d..267bcdeaf6 100644 --- a/network_cmd.h +++ b/network_cmd.h @@ -28,5 +28,7 @@ void network_cmd_pre_frame(network_cmd_t *handle); void network_cmd_set(network_cmd_t *handle, unsigned id); bool network_cmd_get(network_cmd_t *handle, unsigned id); +bool network_cmd_send(const char *cmd); + #endif diff --git a/retroarch.c b/retroarch.c index 0589e19257..644e12515a 100644 --- a/retroarch.c +++ b/retroarch.c @@ -539,6 +539,10 @@ static void print_help(void) puts("\t\tHowever, the client will not be able to play. Multiple clients can connect to the host."); puts("\t--nick: Picks a nickname for use with netplay. Not mandatory."); #endif +#ifdef HAVE_NETWORK_CMD + puts("\t--command: Sends a command over UDP to an already running RetroArch process."); + puts("\t\tAvailable commands are listed if command is invalid."); +#endif #ifdef HAVE_FFMPEG puts("\t-r/--record: Path to record video file.\n\t\tUsing .mkv extension is recommended."); @@ -695,6 +699,9 @@ static void parse_input(int argc, char *argv[]) { "port", 1, &val, 'p' }, { "spectate", 0, &val, 'S' }, { "nick", 1, &val, 'N' }, +#endif +#ifdef HAVE_NETWORK_CMD + { "command", 1, &val, 'c' }, #endif { "ups", 1, NULL, 'U' }, { "bps", 1, &val, 'B' }, @@ -892,8 +899,6 @@ static void parse_input(int argc, char *argv[]) case 'F': g_extern.netplay_sync_frames = strtol(optarg, NULL, 0); - if (g_extern.netplay_sync_frames > 16) - g_extern.netplay_sync_frames = 16; break; #endif @@ -929,6 +934,15 @@ static void parse_input(int argc, char *argv[]) break; #endif +#ifdef HAVE_NETWORK_CMD + case 'c': + if (network_cmd_send(optarg)) + exit(0); + else + rarch_fail(1, "network_cmd_send()"); + break; +#endif + case 'B': strlcpy(g_extern.bps_name, optarg, sizeof(g_extern.bps_name)); g_extern.bps_pref = true;