Allow sending UDP commands from RetroArch.

This commit is contained in:
Themaister 2012-06-01 15:15:06 +02:00
parent 02c77d3685
commit a0ec6da2a8
4 changed files with 142 additions and 3 deletions

View File

@ -177,6 +177,16 @@ Clients can connect and disconnect at any time.
Clients thus cannot interact as player 2. Clients thus cannot interact as player 2.
For spectating mode to work, both host and clients will need to use this flag. 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 .TP
\fB--nick NICK\fR \fB--nick NICK\fR
Pick a nickname for use with netplay. Pick a nickname for use with netplay.

View File

@ -17,9 +17,13 @@
#include "network_cmd.h" #include "network_cmd.h"
#include "driver.h" #include "driver.h"
#include "general.h" #include "general.h"
#include "compat/strl.h"
#include "compat/posix_string.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#define DEFAULT_NETWORK_CMD_PORT 55355
struct network_cmd struct network_cmd
{ {
int fd; int fd;
@ -42,6 +46,8 @@ network_cmd_t *network_cmd_new(uint16_t port)
if (!handle) if (!handle)
return NULL; return NULL;
RARCH_LOG("Bringing up command interface on port %hu.\n", (unsigned short)port);
handle->fd = -1; handle->fd = -1;
struct addrinfo hints, *res = NULL; struct addrinfo hints, *res = NULL;
@ -76,7 +82,6 @@ network_cmd_t *network_cmd_new(uint16_t port)
} }
freeaddrinfo(res); freeaddrinfo(res);
return handle; return handle;
error: 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;
}

View File

@ -28,5 +28,7 @@ void network_cmd_pre_frame(network_cmd_t *handle);
void network_cmd_set(network_cmd_t *handle, unsigned id); void network_cmd_set(network_cmd_t *handle, unsigned id);
bool network_cmd_get(network_cmd_t *handle, unsigned id); bool network_cmd_get(network_cmd_t *handle, unsigned id);
bool network_cmd_send(const char *cmd);
#endif #endif

View File

@ -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\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."); puts("\t--nick: Picks a nickname for use with netplay. Not mandatory.");
#endif #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 #ifdef HAVE_FFMPEG
puts("\t-r/--record: Path to record video file.\n\t\tUsing .mkv extension is recommended."); 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' }, { "port", 1, &val, 'p' },
{ "spectate", 0, &val, 'S' }, { "spectate", 0, &val, 'S' },
{ "nick", 1, &val, 'N' }, { "nick", 1, &val, 'N' },
#endif
#ifdef HAVE_NETWORK_CMD
{ "command", 1, &val, 'c' },
#endif #endif
{ "ups", 1, NULL, 'U' }, { "ups", 1, NULL, 'U' },
{ "bps", 1, &val, 'B' }, { "bps", 1, &val, 'B' },
@ -892,8 +899,6 @@ static void parse_input(int argc, char *argv[])
case 'F': case 'F':
g_extern.netplay_sync_frames = strtol(optarg, NULL, 0); g_extern.netplay_sync_frames = strtol(optarg, NULL, 0);
if (g_extern.netplay_sync_frames > 16)
g_extern.netplay_sync_frames = 16;
break; break;
#endif #endif
@ -929,6 +934,15 @@ static void parse_input(int argc, char *argv[])
break; break;
#endif #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': case 'B':
strlcpy(g_extern.bps_name, optarg, sizeof(g_extern.bps_name)); strlcpy(g_extern.bps_name, optarg, sizeof(g_extern.bps_name));
g_extern.bps_pref = true; g_extern.bps_pref = true;