diff --git a/Makefile.common b/Makefile.common index 8a04851ba1..f1e956cbcd 100644 --- a/Makefile.common +++ b/Makefile.common @@ -233,6 +233,7 @@ endif OBJ += frontend/frontend_driver.o \ retroarch.o \ + command.o \ msg_hash.o \ intl/msg_hash_us.o \ $(LIBRETRO_COMM_DIR)/queues/task_queue.o \ diff --git a/command.c b/command.c new file mode 100644 index 0000000000..8f7b9bdca9 --- /dev/null +++ b/command.c @@ -0,0 +1,514 @@ + +#include +#include +#include +#ifdef HAVE_NETWORKING +#include +#include +#endif +#include +#include + +#include "verbosity.h" +#include "command.h" + +#define CMD_BUF_SIZE 4096 + +#if defined(HAVE_COMMAND) + +/* Generic command parse utilities */ + +static bool command_get_arg(const char *tok, + const char **arg, unsigned *index) +{ + unsigned i; + + for (i = 0; i < ARRAY_SIZE(map); i++) + { + if (string_is_equal(tok, map[i].str)) + { + if (arg) + *arg = NULL; + + if (index) + *index = i; + + return true; + } + } + + for (i = 0; i < ARRAY_SIZE(action_map); i++) + { + const char *str = strstr(tok, action_map[i].str); + if (str == tok) + { + const char *argument = str + strlen(action_map[i].str); + if (*argument != ' ' && *argument != '\0') + return false; + + if (arg) + *arg = argument + 1; + + if (index) + *index = i; + + return true; + } + } + + return false; +} + +static void command_parse_sub_msg(command_t *handle, const char *tok) +{ + const char *arg = NULL; + unsigned index = 0; + + if (command_get_arg(tok, &arg, &index)) + { + if (arg) + { + if (!action_map[index].action(handle, arg)) + RARCH_ERR("Command \"%s\" failed.\n", arg); + } + else + handle->state[map[index].id] = true; + } + else + RARCH_WARN("%s \"%s\" %s.\n", + msg_hash_to_str(MSG_UNRECOGNIZED_COMMAND), + tok, + msg_hash_to_str(MSG_RECEIVED)); +} + +static void command_parse_msg( + command_t *handle, char *buf) +{ + char *save = NULL; + const char *tok = strtok_r(buf, "\n", &save); + + while (tok) + { + command_parse_sub_msg(handle, tok); + tok = strtok_r(NULL, "\n", &save); + } +} + +#if defined(HAVE_NETWORK_CMD) + +typedef struct +{ + /* Network socket FD */ + int net_fd; + /* Source address for the command received */ + struct sockaddr_storage cmd_source; + /* Size of the previous structure in use */ + socklen_t cmd_source_len; +} command_network_t; + +static void network_command_reply( + command_t *cmd, + const char * data, size_t len) +{ + command_network_t *netcmd = (command_network_t*)cmd->userptr; + /* Respond (fire and forget since it's UDP) */ + sendto(netcmd->net_fd, data, len, 0, + (struct sockaddr*)&netcmd->cmd_source, netcmd->cmd_source_len); +} + +static void network_command_free(command_t *handle) +{ + command_network_t *netcmd = (command_network_t*)handle->userptr; + + if (netcmd->net_fd >= 0) + socket_close(netcmd->net_fd); + + free(netcmd); + free(handle); +} + +static void command_network_poll(command_t *handle) +{ + fd_set fds; + struct timeval tmp_tv = {0}; + command_network_t *netcmd = (command_network_t*)handle->userptr; + + if (netcmd->net_fd < 0) + return; + + FD_ZERO(&fds); + FD_SET(netcmd->net_fd, &fds); + + if (socket_select(netcmd->net_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + return; + + if (!FD_ISSET(netcmd->net_fd, &fds)) + return; + + for (;;) + { + ssize_t ret; + char buf[1024]; + + buf[0] = '\0'; + ret = recvfrom(netcmd->net_fd, buf, sizeof(buf) - 1, 0, + (struct sockaddr*)&netcmd->cmd_source, + &netcmd->cmd_source_len); + + if (ret <= 0) + break; + + buf[ret] = '\0'; + + command_parse_msg(handle, buf); + } +} + +command_t* command_network_new(uint16_t port) +{ + struct addrinfo *res = NULL; + command_t *cmd = (command_t*)calloc(1, sizeof(command_t)); + command_network_t *netcmd = (command_network_t*)calloc( + 1, sizeof(command_network_t)); + int fd = socket_init((void**)&res, port, NULL, SOCKET_TYPE_DATAGRAM); + + RARCH_LOG("%s %hu.\n", + msg_hash_to_str(MSG_BRINGING_UP_COMMAND_INTERFACE_ON_PORT), + (unsigned short)port); + + if (fd < 0) + goto error; + + netcmd->net_fd = fd; + cmd->userptr = netcmd; + cmd->poll = command_network_poll; + cmd->replier = network_command_reply; + cmd->destroy = network_command_free; + + if (!socket_nonblock(netcmd->net_fd)) + goto error; + + if (!socket_bind(netcmd->net_fd, (void*)res)) + { + RARCH_ERR("%s.\n", + msg_hash_to_str(MSG_FAILED_TO_BIND_SOCKET)); + goto error; + } + + freeaddrinfo_retro(res); + return cmd; + +error: + if (res) + freeaddrinfo_retro(res); + free(netcmd); + free(cmd); + return NULL; +} +#endif + + +#if defined(HAVE_STDIN_CMD) +typedef struct +{ + /* Buffer and pointer for stdin reads */ + size_t stdin_buf_ptr; + char stdin_buf[CMD_BUF_SIZE]; +} command_stdin_t; + +static void stdin_command_reply( + command_t *cmd, + const char * data, size_t len) +{ + /* Just write to stdout! */ + fwrite(data, 1, len, stdout); +} + +static void stdin_command_free(command_t *handle) +{ + free(handle->userptr); + free(handle); +} + +static void command_stdin_poll(command_t *handle) { + ptrdiff_t msg_len; + char *last_newline = NULL; + command_stdin_t *stdincmd = (command_stdin_t*)handle->userptr; + ssize_t ret = read_stdin( + stdincmd->stdin_buf + stdincmd->stdin_buf_ptr, + CMD_BUF_SIZE - stdincmd->stdin_buf_ptr - 1); + + if (ret == 0) + return; + + stdincmd->stdin_buf_ptr += ret; + stdincmd->stdin_buf[stdincmd->stdin_buf_ptr] = '\0'; + + last_newline = strrchr(stdincmd->stdin_buf, '\n'); + + if (!last_newline) + { + /* We're receiving bogus data in pipe + * (no terminating newline), flush out the buffer. */ + if (stdincmd->stdin_buf_ptr + 1 >= CMD_BUF_SIZE) + { + stdincmd->stdin_buf_ptr = 0; + stdincmd->stdin_buf[0] = '\0'; + } + + return; + } + + *last_newline++ = '\0'; + msg_len = last_newline - stdincmd->stdin_buf; + + command_parse_msg(handle, stdincmd->stdin_buf); + + memmove(stdincmd->stdin_buf, last_newline, + stdincmd->stdin_buf_ptr - msg_len); + stdincmd->stdin_buf_ptr -= msg_len; +} + +command_t* command_stdin_new(void) +{ + command_t *cmd; + command_stdin_t *stdincmd; + +#ifndef _WIN32 +#ifdef HAVE_NETWORKING + if (!socket_nonblock(STDIN_FILENO)) + return NULL; +#endif +#endif + + cmd = (command_t*)calloc(1, sizeof(command_t)); + stdincmd = (command_stdin_t*)calloc(1, sizeof(command_stdin_t)); + cmd->userptr = stdincmd; + cmd->poll = command_stdin_poll; + cmd->replier = stdin_command_reply; + cmd->destroy = stdin_command_free; + + return cmd; +} +#endif + +#if defined(HAVE_LAKKA) +#include +#define MAX_USER_CONNECTIONS 4 +typedef struct +{ + /* File descriptor for the domain socket */ + int sfd; + /* Client sockets */ + int userfd[MAX_USER_CONNECTIONS]; + /* Last received user socket */ + int last_fd; +} command_uds_t; + +static void uds_command_reply( + command_t *cmd, + const char * data, size_t len) +{ + command_uds_t *subcmd = (command_uds_t*)cmd->userptr; + write(subcmd->last_fd, data, len); +} + +static void uds_command_free(command_t *handle) +{ + int i; + command_uds_t *udscmd = (command_uds_t*)handle->userptr; + + for (i = 0; i < MAX_USER_CONNECTIONS; i++) + if (udscmd->userfd[i] >= 0) + socket_close(udscmd->userfd[i]); + socket_close(udscmd->sfd); + + free(handle->userptr); + free(handle); +} + +static void command_uds_poll(command_t *handle) { + int i; + command_uds_t *udscmd = (command_uds_t*)handle->userptr; + int maxfd = udscmd->sfd; + fd_set fds; + struct timeval tmp_tv = {0}; + + if (udscmd->sfd < 0) + return; + + FD_ZERO(&fds); + FD_SET(udscmd->sfd, &fds); + for (i = 0; i < MAX_USER_CONNECTIONS; i++) + { + if (udscmd->userfd[i] >= 0) + { + maxfd = MAX(udscmd->userfd[i], maxfd); + FD_SET(udscmd->userfd[i], &fds); + } + } + + if (socket_select(maxfd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + return; + + /* Read data from clients and process commands */ + for (i = 0; i < MAX_USER_CONNECTIONS; i++) + { + if (udscmd->userfd[i] >= 0 && FD_ISSET(udscmd->userfd[i], &fds)) + { + while (1) + { + char buf[2048]; + ssize_t ret = recv(udscmd->userfd[i], buf, sizeof(buf) - 1, 0); + + if (ret < 0) + break; /* no more data */ + if (!ret) + { + socket_close(udscmd->userfd[i]); + udscmd->userfd[i] = -1; + break; + } + + buf[ret] = 0; + udscmd->last_fd = udscmd->userfd[i]; + command_parse_msg(handle, buf); + } + } + } + + if (FD_ISSET(udscmd->sfd, &fds)) + { + /* Accepts new connections from clients */ + int cfd = accept(udscmd->sfd, NULL, NULL); + if (cfd >= 0) { + if (!socket_nonblock(cfd)) + socket_close(cfd); + else { + for (i = 0; i < MAX_USER_CONNECTIONS; i++) + if (udscmd->userfd[i] < 0) + { + udscmd->userfd[i] = cfd; + break; + } + } + } + } +} + +command_t* command_uds_new(void) +{ + int i; + command_t *cmd; + command_uds_t *subcmd; + struct sockaddr_un addr; + const char *sp = "retroarch/cmd"; + socklen_t addrsz = offsetof(struct sockaddr_un, sun_path) + strlen(sp) + 1; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return NULL; + + /* use an abstract socket for simplicity */ + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(&addr.sun_path[1], sp); + + if (bind(fd, (struct sockaddr*)&addr, addrsz) < 0 || + listen(fd, MAX_USER_CONNECTIONS) < 0) { + socket_close(fd); + return NULL; + } + + if (!socket_nonblock(fd)) + { + socket_close(fd); + return NULL; + } + + cmd = (command_t*)calloc(1, sizeof(command_t)); + subcmd = (command_uds_t*)calloc(1, sizeof(command_uds_t)); + subcmd->sfd = fd; + subcmd->last_fd = -1; + for (i = 0; i < MAX_USER_CONNECTIONS; i++) + subcmd->userfd[i] = -1; + + cmd->userptr = subcmd; + cmd->poll = command_uds_poll; + cmd->replier = uds_command_reply; + cmd->destroy = uds_command_free; + + return cmd; +} +#endif + + +/* Routines used to invoke retroarch command ... */ + +#ifdef HAVE_NETWORK_CMD +static bool command_verify(const char *cmd) +{ + unsigned i; + + if (command_get_arg(cmd, NULL, NULL)) + return true; + + RARCH_ERR("Command \"%s\" is not recognized by the program.\n", cmd); + RARCH_ERR("\tValid commands:\n"); + for (i = 0; i < ARRAY_SIZE(map); i++) + RARCH_ERR("\t\t%s\n", map[i].str); + + for (i = 0; i < ARRAY_SIZE(action_map); i++) + RARCH_ERR("\t\t%s %s\n", action_map[i].str, action_map[i].arg_desc); + + return false; +} + +bool command_network_send(const char *cmd_) +{ + char *command = NULL; + char *save = NULL; + const char *cmd = NULL; + + if (!network_init()) + return false; + + if (!(command = strdup(cmd_))) + return false; + + cmd = strtok_r(command, ";", &save); + if (cmd) + { + uint16_t port = DEFAULT_NETWORK_CMD_PORT; + const char *port_ = NULL; + const char *host = strtok_r(NULL, ";", &save); + if (host) + port_ = strtok_r(NULL, ";", &save); + else + { +#ifdef _WIN32 + host = "127.0.0.1"; +#else + host = "localhost"; +#endif + } + + if (port_) + port = strtoul(port_, NULL, 0); + + RARCH_LOG("%s: \"%s\" to %s:%hu\n", + msg_hash_to_str(MSG_SENDING_COMMAND), + cmd, host, (unsigned short)port); + + if (command_verify(cmd) && udp_send_packet(host, port, cmd)) + { + free(command); + return true; + } + } + + free(command); + return false; +} +#endif + +#endif diff --git a/command.h b/command.h index ec30043341..90d28f2871 100644 --- a/command.h +++ b/command.h @@ -23,12 +23,46 @@ #include #include +#include "retroarch.h" +#include "input/input_defines.h" + #ifdef HAVE_CONFIG_H #include "config.h" #endif RETRO_BEGIN_DECLS +#define MAX_CMD_DRIVERS 3 +#define DEFAULT_NETWORK_CMD_PORT 55355 + +struct cmd_map +{ + const char *str; + unsigned id; +}; + +struct command_handler; + +typedef void (*command_poller_t)(struct command_handler *cmd); +typedef void (*command_replier_t)(struct command_handler *cmd, const char * data, size_t len); +typedef void (*command_destructor_t)(struct command_handler *cmd); + +struct command_handler +{ + /* Interface to poll the driver */ + command_poller_t poll; + /* Interface to reply */ + command_replier_t replier; + /* Interface to delete the underlying command */ + command_destructor_t destroy; + /* Underlying command storage */ + void *userptr; + /* State received */ + bool state[RARCH_BIND_LIST_END]; +}; + +typedef struct command_handler command_t; + enum event_command { CMD_EVENT_NONE = 0, @@ -209,14 +243,21 @@ enum event_command CMD_EVENT_CONTROLLER_INIT }; -typedef struct command command_t; - typedef struct command_handle { command_t *handle; unsigned id; } command_handle_t; +enum cmd_source_t +{ + CMD_NONE = 0, + CMD_STDIN, + CMD_NETWORK +}; + +struct rarch_state; + /** * command_event: * @cmd : Command index. @@ -227,6 +268,107 @@ typedef struct command_handle **/ bool command_event(enum event_command action, void *data); +/* Constructors for the supported drivers */ +command_t* command_network_new(uint16_t port); +command_t* command_stdin_new(void); +command_t* command_uds_new(void); + +bool command_network_send(const char *cmd_); + +/* These forward declarations need to be declared before + * the global state is declared */ +#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) +bool command_set_shader(command_t *cmd, const char *arg); +#endif +#if defined(HAVE_COMMAND) +bool command_version(command_t *cmd, const char* arg); +bool command_get_status(command_t *cmd, const char* arg); +bool command_get_config_param(command_t *cmd, const char* arg); +bool command_show_osd_msg(command_t *cmd, const char* arg); +#ifdef HAVE_CHEEVOS +bool command_read_ram(command_t *cmd, const char *arg); +bool command_write_ram(command_t *cmd, const char *arg); +#endif +bool command_read_memory(command_t *cmd, const char *arg); +bool command_write_memory(command_t *cmd, const char *arg); + +struct cmd_action_map +{ + const char *str; + bool (*action)(command_t* cmd, const char *arg); + const char *arg_desc; +}; + +static const struct cmd_action_map action_map[] = { +#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) + { "SET_SHADER", command_set_shader, "" }, +#endif + { "VERSION", command_version, "No argument"}, + { "GET_STATUS", command_get_status, "No argument" }, + { "GET_CONFIG_PARAM", command_get_config_param, "" }, + { "SHOW_MSG", command_show_osd_msg, "No argument" }, +#if defined(HAVE_CHEEVOS) + /* These functions use achievement addresses and only work if a game with achievements is + * loaded. READ_CORE_MEMORY and WRITE_CORE_MEMORY are preferred and use system addresses. */ + { "READ_CORE_RAM", command_read_ram, "
" }, + { "WRITE_CORE_RAM", command_write_ram, "
..." }, +#endif + { "READ_CORE_MEMORY", command_read_memory, "
" }, + { "WRITE_CORE_MEMORY",command_write_memory, "
..." }, +}; + +static const struct cmd_map map[] = { + { "FAST_FORWARD", RARCH_FAST_FORWARD_KEY }, + { "FAST_FORWARD_HOLD", RARCH_FAST_FORWARD_HOLD_KEY }, + { "SLOWMOTION", RARCH_SLOWMOTION_KEY }, + { "SLOWMOTION_HOLD", RARCH_SLOWMOTION_HOLD_KEY }, + { "LOAD_STATE", RARCH_LOAD_STATE_KEY }, + { "SAVE_STATE", RARCH_SAVE_STATE_KEY }, + { "FULLSCREEN_TOGGLE", RARCH_FULLSCREEN_TOGGLE_KEY }, + { "CLOSE_CONTENT", RARCH_CLOSE_CONTENT_KEY }, + { "QUIT", RARCH_QUIT_KEY }, + { "STATE_SLOT_PLUS", RARCH_STATE_SLOT_PLUS }, + { "STATE_SLOT_MINUS", RARCH_STATE_SLOT_MINUS }, + { "REWIND", RARCH_REWIND }, + { "BSV_RECORD_TOGGLE", RARCH_BSV_RECORD_TOGGLE }, + { "PAUSE_TOGGLE", RARCH_PAUSE_TOGGLE }, + { "FRAMEADVANCE", RARCH_FRAMEADVANCE }, + { "RESET", RARCH_RESET }, + { "SHADER_NEXT", RARCH_SHADER_NEXT }, + { "SHADER_PREV", RARCH_SHADER_PREV }, + { "CHEAT_INDEX_PLUS", RARCH_CHEAT_INDEX_PLUS }, + { "CHEAT_INDEX_MINUS", RARCH_CHEAT_INDEX_MINUS }, + { "CHEAT_TOGGLE", RARCH_CHEAT_TOGGLE }, + { "SCREENSHOT", RARCH_SCREENSHOT }, + { "MUTE", RARCH_MUTE }, + { "OSK", RARCH_OSK }, + { "FPS_TOGGLE", RARCH_FPS_TOGGLE }, + { "SEND_DEBUG_INFO", RARCH_SEND_DEBUG_INFO }, + { "NETPLAY_HOST_TOGGLE", RARCH_NETPLAY_HOST_TOGGLE }, + { "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH }, + { "VOLUME_UP", RARCH_VOLUME_UP }, + { "VOLUME_DOWN", RARCH_VOLUME_DOWN }, + { "OVERLAY_NEXT", RARCH_OVERLAY_NEXT }, + { "DISK_EJECT_TOGGLE", RARCH_DISK_EJECT_TOGGLE }, + { "DISK_NEXT", RARCH_DISK_NEXT }, + { "DISK_PREV", RARCH_DISK_PREV }, + { "GRAB_MOUSE_TOGGLE", RARCH_GRAB_MOUSE_TOGGLE }, + { "UI_COMPANION_TOGGLE", RARCH_UI_COMPANION_TOGGLE }, + { "GAME_FOCUS_TOGGLE", RARCH_GAME_FOCUS_TOGGLE }, + { "MENU_TOGGLE", RARCH_MENU_TOGGLE }, + { "RECORDING_TOGGLE", RARCH_RECORDING_TOGGLE }, + { "STREAMING_TOGGLE", RARCH_STREAMING_TOGGLE }, + { "RUNAHEAD_TOGGLE", RARCH_RUNAHEAD_TOGGLE }, + { "MENU_UP", RETRO_DEVICE_ID_JOYPAD_UP }, + { "MENU_DOWN", RETRO_DEVICE_ID_JOYPAD_DOWN }, + { "MENU_LEFT", RETRO_DEVICE_ID_JOYPAD_LEFT }, + { "MENU_RIGHT", RETRO_DEVICE_ID_JOYPAD_RIGHT }, + { "MENU_A", RETRO_DEVICE_ID_JOYPAD_A }, + { "MENU_B", RETRO_DEVICE_ID_JOYPAD_B }, + { "AI_SERVICE", RARCH_AI_SERVICE }, +}; +#endif + RETRO_END_DECLS #endif diff --git a/griffin/griffin.c b/griffin/griffin.c index de62febf05..d958910f05 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1195,6 +1195,7 @@ GIT RETROARCH ============================================================ */ #include "../retroarch.c" +#include "../command.c" #include "../libretro-common/queues/task_queue.c" #include "../msg_hash.c" diff --git a/qb/config.params.sh b/qb/config.params.sh index e94a04d621..ded3cdd9b7 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -62,6 +62,7 @@ HAVE_NETWORKING=auto # Networking features (recommended) HAVE_NETWORKGAMEPAD=auto # Networked game pad (plus baked-in core) C89_NETWORKGAMEPAD=no HAVE_NETPLAYDISCOVERY=yes # Add netplay discovery (room creation, etc.) +HAVE_COMMAND=no # Network command interface, to remote control RA HAVE_MINIUPNPC=auto # Mini UPnP client library (for NAT traversal) HAVE_BUILTINMINIUPNPC=auto # Bake in Mini UPnP client library (for NAT traversal) C89_BUILTINMINIUPNPC=no diff --git a/retroarch.c b/retroarch.c index 619dcccb45..9d23ef2b64 100644 --- a/retroarch.c +++ b/retroarch.c @@ -65,7 +65,6 @@ #include #include #include -#include #include #include #include @@ -9777,7 +9776,7 @@ static void dir_check_shader(struct rarch_state *p_rarch, #if defined(HAVE_MENU) menu_driver_set_last_shader_preset_path(set_shader_path); #endif - command_set_shader(set_shader_path); + command_set_shader(NULL, set_shader_path); dir_list->shader_loaded = true; } #endif @@ -10304,51 +10303,19 @@ static void retroarch_autosave_deinit(struct rarch_state *p_rarch) /* COMMAND */ -#if defined(HAVE_COMMAND) -#if (defined(HAVE_STDIN_CMD) || defined(HAVE_NETWORK_CMD)) -static void command_reply( - struct rarch_state *p_rarch, - const char * data, size_t len) -{ - const enum cmd_source_t lastcmd_source = p_rarch->lastcmd_source; +#ifdef HAVE_COMMAND - switch (lastcmd_source) - { - case CMD_STDIN: -#ifdef HAVE_STDIN_CMD - fwrite(data, 1,len, stdout); -#endif - break; - case CMD_NETWORK: -#ifdef HAVE_NETWORK_CMD - sendto(p_rarch->lastcmd_net_fd, data, len, 0, - (struct sockaddr*)&p_rarch->lastcmd_net_source, - p_rarch->lastcmd_net_source_len); -#endif - break; - case CMD_NONE: - default: - break; - } -} -#endif - -static bool command_version(const char* arg) +bool command_version(command_t *cmd, const char* arg) { char reply[256] = {0}; -#if (defined(HAVE_STDIN_CMD) || defined(HAVE_NETWORK_CMD)) - struct rarch_state *p_rarch = &rarch_st; -#endif snprintf(reply, sizeof(reply), "%s\n", PACKAGE_VERSION); -#if (defined(HAVE_STDIN_CMD) || defined(HAVE_NETWORK_CMD)) - command_reply(p_rarch, reply, strlen(reply)); -#endif + cmd->replier(cmd, reply, strlen(reply)); return true; } -static bool command_get_status(const char* arg) +bool command_get_status(command_t *cmd, const char* arg) { char reply[4096] = {0}; bool contentless = false; @@ -10380,19 +10347,19 @@ static bool command_get_status(const char* arg) snprintf(reply, sizeof(reply), "GET_STATUS %s %s,%s,crc32=%x\n", status, system_id, content_name, content_crc32); } - command_reply(p_rarch, reply, strlen(reply)); + cmd->replier(cmd, reply, strlen(reply)); return true; } -static bool command_show_osd_msg(const char* arg) +bool command_show_osd_msg(command_t *cmd, const char* arg) { runloop_msg_queue_push(arg, 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); return true; } -static bool command_get_config_param(const char* arg) +bool command_get_config_param(command_t *cmd, const char* arg) { char reply[8192] = {0}; struct rarch_state *p_rarch = &rarch_st; @@ -10429,12 +10396,12 @@ static bool command_get_config_param(const char* arg) /* TODO: query any string */ snprintf(reply, sizeof(reply), "GET_CONFIG_PARAM %s %s\n", arg, value); - command_reply(p_rarch, reply, strlen(reply)); + cmd->replier(cmd, reply, strlen(reply)); return true; } #if defined(HAVE_CHEEVOS) -static bool command_read_ram(const char *arg) +bool command_read_ram(command_t *cmd, const char *arg) { unsigned i; char *reply = NULL; @@ -10444,7 +10411,6 @@ static bool command_read_ram(const char *arg) unsigned int alloc_size = 0; unsigned int addr = -1; unsigned int len = 0; - struct rarch_state *p_rarch = &rarch_st; if (sscanf(arg, "%x %u", &addr, &nbytes) != 2) return true; @@ -10467,12 +10433,12 @@ static bool command_read_ram(const char *arg) strlcpy(reply_at, " -1\n", sizeof(reply) - strlen(reply)); len = reply_at + STRLEN_CONST(" -1\n") - reply; } - command_reply(p_rarch, reply, len); + cmd->replier(cmd, reply, len); free(reply); return true; } -static bool command_write_ram(const char *arg) +bool command_write_ram(command_t *cmd, const char *arg) { unsigned int addr = (unsigned int)strtoul(arg, (char**)&arg, 16); uint8_t *data = (uint8_t *)rcheevos_patch_address(addr); @@ -10549,7 +10515,7 @@ static uint8_t* command_memory_get_pointer(unsigned address, unsigned int* max_b return NULL; } -static bool command_read_memory(const char *arg) +bool command_read_memory(command_t *cmd, const char *arg) { unsigned i; char* reply = NULL; @@ -10559,7 +10525,6 @@ static bool command_read_memory(const char *arg) unsigned int alloc_size = 0; unsigned int address = -1; unsigned int len = 0; - struct rarch_state *p_rarch = &rarch_st; unsigned int max_bytes = 0; if (sscanf(arg, "%x %u", &address, &nbytes) != 2) @@ -10586,16 +10551,15 @@ static bool command_read_memory(const char *arg) else len = strlen(reply); - command_reply(p_rarch, reply, len); + cmd->replier(cmd, reply, len); free(reply); return true; } -static bool command_write_memory(const char *arg) +bool command_write_memory(command_t *cmd, const char *arg) { unsigned int address = (unsigned int)strtoul(arg, (char**)&arg, 16); unsigned int max_bytes = 0; - struct rarch_state *p_rarch = &rarch_st; char reply[128] = ""; char *reply_at = reply + snprintf(reply, sizeof(reply) - 1, "WRITE_CORE_MEMORY %x", address); uint8_t *data = command_memory_get_pointer(address, &max_bytes, 1, reply_at, sizeof(reply) - strlen(reply) - 1); @@ -10622,335 +10586,9 @@ static bool command_write_memory(const char *arg) #endif } - command_reply(p_rarch, reply, strlen(reply)); + cmd->replier(cmd, reply, strlen(reply)); return true; } - -#ifdef HAVE_NETWORK_CMD -static bool command_get_arg(const char *tok, - const char **arg, unsigned *index) -{ - unsigned i; - - for (i = 0; i < ARRAY_SIZE(map); i++) - { - if (string_is_equal(tok, map[i].str)) - { - if (arg) - *arg = NULL; - - if (index) - *index = i; - - return true; - } - } - - for (i = 0; i < ARRAY_SIZE(action_map); i++) - { - const char *str = strstr(tok, action_map[i].str); - if (str == tok) - { - const char *argument = str + strlen(action_map[i].str); - if (*argument != ' ' && *argument != '\0') - return false; - - if (arg) - *arg = argument + 1; - - if (index) - *index = i; - - return true; - } - } - - return false; -} - -static bool command_network_init(command_t *handle, uint16_t port) -{ - struct addrinfo *res = NULL; - int fd = socket_init((void**)&res, port, - NULL, SOCKET_TYPE_DATAGRAM); - - RARCH_LOG("%s %hu.\n", - msg_hash_to_str(MSG_BRINGING_UP_COMMAND_INTERFACE_ON_PORT), - (unsigned short)port); - - if (fd < 0) - goto error; - - handle->net_fd = fd; - - if (!socket_nonblock(handle->net_fd)) - goto error; - - if (!socket_bind(handle->net_fd, (void*)res)) - { - RARCH_ERR("%s.\n", - msg_hash_to_str(MSG_FAILED_TO_BIND_SOCKET)); - goto error; - } - - freeaddrinfo_retro(res); - return true; - -error: - if (res) - freeaddrinfo_retro(res); - return false; -} - -static bool command_verify(const char *cmd) -{ - unsigned i; - - if (command_get_arg(cmd, NULL, NULL)) - return true; - - RARCH_ERR("Command \"%s\" is not recognized by the program.\n", cmd); - RARCH_ERR("\tValid commands:\n"); - for (i = 0; i < ARRAY_SIZE(map); i++) - RARCH_ERR("\t\t%s\n", map[i].str); - - for (i = 0; i < ARRAY_SIZE(action_map); i++) - RARCH_ERR("\t\t%s %s\n", action_map[i].str, action_map[i].arg_desc); - - return false; -} - -static bool command_network_send(const char *cmd_) -{ - char *command = NULL; - char *save = NULL; - const char *cmd = NULL; - - if (!network_init()) - return false; - - if (!(command = strdup(cmd_))) - return false; - - cmd = strtok_r(command, ";", &save); - if (cmd) - { - uint16_t port = DEFAULT_NETWORK_CMD_PORT; - const char *port_ = NULL; - const char *host = strtok_r(NULL, ";", &save); - if (host) - port_ = strtok_r(NULL, ";", &save); - else - { -#ifdef _WIN32 - host = "127.0.0.1"; -#else - host = "localhost"; -#endif - } - - if (port_) - port = strtoul(port_, NULL, 0); - - RARCH_LOG("%s: \"%s\" to %s:%hu\n", - msg_hash_to_str(MSG_SENDING_COMMAND), - cmd, host, (unsigned short)port); - - if (command_verify(cmd) && udp_send_packet(host, port, cmd)) - { - free(command); - return true; - } - } - - free(command); - return false; -} -#endif - -#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) && defined(HAVE_COMMAND) -static void command_parse_sub_msg(command_t *handle, const char *tok) -{ - const char *arg = NULL; - unsigned index = 0; - - if (command_get_arg(tok, &arg, &index)) - { - if (arg) - { - if (!action_map[index].action(arg)) - RARCH_ERR("Command \"%s\" failed.\n", arg); - } - else - handle->state[map[index].id] = true; - } - else - RARCH_WARN("%s \"%s\" %s.\n", - msg_hash_to_str(MSG_UNRECOGNIZED_COMMAND), - tok, - msg_hash_to_str(MSG_RECEIVED)); -} - -static void command_parse_msg( - struct rarch_state *p_rarch, - command_t *handle, - char *buf, enum cmd_source_t source) -{ - char *save = NULL; - const char *tok = strtok_r(buf, "\n", &save); - - p_rarch->lastcmd_source = source; - - while (tok) - { - command_parse_sub_msg(handle, tok); - tok = strtok_r(NULL, "\n", &save); - } - - p_rarch->lastcmd_source = CMD_NONE; -} - -static void command_network_poll( - struct rarch_state *p_rarch, - command_t *handle) -{ - fd_set fds; - struct timeval tmp_tv = {0}; - - if (handle->net_fd < 0) - return; - - FD_ZERO(&fds); - FD_SET(handle->net_fd, &fds); - - if (socket_select(handle->net_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - return; - - if (!FD_ISSET(handle->net_fd, &fds)) - return; - - for (;;) - { - ssize_t ret; - char buf[1024]; - - buf[0] = '\0'; - - p_rarch->lastcmd_net_fd = handle->net_fd; - p_rarch->lastcmd_net_source_len = sizeof(p_rarch->lastcmd_net_source); - ret = recvfrom(handle->net_fd, buf, - sizeof(buf) - 1, 0, - (struct sockaddr*)&p_rarch->lastcmd_net_source, - &p_rarch->lastcmd_net_source_len); - - if (ret <= 0) - break; - - buf[ret] = '\0'; - - command_parse_msg(p_rarch, handle, buf, CMD_NETWORK); - } -} -#endif - -static bool command_free(command_t *handle) -{ -#ifdef HAVE_NETWORK_CMD - if (handle && handle->net_fd >= 0) - socket_close(handle->net_fd); -#endif - - free(handle); - - return true; -} - -#ifdef HAVE_STDIN_CMD -static bool command_stdin_init(command_t *handle) -{ -#ifndef _WIN32 -#ifdef HAVE_NETWORKING - if (!socket_nonblock(STDIN_FILENO)) - return false; -#endif -#endif - - handle->stdin_enable = true; - return true; -} - -static void command_stdin_poll( - struct rarch_state *p_rarch, - command_t *handle) -{ - ptrdiff_t msg_len; - char *last_newline = NULL; - ssize_t ret = read_stdin( - handle->stdin_buf + handle->stdin_buf_ptr, - STDIN_BUF_SIZE - handle->stdin_buf_ptr - 1); - - if (ret == 0) - return; - - handle->stdin_buf_ptr += ret; - handle->stdin_buf[handle->stdin_buf_ptr] = '\0'; - - last_newline = - strrchr(handle->stdin_buf, '\n'); - - if (!last_newline) - { - /* We're receiving bogus data in pipe - * (no terminating newline), flush out the buffer. */ - if (handle->stdin_buf_ptr + 1 >= STDIN_BUF_SIZE) - { - handle->stdin_buf_ptr = 0; - handle->stdin_buf[0] = '\0'; - } - - return; - } - - *last_newline++ = '\0'; - msg_len = last_newline - handle->stdin_buf; - -#if defined(HAVE_NETWORKING) - command_parse_msg(p_rarch, - handle, handle->stdin_buf, CMD_STDIN); -#endif - - memmove(handle->stdin_buf, last_newline, - handle->stdin_buf_ptr - msg_len); - handle->stdin_buf_ptr -= msg_len; -} -#endif - -static bool command_network_new( - command_t *handle, - bool stdin_enable, - bool network_enable, - uint16_t port) -{ -#ifdef HAVE_NETWORK_CMD - handle->net_fd = -1; - if (network_enable && !command_network_init(handle, port)) - goto error; -#endif - -#ifdef HAVE_STDIN_CMD - handle->stdin_enable = stdin_enable; - if (stdin_enable && !command_stdin_init(handle)) - goto error; -#endif - - return true; - -#if defined(HAVE_NETWORK_CMD) || defined(HAVE_STDIN_CMD) -error: - command_free(handle); - return false; -#endif -} #endif static bool retroarch_apply_shader( @@ -11049,7 +10687,7 @@ static bool retroarch_apply_shader( } #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) -static bool command_set_shader(const char *arg) +bool command_set_shader(command_t *cmd, const char *arg) { enum rarch_shader_type type = video_shader_parse_type(arg); struct rarch_state *p_rarch = &rarch_st; @@ -22630,20 +22268,16 @@ static void input_driver_poll(void) } #ifdef HAVE_COMMAND - if (p_rarch->input_driver_command) + for (i = 0; i < ARRAY_SIZE(p_rarch->input_driver_command); i++) { - memset(p_rarch->input_driver_command->state, - 0, sizeof(p_rarch->input_driver_command->state)); -#if defined(HAVE_NETWORK_CMD) && defined(HAVE_COMMAND) - command_network_poll(p_rarch, - p_rarch->input_driver_command); -#endif + if (p_rarch->input_driver_command[i]) + { + memset(p_rarch->input_driver_command[i]->state, + 0, sizeof(p_rarch->input_driver_command[i]->state)); -#ifdef HAVE_STDIN_CMD - if (p_rarch->input_driver_command->stdin_enable) - command_stdin_poll(p_rarch, - p_rarch->input_driver_command); -#endif + p_rarch->input_driver_command[i]->poll( + p_rarch->input_driver_command[i]); + } } #endif @@ -24778,18 +24412,20 @@ static INLINE bool input_keys_pressed_other_sources( unsigned i, input_bits_t* p_new_state) { +#ifdef HAVE_COMMAND + int j; + for (j = 0; j < ARRAY_SIZE(p_rarch->input_driver_command); j++) + if ((i < RARCH_BIND_LIST_END) && p_rarch->input_driver_command[j] + && p_rarch->input_driver_command[j]->state[i]) + return true; +#endif + #ifdef HAVE_OVERLAY if (p_rarch->overlay_ptr && ((BIT256_GET(p_rarch->overlay_ptr->overlay_state.buttons, i)))) return true; #endif -#ifdef HAVE_COMMAND - if (p_rarch->input_driver_command) - return ((i < RARCH_BIND_LIST_END) - && p_rarch->input_driver_command->state[i]); -#endif - #ifdef HAVE_NETWORKGAMEPAD /* Only process key presses related to game input if using Remote RetroPad */ if (i < RARCH_CUSTOM_BIND_LIST_END @@ -25046,7 +24682,7 @@ void input_driver_unset_nonblock_state(void) } #ifdef HAVE_COMMAND -static bool input_driver_init_command(struct rarch_state *p_rarch) +static void input_driver_init_command(struct rarch_state *p_rarch) { settings_t *settings = p_rarch->configuration_settings; bool input_stdin_cmd_enable = settings->bools.stdin_cmd_enable; @@ -25055,35 +24691,50 @@ static bool input_driver_init_command(struct rarch_state *p_rarch) bool grab_stdin = p_rarch->current_input->grab_stdin && p_rarch->current_input->grab_stdin(p_rarch->current_input_data); - if (!input_stdin_cmd_enable && !input_network_cmd_enable) - return false; - - if (input_stdin_cmd_enable && grab_stdin) + #ifdef HAVE_STDIN_CMD + if (input_stdin_cmd_enable) { - RARCH_WARN("stdin command interface is desired, but input driver has already claimed stdin.\n" - "Cannot use this command interface.\n"); + if (grab_stdin) + { + RARCH_WARN("stdin command interface is desired, but input driver has already claimed stdin.\n" + "Cannot use this command interface.\n"); + } + else { + p_rarch->input_driver_command[0] = command_stdin_new(); + if (!p_rarch->input_driver_command[1]) + RARCH_ERR("Failed to initialize the stdin command interface.\n"); + } } + #endif - p_rarch->input_driver_command = (command_t*) - calloc(1, sizeof(*p_rarch->input_driver_command)); + /* Initialize the network command interface */ + #ifdef HAVE_NETWORK_CMD + if (input_network_cmd_enable) + { + p_rarch->input_driver_command[1] = command_network_new(network_cmd_port); + if (!p_rarch->input_driver_command[1]) + RARCH_ERR("Failed to initialize the network command interface.\n"); + } + #endif - if (p_rarch->input_driver_command) - if (command_network_new( - p_rarch->input_driver_command, - input_stdin_cmd_enable && !grab_stdin, - input_network_cmd_enable, - network_cmd_port)) - return true; - - RARCH_ERR("Failed to initialize command interface.\n"); - return false; + #ifdef HAVE_LAKKA + p_rarch->input_driver_command[2] = command_uds_new(); + if (!p_rarch->input_driver_command[2]) + RARCH_ERR("Failed to initialize the UDS command interface.\n"); + #endif } static void input_driver_deinit_command(struct rarch_state *p_rarch) { - if (p_rarch->input_driver_command) - command_free(p_rarch->input_driver_command); - p_rarch->input_driver_command = NULL; + int i; + for (i = 0; i < ARRAY_SIZE(p_rarch->input_driver_command); i++) + { + if (p_rarch->input_driver_command[i]) + p_rarch->input_driver_command[i]->destroy( + p_rarch->input_driver_command[i]); + + p_rarch->input_driver_command[i] = NULL; + } } #endif diff --git a/retroarch_data.h b/retroarch_data.h index 400fa1e704..19a9e9a412 100644 --- a/retroarch_data.h +++ b/retroarch_data.h @@ -107,9 +107,6 @@ #define DECLARE_BIND(base, bind, desc) { #base, desc, 0, bind, true } #define DECLARE_META_BIND(level, base, bind, desc) { #base, desc, level, bind, true } -#define DEFAULT_NETWORK_CMD_PORT 55355 -#define STDIN_BUF_SIZE 4096 - #ifdef HAVE_THREADS #define VIDEO_DRIVER_IS_THREADED_INTERNAL() ((!video_driver_is_hw_context() && p_rarch->video_driver_threaded) ? true : false) #else @@ -1301,13 +1298,6 @@ enum rarch_movie_type RARCH_MOVIE_RECORD }; -enum cmd_source_t -{ - CMD_NONE = 0, - CMD_STDIN, - CMD_NETWORK -}; - enum poll_type_override_t { POLL_TYPE_OVERRIDE_DONTCARE = 0, @@ -1485,36 +1475,6 @@ struct input_overlay }; #endif -struct cmd_map -{ - const char *str; - unsigned id; -}; - -#if defined(HAVE_COMMAND) -struct cmd_action_map -{ - const char *str; - bool (*action)(const char *arg); - const char *arg_desc; -}; -#endif - -struct command -{ -#ifdef HAVE_STDIN_CMD - size_t stdin_buf_ptr; -#endif -#ifdef HAVE_NETWORK_CMD - int net_fd; -#endif -#ifdef HAVE_STDIN_CMD - char stdin_buf[STDIN_BUF_SIZE]; -#endif - bool stdin_enable; - bool state[RARCH_BIND_LIST_END]; -}; - /* Input config. */ struct input_bind_map { @@ -1751,11 +1711,6 @@ struct rarch_state #ifdef HAVE_MENU struct menu_state menu_driver_state; /* int64_t alignment */ #endif -#if defined(HAVE_COMMAND) -#ifdef HAVE_NETWORK_CMD - struct sockaddr_storage lastcmd_net_source; /* int64_t alignment */ -#endif -#endif #ifdef HAVE_GFX_WIDGETS dispgfx_widget_t dispwidget_st; /* uint64_t alignment */ #endif @@ -1883,7 +1838,7 @@ struct rarch_state void *keyboard_press_data; #ifdef HAVE_COMMAND - command_t *input_driver_command; + command_t *input_driver_command[MAX_CMD_DRIVERS]; #endif #ifdef HAVE_NETWORKGAMEPAD input_remote_t *input_driver_remote; @@ -2133,11 +2088,6 @@ struct rarch_state * TODO - Dirty hack, fix it better */ gfx_ctx_flags_t deferred_flag_data; /* uint32_t alignment */ -#if defined(HAVE_COMMAND) -#ifdef HAVE_NETWORK_CMD - socklen_t lastcmd_net_source_len; /* uint32_t alignment */ -#endif -#endif retro_bits_t has_set_libretro_device; /* uint32_t alignment */ input_mapper_t input_driver_mapper; /* uint32_t alignment */ @@ -2691,93 +2641,6 @@ static enum rarch_shader_type shader_types[] = }; #endif -/* These forward declarations need to be declared before - * the global state is declared */ -#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) -static bool command_set_shader(const char *arg); -#endif -#if defined(HAVE_COMMAND) -static bool command_version(const char* arg); -static bool command_get_status(const char* arg); -static bool command_get_config_param(const char* arg); -static bool command_show_osd_msg(const char* arg); -#ifdef HAVE_CHEEVOS -static bool command_read_ram(const char *arg); -static bool command_write_ram(const char *arg); -#endif -static bool command_read_memory(const char *arg); -static bool command_write_memory(const char *arg); - -static const struct cmd_action_map action_map[] = { -#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) - { "SET_SHADER", command_set_shader, "" }, -#endif - { "VERSION", command_version, "No argument"}, - { "GET_STATUS", command_get_status, "No argument" }, - { "GET_CONFIG_PARAM", command_get_config_param, "" }, - { "SHOW_MSG", command_show_osd_msg, "No argument" }, -#if defined(HAVE_CHEEVOS) - /* These functions use achievement addresses and only work if a game with achievements is - * loaded. READ_CORE_MEMORY and WRITE_CORE_MEMORY are preferred and use system addresses. */ - { "READ_CORE_RAM", command_read_ram, "
" }, - { "WRITE_CORE_RAM", command_write_ram, "
..." }, -#endif - { "READ_CORE_MEMORY", command_read_memory, "
" }, - { "WRITE_CORE_MEMORY",command_write_memory, "
..." }, -}; - -static const struct cmd_map map[] = { - { "FAST_FORWARD", RARCH_FAST_FORWARD_KEY }, - { "FAST_FORWARD_HOLD", RARCH_FAST_FORWARD_HOLD_KEY }, - { "SLOWMOTION", RARCH_SLOWMOTION_KEY }, - { "SLOWMOTION_HOLD", RARCH_SLOWMOTION_HOLD_KEY }, - { "LOAD_STATE", RARCH_LOAD_STATE_KEY }, - { "SAVE_STATE", RARCH_SAVE_STATE_KEY }, - { "FULLSCREEN_TOGGLE", RARCH_FULLSCREEN_TOGGLE_KEY }, - { "CLOSE_CONTENT", RARCH_CLOSE_CONTENT_KEY }, - { "QUIT", RARCH_QUIT_KEY }, - { "STATE_SLOT_PLUS", RARCH_STATE_SLOT_PLUS }, - { "STATE_SLOT_MINUS", RARCH_STATE_SLOT_MINUS }, - { "REWIND", RARCH_REWIND }, - { "BSV_RECORD_TOGGLE", RARCH_BSV_RECORD_TOGGLE }, - { "PAUSE_TOGGLE", RARCH_PAUSE_TOGGLE }, - { "FRAMEADVANCE", RARCH_FRAMEADVANCE }, - { "RESET", RARCH_RESET }, - { "SHADER_NEXT", RARCH_SHADER_NEXT }, - { "SHADER_PREV", RARCH_SHADER_PREV }, - { "CHEAT_INDEX_PLUS", RARCH_CHEAT_INDEX_PLUS }, - { "CHEAT_INDEX_MINUS", RARCH_CHEAT_INDEX_MINUS }, - { "CHEAT_TOGGLE", RARCH_CHEAT_TOGGLE }, - { "SCREENSHOT", RARCH_SCREENSHOT }, - { "MUTE", RARCH_MUTE }, - { "OSK", RARCH_OSK }, - { "FPS_TOGGLE", RARCH_FPS_TOGGLE }, - { "SEND_DEBUG_INFO", RARCH_SEND_DEBUG_INFO }, - { "NETPLAY_HOST_TOGGLE", RARCH_NETPLAY_HOST_TOGGLE }, - { "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH }, - { "VOLUME_UP", RARCH_VOLUME_UP }, - { "VOLUME_DOWN", RARCH_VOLUME_DOWN }, - { "OVERLAY_NEXT", RARCH_OVERLAY_NEXT }, - { "DISK_EJECT_TOGGLE", RARCH_DISK_EJECT_TOGGLE }, - { "DISK_NEXT", RARCH_DISK_NEXT }, - { "DISK_PREV", RARCH_DISK_PREV }, - { "GRAB_MOUSE_TOGGLE", RARCH_GRAB_MOUSE_TOGGLE }, - { "UI_COMPANION_TOGGLE", RARCH_UI_COMPANION_TOGGLE }, - { "GAME_FOCUS_TOGGLE", RARCH_GAME_FOCUS_TOGGLE }, - { "MENU_TOGGLE", RARCH_MENU_TOGGLE }, - { "RECORDING_TOGGLE", RARCH_RECORDING_TOGGLE }, - { "STREAMING_TOGGLE", RARCH_STREAMING_TOGGLE }, - { "RUNAHEAD_TOGGLE", RARCH_RUNAHEAD_TOGGLE }, - { "MENU_UP", RETRO_DEVICE_ID_JOYPAD_UP }, - { "MENU_DOWN", RETRO_DEVICE_ID_JOYPAD_DOWN }, - { "MENU_LEFT", RETRO_DEVICE_ID_JOYPAD_LEFT }, - { "MENU_RIGHT", RETRO_DEVICE_ID_JOYPAD_RIGHT }, - { "MENU_A", RETRO_DEVICE_ID_JOYPAD_A }, - { "MENU_B", RETRO_DEVICE_ID_JOYPAD_B }, - { "AI_SERVICE", RARCH_AI_SERVICE }, -}; -#endif - #ifdef HAVE_MENU static void *null_menu_init(void **userdata, bool video_is_threaded) {