mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-24 00:20:01 +00:00
Allow sending UDP commands from RetroArch.
This commit is contained in:
parent
02c77d3685
commit
a0ec6da2a8
@ -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.
|
||||||
|
115
network_cmd.c
115
network_cmd.c
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
18
retroarch.c
18
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\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;
|
||||||
|
Loading…
Reference in New Issue
Block a user