/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2021 - David G.F. * * 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 . */ #include #include #include #include #ifdef HAVE_NETWORKING #include #include #endif #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_CHEEVOS #include "cheevos/cheevos.h" #endif #ifdef HAVE_GFX_WIDGETS #include "gfx/gfx_widgets.h" #endif #ifdef HAVE_MENU #include "menu/menu_driver.h" #endif #ifdef HAVE_NETWORKING #include "network/netplay/netplay.h" #endif #include "audio/audio_driver.h" #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) #include "gfx/video_shader_parse.h" #endif #include "autosave.h" #include "command.h" #include "core_info.h" #include "cheat_manager.h" #include "content.h" #include "dynamic.h" #include "list_special.h" #include "paths.h" #include "retroarch.h" #include "runloop.h" #include "verbosity.h" #include "version.h" #include "version_git.h" #define CMD_BUF_SIZE 4096 static void command_post_state_loaded(void) { #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) { const char *_msg = msg_hash_to_str(MSG_CHEEVOS_HARDCORE_MODE_DISABLED); rcheevos_pause_hardcore(); runloop_msg_queue_push(_msg, strlen(_msg), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } #endif #ifdef HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL); #endif { settings_t *settings = config_get_ptr(); video_driver_state_t *video_st = video_state_get_ptr(); bool frame_time_counter_reset_after_load_state = settings->bools.frame_time_counter_reset_after_load_state; if (frame_time_counter_reset_after_load_state) video_st->frame_time_count = 0; } } #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) return false; 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(msg_hash_to_str(MSG_UNRECOGNIZED_COMMAND), tok); } 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) { ssize_t ret; char buf[2048]; command_network_t *netcmd = (command_network_t*)handle->userptr; if (netcmd->net_fd < 0) return; for (;;) { netcmd->cmd_source_len = sizeof(netcmd->cmd_source); if ((ret = recvfrom(netcmd->net_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&netcmd->cmd_source, &netcmd->cmd_source_len)) <= 0) return; 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(*cmd)); command_network_t *netcmd = (command_network_t*)calloc( 1, sizeof(command_network_t)); int fd = socket_init((void**)&res, port, NULL, SOCKET_TYPE_DATAGRAM, AF_INET); RARCH_LOG("[NetCMD]: %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("[NetCMD]: %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); fflush(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)); if (!cmd) return NULL; if (!stdincmd) { free(cmd); return NULL; } cmd->userptr = stdincmd; cmd->poll = command_stdin_poll; cmd->replier = stdin_command_reply; cmd->destroy = stdin_command_free; return cmd; } #endif bool command_get_config_param(command_t *cmd, const char* arg) { size_t _len; char reply[8192]; #ifdef HAVE_BSV_MOVIE char value_dynamic[256]; #endif const char *value = "unsupported"; settings_t *settings = config_get_ptr(); bool video_fullscreen = settings->bools.video_fullscreen; const char *dir_runtime_log = settings->paths.directory_runtime_log; const char *log_dir = settings->paths.log_dir; const char *directory_cache = settings->paths.directory_cache; const char *directory_system = settings->paths.directory_system; const char *path_username = settings->paths.username; if (string_is_equal(arg, "video_fullscreen")) { if (video_fullscreen) value = "true"; else value = "false"; } else if (string_is_equal(arg, "savefile_directory")) value = dir_get_ptr(RARCH_DIR_SAVEFILE); else if (string_is_equal(arg, "savestate_directory")) value = dir_get_ptr(RARCH_DIR_SAVESTATE); else if (string_is_equal(arg, "runtime_log_directory")) value = dir_runtime_log; else if (string_is_equal(arg, "log_dir")) value = log_dir; else if (string_is_equal(arg, "cache_directory")) value = directory_cache; else if (string_is_equal(arg, "system_directory")) value = directory_system; else if (string_is_equal(arg, "netplay_nickname")) value = path_username; #ifdef HAVE_BSV_MOVIE else if (string_is_equal(arg, "active_replay")) { input_driver_state_t *input_st = input_state_get_ptr(); value = value_dynamic; value_dynamic[0] = '\0'; if (input_st->bsv_movie_state_handle) { bsv_movie_t *movie = input_st->bsv_movie_state_handle; snprintf(value_dynamic, sizeof(value_dynamic), "%lld %u %lld", (long long)(movie->identifier), input_st->bsv_movie_state.flags, (long long)(movie->frame_counter)); } else strlcpy(value_dynamic, "0 0 0", sizeof(value_dynamic)); } #endif /* TODO: query any string */ _len = strlcpy(reply, "GET_CONFIG_PARAM ", sizeof(reply)); _len += strlcpy(reply + _len, arg, sizeof(reply) - _len); reply[ _len] = ' '; reply[++_len] = '\0'; _len += strlcpy(reply + _len, value, sizeof(reply) - _len); cmd->replier(cmd, reply, _len); return true; } #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; int fd; ssize_t ret; char buf[2048]; command_uds_t *udscmd = (command_uds_t*)handle->userptr; if (udscmd->sfd < 0) return; /* Read data from clients and process commands */ for (i = 0; i < MAX_USER_CONNECTIONS; i++) { bool err = false; fd = udscmd->userfd[i]; if (fd < 0) continue; ret = socket_receive_all_nonblocking(fd, &err, buf, sizeof(buf) - 1); if (!ret) continue; if (!err) { buf[ret] = '\0'; udscmd->last_fd = fd; command_parse_msg(handle, buf); } else { socket_close(fd); udscmd->userfd[i] = -1; } } /* Accepts new connections from clients */ fd = accept(udscmd->sfd, NULL, NULL); if (fd >= 0) { if (socket_nonblock(fd)) { for (i = 0; i < MAX_USER_CONNECTIONS; i++) { if (udscmd->userfd[i] < 0) { udscmd->userfd[i] = fd; return; } } } socket_close(fd); } } command_t* command_uds_new(void) { int i; command_t *cmd; command_uds_t *subcmd; struct sockaddr_un addr; socklen_t addrsz = offsetof(struct sockaddr_un, sun_path) + STRLEN_CONST("retroarch/cmd") + 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], "retroarch/cmd"); 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("[NetCMD]: Command \"%s\" is not recognized by the program.\n", cmd); RARCH_ERR("[NetCMD]: \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 udp_send_packet(const char *host, uint16_t port, const char *msg) { char port_buf[6]; const struct addrinfo *tmp_info; struct addrinfo *addr = NULL; struct addrinfo hints = {0}; size_t len = strlen(msg); bool ret = false; snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port); hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICSERV; if (getaddrinfo_retro(host, port_buf, &hints, &addr)) return false; if (!addr) return false; /* Send to all possible targets. */ tmp_info = addr; do { int fd = socket(tmp_info->ai_family, tmp_info->ai_socktype, tmp_info->ai_protocol); if (fd < 0) continue; if (sendto(fd, msg, len, 0, tmp_info->ai_addr, tmp_info->ai_addrlen) == (ssize_t)len) ret = true; socket_close(fd); } while ((tmp_info = tmp_info->ai_next)); freeaddrinfo_retro(addr); return ret; } 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("[NetCMD]: %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 bool command_show_osd_msg(command_t *cmd, const char* arg) { runloop_msg_queue_push(arg, strlen(arg), 1, 180, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); return true; } bool command_load_state_slot(command_t *cmd, const char *arg) { char state_path[16384]; size_t _len = 0; char reply[128] = ""; unsigned int slot = (unsigned int)strtoul(arg, NULL, 10); bool savestates_enabled = core_info_current_supports_savestate(); bool ret = false; state_path[0] = '\0'; _len = strlcpy(reply, "LOAD_STATE_SLOT ", sizeof(reply)); _len += snprintf(reply + _len, sizeof(reply) - _len, "%d", slot); if (savestates_enabled) { size_t info_size; runloop_get_savestate_path(state_path, sizeof(state_path), slot); info_size = core_serialize_size(); savestates_enabled = (info_size > 0); } if (savestates_enabled) { if ((ret = content_load_state(state_path, false, false))) command_post_state_loaded(); } else ret = false; cmd->replier(cmd, reply, _len); return ret; } bool command_play_replay_slot(command_t *cmd, const char *arg) { #ifdef HAVE_BSV_MOVIE char replay_path[16384]; char reply[128] = ""; unsigned int slot = (unsigned int)strtoul(arg, NULL, 10); bool savestates_enabled = core_info_current_supports_savestate(); bool ret = false; replay_path[0] = '\0'; if (savestates_enabled) { size_t info_size; runloop_get_replay_path(replay_path, sizeof(replay_path), slot); info_size = core_serialize_size(); savestates_enabled = (info_size > 0); } if (savestates_enabled) { ret = movie_start_playback(input_state_get_ptr(), replay_path); if (ret) { input_driver_state_t *input_st = input_state_get_ptr(); task_queue_wait(NULL, NULL); if (input_st->bsv_movie_state_next_handle) snprintf(reply, sizeof(reply) - 1, "PLAY_REPLAY_SLOT %lld", (long long)(input_st->bsv_movie_state_next_handle->identifier)); else snprintf(reply, sizeof(reply) - 1, "PLAY_REPLAY_SLOT 0"); command_post_state_loaded(); } } else ret = false; cmd->replier(cmd, reply, strlen(reply)); return ret; #else return false; #endif } #if defined(HAVE_CHEEVOS) bool command_read_ram(command_t *cmd, const char *arg) { unsigned i; char *reply = NULL; const uint8_t *data = NULL; char *reply_at = NULL; unsigned int nbytes = 0; unsigned int alloc_size = 0; unsigned int addr = -1; size_t _len = 0; if (sscanf(arg, "%x %u", &addr, &nbytes) != 2) return true; /* We allocate more than needed, saving 20 bytes is not really relevant */ alloc_size = 40 + nbytes * 3; reply = (char*)malloc(alloc_size); reply[0] = '\0'; reply_at = reply + snprintf( reply, alloc_size - 1, "READ_CORE_RAM" " %x", addr); if ((data = rcheevos_patch_address(addr))) { for (i = 0; i < nbytes; i++) snprintf(reply_at + 3 * i, 4, " %.2X", data[i]); reply_at[3 * nbytes] = '\n'; _len = reply_at + 3 * nbytes + 1 - reply; } else { strlcpy(reply_at, " -1\n", sizeof(reply) - strlen(reply)); _len = reply_at + STRLEN_CONST(" -1\n") - reply; } cmd->replier(cmd, reply, _len); free(reply); return true; } 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); if (!data) return false; if (rcheevos_hardcore_active()) { RARCH_LOG("[Command]: Achievements hardcore mode disabled by WRITE_CORE_RAM.\n"); rcheevos_pause_hardcore(); } while (*arg) { *data = strtoul(arg, (char**)&arg, 16); data++; } return true; } #endif bool command_version(command_t *cmd, const char* arg) { char reply[256]; size_t _len = strlcpy(reply, PACKAGE_VERSION, sizeof(reply)); reply[ _len] = '\n'; reply[++_len] = '\0'; cmd->replier(cmd, reply, _len); return true; } static const rarch_memory_descriptor_t* command_memory_get_descriptor(const rarch_memory_map_t* mmap, unsigned address, size_t* offset) { const rarch_memory_descriptor_t* desc = mmap->descriptors; const rarch_memory_descriptor_t* end = desc + mmap->num_descriptors; for (; desc < end; desc++) { if (desc->core.select == 0) { /* if select is 0, attempt to explicitly match the address */ if (address >= desc->core.start && address < desc->core.start + desc->core.len) { *offset = address - desc->core.start; return desc; } } else { /* otherwise, attempt to match the address by matching the select bits */ if (((desc->core.start ^ address) & desc->core.select) == 0) { /* adjust the address to the start of the descriptor */ unsigned desc_offset = address - (unsigned)desc->core.start; /* address is unsigned. we only need that much of the disconnect mask */ unsigned mask = (unsigned)desc->core.disconnect; /* this magic logic is copied from mmap_reduce. it removes any bits from * address that are non-zero in the disconnect field. bits above the * removed bits are shifted down to fill the gap. */ while (mask) { const unsigned tmp = (mask - 1) & ~mask; desc_offset = (desc_offset & tmp) | ((desc_offset >> 1) & ~tmp); mask = (mask & (mask - 1)) >> 1; } /* we've calculated the actual offset of the data within the descriptor */ *offset = desc_offset; /* sanity check - make sure the descriptor is large enough to hold the target address */ if (desc_offset < desc->core.len) return desc; } } } return NULL; } static uint8_t *command_memory_get_pointer( const rarch_system_info_t* sys_info, unsigned address, unsigned int* max_bytes, int for_write, char *s, size_t len) { if (!sys_info || sys_info->mmaps.num_descriptors == 0) strlcpy(s, " -1 no memory map defined\n", len); else { size_t offset; const rarch_memory_descriptor_t* desc = command_memory_get_descriptor(&sys_info->mmaps, address, &offset); if (!desc) strlcpy(s, " -1 no descriptor for address\n", len); else if (!desc->core.ptr) strlcpy(s, " -1 no data for descriptor\n", len); else if (for_write && (desc->core.flags & RETRO_MEMDESC_CONST)) strlcpy(s, " -1 descriptor data is readonly\n", len); else { *max_bytes = (unsigned int)(desc->core.len - offset); return (uint8_t*)desc->core.ptr + desc->core.offset + offset; } } *max_bytes = 0; return NULL; } bool command_get_status(command_t *cmd, const char* arg) { size_t _len; char reply[4096]; uint8_t flags = content_get_flags(); if (flags & CONTENT_ST_FLAG_IS_INITED) { /* add some content info */ core_info_t *core_info = NULL; runloop_state_t *runloop_st = runloop_state_get_ptr(); core_info_get_current_core(&core_info); _len = strlcpy(reply, "GET_STATUS ", sizeof(reply)); if (runloop_st->flags & RUNLOOP_FLAG_PAUSED) _len += strlcpy(reply + _len, "PAUSED", sizeof(reply) - _len); else _len += strlcpy(reply + _len, "PLAYING", sizeof(reply) - _len); _len += strlcpy(reply + _len, " ", sizeof(reply) - _len); if (core_info) _len += strlcpy(reply + _len, core_info->system_id, sizeof(reply) - _len); else _len += strlcpy(reply + _len, runloop_st->system.info.library_name, sizeof(reply) - _len); _len += strlcpy(reply + _len, ",", sizeof(reply) - _len); _len += strlcpy(reply + _len, path_basename(path_get(RARCH_PATH_BASENAME)), sizeof(reply) - _len); _len += snprintf(reply + _len, sizeof(reply) - _len, ",crc32=%x\n", content_get_crc()); } else _len = strlcpy(reply, "GET_STATUS CONTENTLESS", sizeof(reply)); cmd->replier(cmd, reply, _len); return true; } bool command_read_memory(command_t *cmd, const char *arg) { unsigned i; char* reply = NULL; char* reply_at = NULL; const uint8_t* data = NULL; unsigned int nbytes = 0; unsigned int alloc_size = 0; unsigned int address = -1; size_t _len = 0; unsigned int max_bytes = 0; runloop_state_t *runloop_st = runloop_state_get_ptr(); const rarch_system_info_t* sys_info= &runloop_st->system; if (sscanf(arg, "%x %u", &address, &nbytes) != 2) return false; /* Ensure large enough to return all requested bytes or an error message */ alloc_size = 64 + nbytes * 3; reply = (char*)malloc(alloc_size); reply_at = reply + snprintf(reply, alloc_size - 1, "READ_CORE_MEMORY %x", address); if ((data = command_memory_get_pointer( sys_info, address, &max_bytes, 0, reply_at, alloc_size - strlen(reply)))) { if (nbytes > max_bytes) nbytes = max_bytes; for (i = 0; i < nbytes; i++) snprintf(reply_at + 3 * i, 4, " %02X", data[i]); reply_at[3 * nbytes] = '\n'; _len = reply_at + 3 * nbytes + 1 - reply; } else _len = strlen(reply); cmd->replier(cmd, reply, _len); free(reply); return true; } 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; char reply[128] = ""; runloop_state_t *runloop_st = runloop_state_get_ptr(); const rarch_system_info_t *sys_info = &runloop_st->system; char *reply_at = reply + snprintf(reply, sizeof(reply) - 1, "WRITE_CORE_MEMORY %x", address); uint8_t *data = command_memory_get_pointer(sys_info, address, &max_bytes, 1, reply_at, sizeof(reply) - strlen(reply) - 1); if (data) { uint8_t* start = data; while (*arg && max_bytes > 0) { --max_bytes; *data = strtoul(arg, (char**)&arg, 16); data++; } snprintf(reply_at, sizeof(reply) - strlen(reply) - 1, " %u\n", (unsigned)(data - start)); #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) { RARCH_LOG("[Command]: Achievements hardcore mode disabled by WRITE_CORE_MEMORY.\n"); rcheevos_pause_hardcore(); } #endif } cmd->replier(cmd, reply, strlen(reply)); return true; } #endif void command_event_set_volume( settings_t *settings, float gain, bool widgets_active, bool audio_driver_mute_enable) { size_t _len; char msg[128]; float new_volume = settings->floats.audio_volume + gain; new_volume = MAX(new_volume, -80.0f); new_volume = MIN(new_volume, 12.0f); configuration_set_float(settings, settings->floats.audio_volume, new_volume); _len = strlcpy(msg, msg_hash_to_str(MSG_AUDIO_VOLUME), sizeof(msg)); msg[_len ] = ':'; msg[++_len] = ' '; msg[++_len] = '\0'; _len += snprintf(msg + _len, sizeof(msg) - _len, "%.1f", new_volume); msg[_len ] = ' '; msg[++_len] = 'd'; msg[++_len] = 'B'; msg[++_len] = '\0'; #if defined(HAVE_GFX_WIDGETS) if (widgets_active) gfx_widget_volume_update_and_show(new_volume, audio_driver_mute_enable); else #endif runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); RARCH_LOG("[Audio]: %s\n", msg); audio_set_float(AUDIO_ACTION_VOLUME_GAIN, new_volume); } /** * event_set_mixer_volume: * @gain : amount of gain to be applied to current volume level. * * Adjusts the current audio volume level. * **/ void command_event_set_mixer_volume( settings_t *settings, float gain) { size_t _len; char msg[128]; float new_volume = settings->floats.audio_mixer_volume + gain; new_volume = MAX(new_volume, -80.0f); new_volume = MIN(new_volume, 12.0f); configuration_set_float(settings, settings->floats.audio_mixer_volume, new_volume); _len = strlcpy(msg, msg_hash_to_str(MSG_AUDIO_VOLUME), sizeof(msg)); msg[_len ] = ':'; msg[++_len] = ' '; msg[++_len] = '\0'; _len += snprintf(msg + _len, sizeof(msg) - _len, "%.1f", new_volume); msg[_len ] = ' '; msg[++_len] = 'd'; msg[++_len] = 'B'; msg[++_len] = '\0'; runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); RARCH_LOG("[Audio]: %s\n", msg); audio_set_float(AUDIO_ACTION_VOLUME_GAIN, new_volume); } void command_event_init_controllers(rarch_system_info_t *sys_info, settings_t *settings, unsigned num_active_users) { unsigned port; unsigned num_core_ports = sys_info->ports.size; for (port = 0; port < num_core_ports; port++) { unsigned i; retro_ctx_controller_info_t pad; unsigned device = RETRO_DEVICE_NONE; const struct retro_controller_description *desc = NULL; /* Check whether current core port is mapped * to an input device * > If is not, leave 'device' set to * 'RETRO_DEVICE_NONE' * > For example: if input ports 0 and 1 are * mapped to core port 0, core port 1 will * be unmapped and should be disabled */ for (i = 0; i < num_active_users; i++) { if (i >= MAX_USERS) break; if (port == settings->uints.input_remap_ports[i]) { device = input_config_get_device(port); break; } } desc = libretro_find_controller_description( &sys_info->ports.data[port], device); if (desc && !desc->desc) { /* If we're trying to connect a completely unknown device, * revert back to JOYPAD. */ if (device != RETRO_DEVICE_JOYPAD && device != RETRO_DEVICE_NONE) { /* Do not fix device, * because any use of dummy core will reset this, * which is not a good idea. */ RARCH_WARN("[Input]: Input device ID %u is unknown to this " "libretro implementation. Using RETRO_DEVICE_JOYPAD.\n", device); device = RETRO_DEVICE_JOYPAD; } } pad.device = device; pad.port = port; core_set_controller_port_device(&pad); } } #ifdef HAVE_CONFIGFILE static size_t command_event_save_config( const char *config_path, char *s, size_t len) { size_t _len = 0; bool path_exists = !string_is_empty(config_path); const char *str = path_exists ? config_path : path_get(RARCH_PATH_CONFIG); /* Workaround for libdecor 0.2.0 setting unwanted locale */ #if defined(HAVE_WAYLAND) && defined(HAVE_DYNAMIC) setlocale(LC_NUMERIC,"C"); #endif if (path_exists && config_save_file(config_path)) { #if IOS char tmp[PATH_MAX_LENGTH] = {0}; fill_pathname_abbreviate_special(tmp, config_path, sizeof(tmp)); _len = snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_SAVED_NEW_CONFIG_TO), tmp); #else _len = snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_SAVED_NEW_CONFIG_TO), config_path); #endif RARCH_LOG("[Config]: %s\n", s); return _len; } if (!string_is_empty(str)) { _len = snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_FAILED_SAVING_CONFIG_TO), str); RARCH_ERR("[Config]: %s\n", s); } return _len; } #endif static size_t command_event_undo_save_state(char *s, size_t len) { if (content_undo_save_buf_is_empty()) return strlcpy(s, msg_hash_to_str(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET), len); if (!content_undo_save_state()) return strlcpy(s, msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE), len); return strlcpy(s, msg_hash_to_str(MSG_UNDOING_SAVE_STATE), len); } static size_t command_event_undo_load_state(char *s, size_t len) { if (content_undo_load_buf_is_empty()) return strlcpy(s, msg_hash_to_str(MSG_NO_STATE_HAS_BEEN_LOADED_YET), len); if (!content_undo_load_state()) return snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE), "RAM"); #ifdef HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL); #endif return strlcpy(s, msg_hash_to_str(MSG_UNDID_LOAD_STATE), len); } bool command_event_resize_windowed_scale(settings_t *settings, unsigned window_scale) { unsigned idx = 0; bool video_fullscreen = settings->bools.video_fullscreen; if (window_scale == 0) return false; configuration_set_uint(settings, settings->uints.video_scale, window_scale); if (!video_fullscreen) command_event(CMD_EVENT_REINIT, NULL); retroarch_ctl(RARCH_CTL_SET_WINDOWED_SCALE, &idx); return true; } size_t command_event_save_auto_state(void) { size_t _len; runloop_state_t *runloop_st = runloop_state_get_ptr(); char savestate_name_auto[PATH_MAX_LENGTH]; if (runloop_st->entry_state_slot) return 0; if (!core_info_current_supports_savestate()) return 0; if (string_is_empty(path_basename(path_get(RARCH_PATH_BASENAME)))) return 0; _len = strlcpy(savestate_name_auto, runloop_st->name.savestate, sizeof(savestate_name_auto)); _len += strlcpy(savestate_name_auto + _len, ".auto", sizeof(savestate_name_auto) - _len); if (content_auto_save_state((const char*)savestate_name_auto)) RARCH_LOG("[State]: %s \"%s\" %s.\n", msg_hash_to_str(MSG_AUTO_SAVE_STATE_TO), savestate_name_auto, "succeeded"); else RARCH_LOG("[State]: %s \"%s\" %s.\n", msg_hash_to_str(MSG_AUTO_SAVE_STATE_TO), savestate_name_auto, "failed"); return _len; } #ifdef HAVE_CHEATS void command_event_init_cheats( bool apply_cheats_after_load, const char *path_cheat_db, void *bsv_movie_data) { #ifdef HAVE_NETWORKING bool allow_cheats = !netplay_driver_ctl( RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL); #else bool allow_cheats = true; #endif #ifdef HAVE_BSV_MOVIE allow_cheats &= !(bsv_movie_data != NULL); #endif if (!allow_cheats) return; cheat_manager_alloc_if_empty(); cheat_manager_load_game_specific_cheats(path_cheat_db); if (apply_cheats_after_load) cheat_manager_apply_cheats(); } #endif bool command_event_load_entry_state(settings_t *settings) { char entry_state_path[PATH_MAX_LENGTH]; int entry_path_stats; runloop_state_t *runloop_st = runloop_state_get_ptr(); bool ret = false; if (!core_info_current_supports_savestate()) return false; #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) return false; #endif #ifdef HAVE_NETWORKING if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) return false; #endif entry_state_path[0] = '\0'; if (!runloop_get_entry_state_path( entry_state_path, sizeof(entry_state_path), runloop_st->entry_state_slot)) return false; entry_path_stats = path_stat(entry_state_path); if ((entry_path_stats & RETRO_VFS_STAT_IS_VALID) == 0 || (entry_path_stats & RETRO_VFS_STAT_IS_DIRECTORY) != 0) return false; ret = content_load_state(entry_state_path, false, true); RARCH_LOG("[State]: %s \"%s\".\n", msg_hash_to_str(MSG_FOUND_ENTRY_STATE_IN), entry_state_path); RARCH_LOG("[State]: %s \"%s\" %s.\n", msg_hash_to_str(MSG_LOADING_ENTRY_STATE_FROM), entry_state_path, ret ? "succeeded" : "failed" ); if (ret) configuration_set_int(settings, settings->ints.state_slot, runloop_st->entry_state_slot); return ret; } void command_event_load_auto_state(void) { size_t _len; char savestate_name_auto[PATH_MAX_LENGTH]; runloop_state_t *runloop_st = runloop_state_get_ptr(); if (!core_info_current_supports_savestate()) return; #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) return; #endif #ifdef HAVE_NETWORKING if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) return; #endif _len = strlcpy(savestate_name_auto, runloop_st->name.savestate, sizeof(savestate_name_auto)); strlcpy(savestate_name_auto + _len, ".auto", sizeof(savestate_name_auto) - _len); if (!path_is_valid(savestate_name_auto)) return; RARCH_LOG("[State]: %s \"%s\".\n", msg_hash_to_str(MSG_FOUND_AUTO_SAVESTATE_IN), savestate_name_auto); if ((content_load_state(savestate_name_auto, false, true))) RARCH_LOG("[State]: %s \"%s\" %s.\n", msg_hash_to_str(MSG_AUTOLOADING_SAVESTATE_FROM), savestate_name_auto, "succeeded"); else RARCH_LOG("[State]: %s \"%s\" %s.\n", msg_hash_to_str(MSG_AUTOLOADING_SAVESTATE_FROM), savestate_name_auto, "failed"); } /** * Scans existing states to determine which one should be loaded * and which one can be deleted, using savestate wraparound if * enabled. * * @param settings The usual RetroArch settings ptr. * @param last_index Return value for load slot. * @param @s Return value for file name that should be removed. */ static void scan_states(settings_t *settings, unsigned *last_index, char *s) { /* Base name of 128 may be too short for some (<<1%) of the tosec-based file names, but in practice truncating will not lead to mismatch */ char state_base[128]; char state_dir[DIR_MAX_LENGTH]; runloop_state_t *runloop_st = runloop_state_get_ptr(); bool show_hidden_files = settings->bools.show_hidden_files; unsigned savestate_max_keep = settings->uints.savestate_max_keep; int curr_state_slot = settings->ints.state_slot; unsigned max_idx = 0; unsigned loa_idx = 0; unsigned gap_idx = UINT_MAX; unsigned del_idx = UINT_MAX; retro_bits_512_t slot_mapping_low = {0}; retro_bits_512_t slot_mapping_high = {0}; struct string_list *dir_list = NULL; const char *savefile_root = NULL; size_t savefile_root_length = 0; size_t i, cnt = 0; size_t cnt_in_range = 0; fill_pathname_basedir(state_dir, runloop_st->name.savestate, sizeof(state_dir)); if (!(dir_list = dir_list_new_special(state_dir, DIR_LIST_PLAIN, NULL, show_hidden_files))) return; fill_pathname_base(state_base, runloop_st->name.savestate, sizeof(state_base)); for (i = 0; i < dir_list->size; i++) { unsigned idx; size_t _len; char elem_base[128]; const char *ext = NULL; const char *end = NULL; const char *dir_elem = dir_list->elems[i].data; if (string_is_empty(dir_elem)) continue; _len = strlen(dir_elem); fill_pathname_base(elem_base, dir_elem, sizeof(elem_base)); /* Only consider files with a '.state' extension * > i.e. Ignore '.state.auto', '.state.bak', etc. */ ext = path_get_extension(elem_base); if ( string_is_empty(ext) || !string_starts_with_size(ext, "state", STRLEN_CONST("state"))) continue; /* Check whether this file is associated with * the current content */ if (!string_starts_with(elem_base, state_base)) continue; /* This looks like a valid savestate */ /* Save filename root and length (once) */ if (savefile_root_length == 0) { savefile_root = dir_elem; savefile_root_length = _len; } /* Decode the savestate index */ end = dir_elem + _len; while ((end > dir_elem) && ISDIGIT((int)end[-1])) { end--; if (savefile_root == dir_elem) savefile_root_length--; } idx = string_to_unsigned(end); /* Simple administration: max, total. */ if (idx > max_idx) max_idx = idx; cnt++; if (idx <= savestate_max_keep) cnt_in_range++; /* Maintain a 2x512 bit map of occupied save states */ if (idx < 512) BIT512_SET(slot_mapping_low,idx); else if (idx < 1024) BIT512_SET(slot_mapping_high, idx - 512); } /* Next loop on the bitmap, since the file system may have presented the files in any order above */ for (i = 0; i <= savestate_max_keep; i++) { /* Unoccupied save slots */ if ( (i < 512 && !BIT512_GET(slot_mapping_low, i)) || (i > 511 && !BIT512_GET(slot_mapping_high, i-512))) { /* Gap index: lowest free slot in the wraparound range */ if (gap_idx == UINT_MAX) gap_idx = (unsigned)i; } else /* Occupied save slots */ { /* Del index: first occupied slot in the wraparound range, after gap index */ if ( gap_idx < UINT_MAX && del_idx == UINT_MAX) del_idx = (unsigned)i; } } /* Special cases of wraparound */ /* No previous savestate - set to end, so that first save goes to 0 */ if (cnt_in_range == 0) { if (cnt == 0) loa_idx = savestate_max_keep; /* Transient: nothing in current range, but something is present * higher up -> load that */ else loa_idx = max_idx; gap_idx = savestate_max_keep; del_idx = savestate_max_keep; } /* No gap was found - deduct from current index or default and set (missing) gap index to be deleted */ else if (gap_idx == UINT_MAX) { /* Transient: no gap, and max is higher than currently * allowed -> load that, but wrap around so that next * time gap will be present */ if (max_idx > savestate_max_keep) { loa_idx = max_idx; gap_idx = 1; } /* Current index is in range, so let's assume it is correct */ else if ( (unsigned)curr_state_slot < savestate_max_keep) { loa_idx = curr_state_slot; gap_idx = curr_state_slot + 1; } else { loa_idx = savestate_max_keep; gap_idx = 0; } del_idx = gap_idx; } /* Gap was found */ else { /* No candidate to delete */ /* Either gap is at the end of the range: wraparound. or there is no better idea than the lowest index */ if (del_idx == UINT_MAX) del_idx = 0; /* Adjust load index */ if (gap_idx == 0) loa_idx = savestate_max_keep; else loa_idx = gap_idx - 1; } RARCH_DBG("[State]: Save state scanning finished, used slots (in range): " "%d (%d), max:%d, load index %d, gap index %d, delete index %d.\n", cnt, cnt_in_range, max_idx, loa_idx, gap_idx, del_idx); if (last_index) *last_index = loa_idx; if ( s && cnt_in_range >= savestate_max_keep) { strlcpy(s, savefile_root, savefile_root_length + 1); /* ".state0" is just ".state" instead, so don't print that. */ if (del_idx > 0) snprintf(s + savefile_root_length, 5, "%d", del_idx); } dir_list_free(dir_list); } /** * Determines next savestate slot in case of auto-increment, * i.e. save state scanning was done already earlier. * Logic moved here so that all save state wraparound code is * in this file. * * @param settings The usual RetroArch settings ptr. * @return \c The next savestate slot. */ int command_event_get_next_savestate_auto_index(settings_t *settings) { unsigned savestate_max_keep = settings->uints.savestate_max_keep; int new_state_slot = settings->ints.state_slot + 1; /* If previous save was above the wraparound range, or it overflows, return to the start of the range. */ if ( (savestate_max_keep > 0) && (unsigned)new_state_slot > savestate_max_keep) return 0; return new_state_slot; } /** * Determines most recent savestate slot in case of content load. * * @param settings The usual RetroArch settings ptr. * @return \c The most recent savestate slot. */ void command_event_set_savestate_auto_index(settings_t *settings) { unsigned max_idx = 0; bool savestate_auto_index = settings->bools.savestate_auto_index; if (!savestate_auto_index) { /* Reset savestate index to 0 when loading content. */ configuration_set_int(settings, settings->ints.state_slot, 0); return; } scan_states(settings, &max_idx, NULL); configuration_set_int(settings, settings->ints.state_slot, max_idx); RARCH_LOG("[State]: %s: #%d.\n", msg_hash_to_str(MSG_FOUND_LAST_STATE_SLOT), max_idx); } /** * Deletes the oldest save state and its thumbnail, if needed. * * @param settings The usual RetroArch settings ptr. */ static void command_event_set_savestate_garbage_collect(settings_t *settings) { size_t i; char state_to_delete[PATH_MAX_LENGTH] = {0}; scan_states(settings, NULL, state_to_delete); /* Only delete one save state per save action * > Conservative behaviour, designed to minimise * the risk of deleting multiple incorrect files * in case of accident */ if (!string_is_empty(state_to_delete)) { filestream_delete(state_to_delete); RARCH_DBG("[State]: Garbage collect, deleting \"%s\".\n",state_to_delete); /* Construct the save state thumbnail name * and delete that one as well. */ i = strlen(state_to_delete); strlcpy(state_to_delete + i,".png",STRLEN_CONST(".png")+1); filestream_delete(state_to_delete); RARCH_DBG("[State]: Garbage collect, deleting \"%s\".\n",state_to_delete); } } void command_event_set_replay_auto_index(settings_t *settings) { size_t i; char state_base[128]; char state_dir[DIR_MAX_LENGTH]; struct string_list *dir_list = NULL; unsigned max_idx = 0; runloop_state_t *runloop_st = runloop_state_get_ptr(); bool replay_auto_index = settings->bools.replay_auto_index; bool show_hidden_files = settings->bools.show_hidden_files; if (!replay_auto_index) return; /* Find the file in the same directory as runloop_st->names.replay * with the largest numeral suffix. * * E.g. /foo/path/content.replay will try to find * /foo/path/content.replay%d, where %d is the largest number available. */ fill_pathname_basedir(state_dir, runloop_st->name.replay, sizeof(state_dir)); if (!(dir_list = dir_list_new_special(state_dir, DIR_LIST_PLAIN, NULL, show_hidden_files))) return; fill_pathname_base(state_base, runloop_st->name.replay, sizeof(state_base)); for (i = 0; i < dir_list->size; i++) { unsigned idx; char elem_base[128] = {0}; const char *end = NULL; const char *dir_elem = dir_list->elems[i].data; size_t _len = fill_pathname_base(elem_base, dir_elem, sizeof(elem_base)); if (strstr(elem_base, state_base) != elem_base) continue; end = dir_elem + _len; while ((end > dir_elem) && ISDIGIT((int)end[-1])) end--; idx = (unsigned)strtoul(end, NULL, 0); if (idx > max_idx) max_idx = idx; } dir_list_free(dir_list); configuration_set_int(settings, settings->ints.replay_slot, max_idx); if (max_idx) RARCH_LOG("[Replay]: %s: #%d\n", msg_hash_to_str(MSG_FOUND_LAST_REPLAY_SLOT), max_idx); } void command_event_set_replay_garbage_collect( unsigned max_to_keep, bool show_hidden_files ) { /* TODO: debugme */ size_t i, cnt = 0; char tmp[DIR_MAX_LENGTH]; runloop_state_t *runloop_st = runloop_state_get_ptr(); struct string_list *dir_list = NULL; unsigned min_idx = UINT_MAX; const char *oldest_save = NULL; /* Similar to command_event_set_replay_auto_index(), * this will find the lowest numbered replay */ fill_pathname_basedir(tmp, runloop_st->name.replay, sizeof(tmp)); if (!(dir_list = dir_list_new_special(tmp, DIR_LIST_PLAIN, NULL, show_hidden_files))) return; fill_pathname_base(tmp, runloop_st->name.replay, sizeof(tmp)); for (i = 0; i < dir_list->size; i++) { unsigned idx; size_t _len; char elem_base[128]; const char *ext = NULL; const char *end = NULL; const char *dir_elem = dir_list->elems[i].data; if (string_is_empty(dir_elem)) continue; _len = fill_pathname_base(elem_base, dir_elem, sizeof(elem_base)); /* Only consider files with a '.replayXX' extension * > i.e. Ignore '.replay.auto', '.replay.bak', etc. */ ext = path_get_extension(elem_base); if ( string_is_empty(ext) || !string_starts_with_size(ext, "replay", STRLEN_CONST("REPLAY"))) continue; /* Check whether this file is associated with * the current content */ if (!string_starts_with(elem_base, tmp)) continue; /* This looks like a valid save */ cnt++; /* > Get index */ end = dir_elem + _len; while ((end > dir_elem) && ISDIGIT((int)end[-1])) end--; idx = string_to_unsigned(end); /* > Check if this is the lowest index so far */ if (idx < min_idx) { min_idx = idx; oldest_save = dir_elem; } } /* Only delete one save state per save action * > Conservative behaviour, designed to minimise * the risk of deleting multiple incorrect files * in case of accident */ if (!string_is_empty(oldest_save) && (cnt > max_to_keep)) filestream_delete(oldest_save); dir_list_free(dir_list); } #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) bool command_set_shader(command_t *cmd, const char *arg) { enum rarch_shader_type type = video_shader_parse_type(arg); settings_t *settings = config_get_ptr(); if (!string_is_empty(arg)) { gfx_ctx_flags_t flags; flags.flags = 0; video_context_driver_get_flags(&flags); if (!BIT32_GET(flags.flags, video_shader_type_to_flag(type))) return false; /* rebase on shader directory */ if (!path_is_absolute(arg)) { char abs_arg[PATH_MAX_LENGTH]; const char *ref_path = settings->paths.directory_video_shader; fill_pathname_join_special(abs_arg, ref_path, arg, sizeof(abs_arg)); return video_shader_apply_shader(settings, type, abs_arg, true); } } return video_shader_apply_shader(settings, type, arg, true); } #endif #ifdef HAVE_CONFIGFILE bool command_event_save_core_config( const char *dir_menu_config, const char *rarch_path_config) { char msg[128]; char config_dir[DIR_MAX_LENGTH]; char config_path[PATH_MAX_LENGTH]; char config_name[NAME_MAX_LENGTH]; size_t _len = 0; bool new_path_available = false; bool overrides_active = false; const char *core_path = NULL; runloop_state_t *runloop_st = runloop_state_get_ptr(); msg[0] = '\0'; if (!string_is_empty(dir_menu_config)) _len = strlcpy(config_dir, dir_menu_config, sizeof(config_dir)); else if (!string_is_empty(rarch_path_config)) /* Fallback */ _len = fill_pathname_basedir(config_dir, rarch_path_config, sizeof(config_dir)); if (_len == 0) { const char *_msg = msg_hash_to_str(MSG_CONFIG_DIRECTORY_NOT_SET); runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); RARCH_ERR("[Config]: %s\n", _msg); return false; } core_path = path_get(RARCH_PATH_CORE); /* Infer file name based on libretro core. */ if (path_is_valid(core_path)) { unsigned i; char tmp[PATH_MAX_LENGTH + 8]; RARCH_LOG("[Config]: %s\n", msg_hash_to_str(MSG_USING_CORE_NAME_FOR_NEW_CONFIG)); fill_pathname(config_name, path_basename(core_path), "", sizeof(config_name)); fill_pathname_join_special(config_path, config_dir, config_name, sizeof(config_path)); /* In case of collision, find an alternative name. */ for (i = 0; i < 16; i++) { size_t __len = strlcpy(tmp, config_path, sizeof(tmp)); if (i) __len += snprintf(tmp + __len, sizeof(tmp) - __len, "-%u", i); strlcpy(tmp + __len, ".cfg", sizeof(tmp) - __len); if (!path_is_valid(tmp)) { strlcpy(config_path, tmp, sizeof(config_path)); new_path_available = true; break; } } } if (!new_path_available) { /* Fallback to system time... */ RARCH_WARN("[Config]: %s\n", msg_hash_to_str(MSG_CANNOT_INFER_NEW_CONFIG_PATH)); fill_dated_filename(config_name, ".cfg", sizeof(config_name)); fill_pathname_join_special(config_path, config_dir, config_name, sizeof(config_path)); } if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE) { /* Overrides block config file saving, * make it appear as overrides weren't enabled * for a manual save. */ runloop_st->flags &= ~RUNLOOP_FLAG_OVERRIDES_ACTIVE; overrides_active = true; } #ifdef HAVE_CONFIGFILE _len = command_event_save_config(config_path, msg, sizeof(msg)); #endif if (_len > 0) runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); if (overrides_active) runloop_st->flags |= RUNLOOP_FLAG_OVERRIDES_ACTIVE; else runloop_st->flags &= ~RUNLOOP_FLAG_OVERRIDES_ACTIVE; return true; } void command_event_save_current_config(enum override_type type) { runloop_state_t *runloop_st = runloop_state_get_ptr(); switch (type) { default: case OVERRIDE_NONE: { size_t _len; char msg[256]; msg[0] = '\0'; if (path_is_empty(RARCH_PATH_CONFIG)) { _len = strlcpy(msg, "Config directory not set, cannot save configuration.", sizeof(msg)); runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } else { if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE) _len = strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_ACTIVE_NOT_SAVING), sizeof(msg)); else _len = command_event_save_config(path_get(RARCH_PATH_CONFIG), msg, sizeof(msg)); runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } } break; case OVERRIDE_GAME: case OVERRIDE_CORE: case OVERRIDE_CONTENT_DIR: { size_t _len; char msg[256]; int8_t ret = config_save_overrides(type, &runloop_st->system, false, NULL); switch (ret) { case 1: _len = strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_SAVED_SUCCESSFULLY), sizeof(msg)); /* set overrides to active so the original config can be restored after closing content */ runloop_st->flags |= RUNLOOP_FLAG_OVERRIDES_ACTIVE; break; case -1: _len = strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_NOT_SAVED), sizeof(msg)); break; default: case 0: _len = strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_ERROR_SAVING), sizeof(msg)); break; } RARCH_LOG("[Overrides]: %s\n", msg); runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); #ifdef HAVE_MENU { struct menu_state *menu_st = menu_state_get_ptr(); menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH | MENU_ST_FLAG_PREVENT_POPULATE; } #endif } break; } } void command_event_remove_current_config(enum override_type type) { runloop_state_t *runloop_st = runloop_state_get_ptr(); switch (type) { default: case OVERRIDE_NONE: break; case OVERRIDE_GAME: case OVERRIDE_CORE: case OVERRIDE_CONTENT_DIR: { size_t _len; char msg[256]; if (config_save_overrides(type, &runloop_st->system, true, NULL)) _len = strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_REMOVED_SUCCESSFULLY), sizeof(msg)); else _len = strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_ERROR_REMOVING), sizeof(msg)); RARCH_LOG("[Overrides]: %s\n", msg); runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); #ifdef HAVE_MENU { struct menu_state *menu_st = menu_state_get_ptr(); menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH | MENU_ST_FLAG_PREVENT_POPULATE; } #endif } break; } } #endif bool command_event_main_state(unsigned cmd) { char msg[128]; char state_path[16384]; /* TODO/FIXME - reduce this */ size_t _len = 0; settings_t *settings = config_get_ptr(); bool savestates_enabled = core_info_current_supports_savestate(); bool ret = false; state_path[0] = msg[0] = '\0'; if (savestates_enabled) { size_t info_size; runloop_get_current_savestate_path(state_path, sizeof(state_path)); info_size = core_serialize_size(); savestates_enabled = (info_size > 0); } /* TODO: Load state should act in one of three ways: - [X] Not during recording or playback: normally - [-] During playback: If the state is part of this replay, go back to that state and rewind the replay (not yet implemented); otherwise halt playback and go to that state normally. - [-] During recording: If the state is part of this replay, go back to that state and rewind the replay, clobbering the stuff in between then and now (not yet implemented); if the state is not part of the replay, do nothing and log a warning. */ if (savestates_enabled) { switch (cmd) { case CMD_EVENT_SAVE_STATE: case CMD_EVENT_SAVE_STATE_TO_RAM: { /* TODO: Saving state during recording should associate * the state with the replay. */ video_driver_state_t *video_st = video_state_get_ptr(); bool savestate_auto_index = settings->bools.savestate_auto_index; unsigned savestate_max_keep = settings->uints.savestate_max_keep; bool frame_time_counter_reset_after_save_state = settings->bools.frame_time_counter_reset_after_save_state; if (cmd == CMD_EVENT_SAVE_STATE) content_save_state(state_path, true); else content_save_state_to_ram(); /* Clean up excess savestates if necessary */ if (savestate_auto_index && (savestate_max_keep > 0)) command_event_set_savestate_garbage_collect(settings); if (frame_time_counter_reset_after_save_state) video_st->frame_time_count = 0; ret = true; } break; case CMD_EVENT_LOAD_STATE: case CMD_EVENT_LOAD_STATE_FROM_RAM: { bool res = false; if (cmd == CMD_EVENT_LOAD_STATE) res = content_load_state(state_path, false, false); else res = content_load_state_from_ram(); if (res) { command_post_state_loaded(); ret = true; } } break; case CMD_EVENT_UNDO_LOAD_STATE: { /* TODO: To support this through re-recording would take some * care around moving the replay recording forward to the time * when the undo happened, which would need undo support for * replays. For now, forbid it during recording and halt * playback. */ #ifdef HAVE_BSV_MOVIE input_driver_state_t *input_st = input_state_get_ptr(); if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING) { RARCH_ERR("[State]: Can't undo load state during movie record.\n"); return false; } if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK) { RARCH_LOG("[State]: Undo load state during movie playback, halting playback.\n"); movie_stop(input_st); } #endif _len = command_event_undo_load_state(msg, sizeof(msg)); ret = true; break; } case CMD_EVENT_UNDO_SAVE_STATE: _len = command_event_undo_save_state(msg, sizeof(msg)); ret = true; break; } } else _len = strlcpy(msg, msg_hash_to_str( MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES), sizeof(msg)); if (_len > 0) { runloop_msg_queue_push(msg, _len, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); RARCH_LOG("[State]: %s\n", msg); } return ret; } bool command_event_disk_control_append_image( const char *path) { runloop_state_t *runloop_st = runloop_state_get_ptr(); rarch_system_info_t *sys_info = runloop_st ? (rarch_system_info_t*)&runloop_st->system : NULL; if ( !sys_info || !disk_control_append_image(&sys_info->disk_control, path)) return false; #ifdef HAVE_THREADS if (runloop_st->flags & RUNLOOP_FLAG_USE_SRAM) autosave_deinit(); #endif /* TODO/FIXME: Need to figure out what to do with subsystems case. */ if (path_is_empty(RARCH_PATH_SUBSYSTEM)) { /* Update paths for our new image. * If we actually use append_image, we assume that we * started out in a single disk case, and that this way * of doing it makes the most sense. */ path_set(RARCH_PATH_NAMES, path); runloop_path_fill_names(); } command_event(CMD_EVENT_AUTOSAVE_INIT, NULL); return true; } void command_event_reinit(const int flags) { settings_t *settings = config_get_ptr(); input_driver_state_t *input_st = input_state_get_ptr(); video_driver_state_t *video_st = video_state_get_ptr(); #ifdef HAVE_MENU gfx_display_t *p_disp = disp_get_ptr(); struct menu_state *menu_st = menu_state_get_ptr(); bool video_fullscreen = settings->bools.video_fullscreen; bool adaptive_vsync = settings->bools.video_adaptive_vsync; unsigned swap_interval_config = settings->uints.video_swap_interval; #endif enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_REAPPLY; const input_device_driver_t *joypad = input_st->primary_joypad; #ifdef HAVE_MFI const input_device_driver_t *sec_joypad = input_st->secondary_joypad; #else const input_device_driver_t *sec_joypad = NULL; #endif video_driver_reinit(flags); /* Poll input to avoid possibly stale data to corrupt things. */ if (joypad && joypad->poll) joypad->poll(); if (sec_joypad && sec_joypad->poll) sec_joypad->poll(); if (input_st->current_driver && input_st->current_driver->poll) input_st->current_driver->poll(input_st->current_data); command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd); #ifdef HAVE_MENU p_disp->flags |= GFX_DISP_FLAG_FB_DIRTY; if (video_fullscreen) { if ( video_st->poke && video_st->poke->show_mouse) video_st->poke->show_mouse(video_st->data, false); } if ( (menu_st->flags & MENU_ST_FLAG_ALIVE) && video_st->current_video->set_nonblock_state) video_st->current_video->set_nonblock_state( video_st->data, false, video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) && adaptive_vsync, runloop_get_video_swap_interval(swap_interval_config)); #endif }