/* RetroArch - A frontend for libretro. * Copyright (C) 2011-2016 - Daniel De Matteis * Copyright (C) 2016 - Brad Parker * * 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 #ifdef _WIN32 #include #else #include #endif #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_COMMAND #ifdef HAVE_NETWORKING #include #include #endif #include #endif #ifdef HAVE_CHEEVOS #include "cheevos/cheevos.h" #endif #ifdef HAVE_MENU #include "menu/menu_driver.h" #include "menu/menu_content.h" #include "menu/menu_display.h" #include "menu/menu_shader.h" #include "menu/widgets/menu_dialog.h" #endif #ifdef HAVE_NETWORKING #include #include "network/netplay/netplay.h" #endif #include "command.h" #include "defaults.h" #include "driver.h" #include "frontend/frontend_driver.h" #include "audio/audio_driver.h" #include "record/record_driver.h" #include "file_path_special.h" #include "autosave.h" #include "core_info.h" #include "core_type.h" #include "performance_counters.h" #include "dynamic.h" #include "content.h" #include "dirs.h" #include "movie.h" #include "paths.h" #include "msg_hash.h" #include "retroarch.h" #include "managers/cheat_manager.h" #include "managers/state_manager.h" #include "ui/ui_companion_driver.h" #include "tasks/tasks_internal.h" #include "list_special.h" #include "core.h" #include "verbosity.h" #include "runloop.h" #include "configuration.h" #include "input/input_remapping.h" #define DEFAULT_NETWORK_CMD_PORT 55355 #define STDIN_BUF_SIZE 4096 struct command { bool local_enable; #ifdef HAVE_STDIN_CMD bool stdin_enable; char stdin_buf[STDIN_BUF_SIZE]; size_t stdin_buf_ptr; #endif #if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) int net_fd; #endif bool state[RARCH_BIND_LIST_END]; }; enum cmd_source_t { CMD_NONE = 0, CMD_STDIN, CMD_NETWORK }; #if defined(HAVE_STDIN_CMD) || defined(HAVE_NETWORK_CMD) && defined(HAVE_NETWORKING) static enum cmd_source_t lastcmd_source; #endif #if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) static int lastcmd_net_fd; static struct sockaddr_storage lastcmd_net_source; static socklen_t lastcmd_net_source_len; #endif #ifdef HAVE_CHEEVOS #if defined(HAVE_STDIN_CMD) || defined(HAVE_NETWORK_CMD) && defined(HAVE_NETWORKING) static bool command_reply(const char * data, size_t len) { #ifdef HAVE_STDIN_CMD if (lastcmd_source == CMD_STDIN) { fwrite(data, 1,len, stdout); return true; } #endif #if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) if (lastcmd_source == CMD_NETWORK) { sendto(lastcmd_net_fd, data, len, 0, (struct sockaddr*)&lastcmd_net_source, lastcmd_net_source_len); return true; } #endif return false; } #endif #endif struct cmd_map { const char *str; unsigned id; }; struct cmd_action_map { const char *str; bool (*action)(const char *arg); const char *arg_desc; }; #ifdef HAVE_COMMAND static bool command_set_shader(const char *arg) { char msg[256]; enum rarch_shader_type type = RARCH_SHADER_NONE; switch (msg_hash_to_file_type(msg_hash_calculate(path_get_extension(arg)))) { case FILE_TYPE_SHADER_GLSL: case FILE_TYPE_SHADER_PRESET_GLSLP: type = RARCH_SHADER_GLSL; break; case FILE_TYPE_SHADER_CG: case FILE_TYPE_SHADER_PRESET_CGP: type = RARCH_SHADER_CG; break; case FILE_TYPE_SHADER_SLANG: case FILE_TYPE_SHADER_PRESET_SLANGP: type = RARCH_SHADER_SLANG; break; default: return false; } snprintf(msg, sizeof(msg), "Shader: \"%s\"", arg); runloop_msg_queue_push(msg, 1, 120, true); RARCH_LOG("%s \"%s\".\n", msg_hash_to_str(MSG_APPLYING_SHADER), arg); return video_driver_set_shader(type, arg); } #ifdef HAVE_CHEEVOS static bool command_read_ram(const char *arg) { cheevos_var_t var; const uint8_t * data; unsigned nbytes; unsigned i; char reply[256]; char *reply_at = NULL; strlcpy(reply, "READ_CORE_RAM ", sizeof(reply)); reply_at = reply + strlen("READ_CORE_RAM "); strlcpy(reply_at, arg, sizeof(reply)-strlen(reply)); cheevos_parse_guest_addr(&var, strtoul(reply_at, (char**)&reply_at, 16)); data = cheevos_get_memory(&var); if (data) { unsigned nbytes = strtol(reply_at, NULL, 10); for (i=0;i" }, #ifdef HAVE_CHEEVOS { "READ_CORE_RAM", command_read_ram, "
" }, { "WRITE_CORE_RAM", command_write_ram, "
..." }, #endif }; static const struct cmd_map map[] = { { "FAST_FORWARD", RARCH_FAST_FORWARD_KEY }, { "FAST_FORWARD_HOLD", RARCH_FAST_FORWARD_HOLD_KEY }, { "LOAD_STATE", RARCH_LOAD_STATE_KEY }, { "SAVE_STATE", RARCH_SAVE_STATE_KEY }, { "FULLSCREEN_TOGGLE", RARCH_FULLSCREEN_TOGGLE_KEY }, { "QUIT", RARCH_QUIT_KEY }, { "STATE_SLOT_PLUS", RARCH_STATE_SLOT_PLUS }, { "STATE_SLOT_MINUS", RARCH_STATE_SLOT_MINUS }, { "REWIND", RARCH_REWIND }, { "MOVIE_RECORD_TOGGLE", RARCH_MOVIE_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 }, { "NETPLAY_FLIP", RARCH_NETPLAY_FLIP }, { "NETPLAY_GAME_WATCH", RARCH_NETPLAY_GAME_WATCH }, { "SLOWMOTION", RARCH_SLOWMOTION }, { "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 }, { "GAME_FOCUS_TOGGLE", RARCH_GAME_FOCUS_TOGGLE }, { "MENU_TOGGLE", RARCH_MENU_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 }, { "MENU_B", RETRO_DEVICE_ID_JOYPAD_B }, }; 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 (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(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, enum cmd_source_t source) { char *save = NULL; const char *tok = strtok_r(buf, "\n", &save); lastcmd_source = source; while (tok) { command_parse_sub_msg(handle, tok); tok = strtok_r(NULL, "\n", &save); } lastcmd_source = CMD_NONE; } #if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) static bool command_network_init(command_t *handle, uint16_t port) { int fd; struct addrinfo *res = NULL; RARCH_LOG("%s %hu.\n", msg_hash_to_str(MSG_BRINGING_UP_COMMAND_INTERFACE_ON_PORT), (unsigned short)port); fd = socket_init((void**)&res, port, NULL, SOCKET_TYPE_DATAGRAM); 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 send_udp_packet(const char *host, uint16_t port, const char *msg) { char port_buf[16] = {0}; struct addrinfo hints = {0}; struct addrinfo *res = NULL; const struct addrinfo *tmp = NULL; int fd = -1; bool ret = true; hints.ai_socktype = SOCK_DGRAM; snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port); if (getaddrinfo_retro(host, port_buf, &hints, &res) < 0) return false; /* Send to all possible targets. * "localhost" might resolve to several different IPs. */ tmp = (const struct addrinfo*)res; while (tmp) { ssize_t len, ret_len; fd = socket(tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol); if (fd < 0) { ret = false; goto end; } len = strlen(msg); ret_len = sendto(fd, msg, len, 0, tmp->ai_addr, tmp->ai_addrlen); if (ret_len < len) { ret = false; goto end; } socket_close(fd); fd = -1; tmp = tmp->ai_next; } end: freeaddrinfo_retro(res); if (fd >= 0) socket_close(fd); return ret; } 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 < sizeof(map) / sizeof(map[0]); i++) RARCH_ERR("\t\t%s\n", map[i].str); for (i = 0; i < sizeof(action_map) / sizeof(action_map[0]); 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_) { bool ret = false; char *command = NULL; char *save = NULL; const char *cmd = NULL; const char *host = NULL; const char *port_ = NULL; uint16_t port = DEFAULT_NETWORK_CMD_PORT; if (!network_init()) return false; if (!(command = strdup(cmd_))) return false; cmd = strtok_r(command, ";", &save); if (cmd) host = strtok_r(NULL, ";", &save); if (host) port_ = strtok_r(NULL, ";", &save); if (!host) { #ifdef _WIN32 host = "127.0.0.1"; #else host = "localhost"; #endif } if (port_) port = strtoul(port_, NULL, 0); if (cmd) { RARCH_LOG("%s: \"%s\" to %s:%hu\n", msg_hash_to_str(MSG_SENDING_COMMAND), cmd, host, (unsigned short)port); ret = command_verify(cmd) && send_udp_packet(host, port, cmd); } free(command); return ret; } #ifdef HAVE_COMMAND static void command_network_poll(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'; lastcmd_net_fd = handle->net_fd; lastcmd_net_source_len = sizeof(lastcmd_net_source); ret = recvfrom(handle->net_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&lastcmd_net_source, &lastcmd_net_source_len); if (ret <= 0) break; buf[ret] = '\0'; command_parse_msg(handle, buf, CMD_NETWORK); } } #endif #endif #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; } #endif command_t *command_new(bool local_enable) { command_t *handle = (command_t*)calloc(1, sizeof(*handle)); if (!handle) return NULL; handle->local_enable = local_enable; return handle; } bool command_network_new( command_t *handle, bool stdin_enable, bool network_enable, uint16_t port) { if (!handle) return false; #if defined(HAVE_NETWORKING) && defined(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_NETWORKING)) || defined(HAVE_STDIN_CMD) error: command_free(handle); return false; #endif } #ifdef HAVE_STDIN_CMD #ifdef _WIN32 static size_t read_stdin(char *buf, size_t size) { DWORD i; DWORD has_read = 0; DWORD avail = 0; bool echo = false; HANDLE hnd = GetStdHandle(STD_INPUT_HANDLE); if (hnd == INVALID_HANDLE_VALUE) return 0; /* Check first if we're a pipe * (not console). */ /* If not a pipe, check if we're running in a console. */ if (!PeekNamedPipe(hnd, NULL, 0, NULL, &avail, NULL)) { INPUT_RECORD recs[256]; bool has_key = false; DWORD mode = 0; DWORD has_read = 0; if (!GetConsoleMode(hnd, &mode)) return 0; if ((mode & (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)) && !SetConsoleMode(hnd, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))) return 0; /* Win32, Y U NO SANE NONBLOCK READ!? */ if (!PeekConsoleInput(hnd, recs, sizeof(recs) / sizeof(recs[0]), &has_read)) return 0; for (i = 0; i < has_read; i++) { /* Very crude, but should get the job done. */ if (recs[i].EventType == KEY_EVENT && recs[i].Event.KeyEvent.bKeyDown && (isgraph(recs[i].Event.KeyEvent.wVirtualKeyCode) || recs[i].Event.KeyEvent.wVirtualKeyCode == VK_RETURN)) { has_key = true; echo = true; avail = size; break; } } if (!has_key) { FlushConsoleInputBuffer(hnd); return 0; } } if (!avail) return 0; if (avail > size) avail = size; if (!ReadFile(hnd, buf, avail, &has_read, NULL)) return 0; for (i = 0; i < has_read; i++) if (buf[i] == '\r') buf[i] = '\n'; /* Console won't echo for us while in non-line mode, * so do it manually ... */ if (echo) { HANDLE hnd_out = GetStdHandle(STD_OUTPUT_HANDLE); if (hnd_out != INVALID_HANDLE_VALUE) { DWORD has_written; WriteConsole(hnd_out, buf, has_read, &has_written, NULL); } } return has_read; } #else static size_t read_stdin(char *buf, size_t size) { size_t has_read = 0; while (size) { ssize_t ret = read(STDIN_FILENO, buf, size); if (ret <= 0) break; buf += ret; has_read += ret; size -= ret; } return has_read; } #endif static void command_stdin_poll(command_t *handle) { ssize_t ret; ptrdiff_t msg_len; char *last_newline = NULL; if (!handle->stdin_enable) return; 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; command_parse_msg(handle, handle->stdin_buf, CMD_STDIN); memmove(handle->stdin_buf, last_newline, handle->stdin_buf_ptr - msg_len); handle->stdin_buf_ptr -= msg_len; } #endif #endif static void command_local_poll(command_t *handle) { if (!handle->local_enable) return; } bool command_poll(command_t *handle) { memset(handle->state, 0, sizeof(handle->state)); #if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) #ifdef HAVE_COMMAND command_network_poll(handle); #endif #endif #ifdef HAVE_STDIN_CMD command_stdin_poll(handle); #endif command_local_poll(handle); return true; } bool command_get(command_handle_t *handle) { if (!handle || !handle->handle) return false; return handle->id < RARCH_BIND_LIST_END && handle->handle->state[handle->id]; } bool command_set(command_handle_t *handle) { if (!handle || !handle->handle) return false; if (handle->id < RARCH_BIND_LIST_END) handle->handle->state[handle->id] = true; return true; } bool command_free(command_t *handle) { #if defined(HAVE_NETWORKING) && defined(HAVE_NETWORK_CMD) #ifdef HAVE_COMMAND if (handle && handle->net_fd >= 0) socket_close(handle->net_fd); #endif #endif free(handle); return true; } /** * command_event_disk_control_set_eject: * @new_state : Eject or close the virtual drive tray. * false (0) : Close * true (1) : Eject * @print_log : Show message onscreen. * * Ejects/closes of the virtual drive tray. **/ static void command_event_disk_control_set_eject(bool new_state, bool print_log) { char msg[128]; bool error = false; rarch_system_info_t *info = NULL; const struct retro_disk_control_callback *control = NULL; msg[0] = '\0'; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); if (info) control = (const struct retro_disk_control_callback*)&info->disk_control_cb; if (!control || !control->get_num_images) return; if (control->set_eject_state(new_state)) snprintf(msg, sizeof(msg), "%s %s", new_state ? msg_hash_to_str(MSG_DISK_EJECTED) : msg_hash_to_str(MSG_DISK_CLOSED), msg_hash_to_str(MSG_VIRTUAL_DISK_TRAY)); else { error = true; snprintf(msg, sizeof(msg), "%s %s %s", msg_hash_to_str(MSG_FAILED_TO), new_state ? "eject" : "close", msg_hash_to_str(MSG_VIRTUAL_DISK_TRAY)); } if (!string_is_empty(msg)) { if (error) RARCH_ERR("%s\n", msg); else RARCH_LOG("%s\n", msg); /* Only noise in menu. */ if (print_log) runloop_msg_queue_push(msg, 1, 180, true); } } /** * command_event_disk_control_set_index: * @idx : Index of disk to set as current. * * Sets current disk to @index. **/ static void command_event_disk_control_set_index(unsigned idx) { unsigned num_disks; char msg[128]; bool error = false; rarch_system_info_t *info = NULL; const struct retro_disk_control_callback *control = NULL; msg[0] = '\0'; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); if (info) control = (const struct retro_disk_control_callback*)&info->disk_control_cb; if (!control || !control->get_num_images) return; num_disks = control->get_num_images(); if (control->set_image_index(idx)) { if (idx < num_disks) snprintf(msg, sizeof(msg), "%s: %u/%u.", msg_hash_to_str(MSG_SETTING_DISK_IN_TRAY), idx + 1, num_disks); else strlcpy(msg, msg_hash_to_str(MSG_REMOVED_DISK_FROM_TRAY), sizeof(msg)); } else { if (idx < num_disks) snprintf(msg, sizeof(msg), "%s %u/%u.", msg_hash_to_str(MSG_FAILED_TO_SET_DISK), idx + 1, num_disks); else strlcpy(msg, msg_hash_to_str(MSG_FAILED_TO_REMOVE_DISK_FROM_TRAY), sizeof(msg)); error = true; } if (!string_is_empty(msg)) { if (error) RARCH_ERR("%s\n", msg); else RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 1, 180, true); } } /** * command_event_disk_control_append_image: * @path : Path to disk image. * * Appends disk image to disk image list. **/ static bool command_event_disk_control_append_image(const char *path) { unsigned new_idx; char msg[128]; struct retro_game_info info = {0}; const struct retro_disk_control_callback *control = NULL; rarch_system_info_t *sysinfo = NULL; msg[0] = '\0'; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &sysinfo); if (sysinfo) control = (const struct retro_disk_control_callback*) &sysinfo->disk_control_cb; if (!control) return false; command_event_disk_control_set_eject(true, false); control->add_image_index(); new_idx = control->get_num_images(); if (!new_idx) return false; new_idx--; info.path = path; control->replace_image_index(new_idx, &info); snprintf(msg, sizeof(msg), "%s: ", msg_hash_to_str(MSG_APPENDED_DISK)); strlcat(msg, path, sizeof(msg)); RARCH_LOG("%s\n", msg); runloop_msg_queue_push(msg, 0, 180, true); command_event(CMD_EVENT_AUTOSAVE_DEINIT, NULL); /* TODO: 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); path_fill_names(); } command_event(CMD_EVENT_AUTOSAVE_INIT, NULL); command_event_disk_control_set_index(new_idx); command_event_disk_control_set_eject(false, false); return true; } /** * command_event_check_disk_prev: * @control : Handle to disk control handle. * * Perform disk cycle to previous index action (Core Disk Options). **/ static void command_event_check_disk_prev( const struct retro_disk_control_callback *control) { unsigned num_disks = 0; unsigned current = 0; bool disk_prev_enable = false; if (!control || !control->get_num_images) return; if (!control->get_image_index) return; num_disks = control->get_num_images(); current = control->get_image_index(); disk_prev_enable = num_disks && num_disks != UINT_MAX; if (!disk_prev_enable) { RARCH_ERR("%s.\n", msg_hash_to_str(MSG_GOT_INVALID_DISK_INDEX)); return; } if (current > 0) current--; command_event_disk_control_set_index(current); } /** * command_event_check_disk_next: * @control : Handle to disk control handle. * * Perform disk cycle to next index action (Core Disk Options). **/ static void command_event_check_disk_next( const struct retro_disk_control_callback *control) { unsigned num_disks = 0; unsigned current = 0; bool disk_next_enable = false; if (!control || !control->get_num_images) return; if (!control->get_image_index) return; num_disks = control->get_num_images(); current = control->get_image_index(); disk_next_enable = num_disks && num_disks != UINT_MAX; if (!disk_next_enable) { RARCH_ERR("%s.\n", msg_hash_to_str(MSG_GOT_INVALID_DISK_INDEX)); return; } if (current < num_disks - 1) current++; command_event_disk_control_set_index(current); } /** * event_set_volume: * @gain : amount of gain to be applied to current volume level. * * Adjusts the current audio volume level. * **/ static void command_event_set_volume(float gain) { char msg[128]; settings_t *settings = config_get_ptr(); settings->audio.volume += gain; settings->audio.volume = MAX(settings->audio.volume, -80.0f); settings->audio.volume = MIN(settings->audio.volume, 12.0f); snprintf(msg, sizeof(msg), "%s: %.1f dB", msg_hash_to_str(MSG_AUDIO_VOLUME), settings->audio.volume); runloop_msg_queue_push(msg, 1, 180, true); RARCH_LOG("%s\n", msg); audio_driver_set_volume_gain(db_to_gain(settings->audio.volume)); } /** * command_event_init_controllers: * * Initialize libretro controllers. **/ static void command_event_init_controllers(void) { unsigned i; settings_t *settings = config_get_ptr(); rarch_system_info_t *info = NULL; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); for (i = 0; i < MAX_USERS; i++) { retro_ctx_controller_info_t pad; const char *ident = NULL; bool set_controller = false; const struct retro_controller_description *desc = NULL; unsigned device = settings->input.libretro_device[i]; if (info) { if (i < info->ports.size) desc = libretro_find_controller_description( &info->ports.data[i], device); } if (desc) ident = desc->desc; if (!ident) { /* 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 settings->input.libretro_device[i], * because any use of dummy core will reset this, * which is not a good idea. */ RARCH_WARN("Input device ID %u is unknown to this " "libretro implementation. Using RETRO_DEVICE_JOYPAD.\n", device); device = RETRO_DEVICE_JOYPAD; } ident = "Joypad"; } switch (device) { case RETRO_DEVICE_NONE: RARCH_LOG("%s %u.\n", msg_hash_to_str(MSG_VALUE_DISCONNECTING_DEVICE_FROM_PORT), i + 1); set_controller = true; break; case RETRO_DEVICE_JOYPAD: break; default: /* Some cores do not properly range check port argument. * This is broken behavior of course, but avoid breaking * cores needlessly. */ RARCH_LOG("%s %u: %s (ID: %u).\n", msg_hash_to_str(MSG_CONNECTING_TO_PORT), device, ident, i+1); set_controller = true; break; } if (set_controller) { pad.device = device; pad.port = i; core_set_controller_port_device(&pad); } } } static void command_event_deinit_core(bool reinit) { #ifdef HAVE_CHEEVOS cheevos_unload(); #endif core_unload_game(); core_unload(); core_uninit_symbols(); if (reinit) { int flags = DRIVERS_CMD_ALL; driver_ctl(RARCH_DRIVER_CTL_UNINIT, &flags); } command_event(CMD_EVENT_DISABLE_OVERRIDES, NULL); command_event(CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET, NULL); } static void command_event_init_cheats(void) { bool allow_cheats = true; #ifdef HAVE_NETWORKING allow_cheats &= !netplay_driver_ctl( RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL); #endif allow_cheats &= !bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL); if (!allow_cheats) return; /* TODO/FIXME - add some stuff here. */ } static void command_event_load_auto_state(void) { bool ret; char msg[128] = {0}; char savestate_name_auto[PATH_MAX_LENGTH] = {0}; settings_t *settings = config_get_ptr(); global_t *global = global_get_ptr(); #ifdef HAVE_NETWORKING if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) return; #endif #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return; #endif if (!settings->savestate_auto_load) return; if (global) fill_pathname_noext(savestate_name_auto, global->name.savestate, file_path_str(FILE_PATH_AUTO_EXTENSION), sizeof(savestate_name_auto)); if (!path_file_exists(savestate_name_auto)) return; ret = content_load_state(savestate_name_auto, false, true); RARCH_LOG("%s: %s\n", msg_hash_to_str(MSG_FOUND_AUTO_SAVESTATE_IN), savestate_name_auto); snprintf(msg, sizeof(msg), "%s \"%s\" %s.", msg_hash_to_str(MSG_AUTOLOADING_SAVESTATE_FROM), savestate_name_auto, ret ? "succeeded" : "failed"); RARCH_LOG("%s\n", msg); } static void command_event_set_savestate_auto_index(void) { size_t i; char state_dir[PATH_MAX_LENGTH] = {0}; char state_base[PATH_MAX_LENGTH] = {0}; struct string_list *dir_list = NULL; unsigned max_idx = 0; settings_t *settings = config_get_ptr(); global_t *global = global_get_ptr(); if (!settings->savestate_auto_index) return; if (global) { /* Find the file in the same directory as global->savestate_name * with the largest numeral suffix. * * E.g. /foo/path/content.state, will try to find * /foo/path/content.state%d, where %d is the largest number available. */ fill_pathname_basedir(state_dir, global->name.savestate, sizeof(state_dir)); fill_pathname_base(state_base, global->name.savestate, sizeof(state_base)); } dir_list = dir_list_new_special(state_dir, DIR_LIST_PLAIN, NULL); if (!dir_list) return; 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; fill_pathname_base(elem_base, dir_elem, sizeof(elem_base)); if (strstr(elem_base, state_base) != elem_base) continue; end = dir_elem + strlen(dir_elem); while ((end > dir_elem) && isdigit((int)end[-1])) end--; idx = strtoul(end, NULL, 0); if (idx > max_idx) max_idx = idx; } dir_list_free(dir_list); settings->state_slot = max_idx; RARCH_LOG("%s: #%d\n", msg_hash_to_str(MSG_FOUND_LAST_STATE_SLOT), settings->state_slot); } static bool event_init_content(void) { bool contentless = false; bool is_inited = false; content_get_status(&contentless, &is_inited); rarch_ctl(RARCH_CTL_SET_SRAM_ENABLE, NULL); /* No content to be loaded for dummy core, * just successfully exit. */ if (rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) return true; if (!contentless) path_fill_names(); if (!content_init()) return false; content_get_status(&contentless, &is_inited); command_event_set_savestate_auto_index(); if (event_load_save_files()) RARCH_LOG("%s.\n", msg_hash_to_str(MSG_SKIPPING_SRAM_LOAD)); command_event_load_auto_state(); command_event(CMD_EVENT_BSV_MOVIE_INIT, NULL); command_event(CMD_EVENT_NETPLAY_INIT, NULL); return true; } static bool command_event_init_core(enum rarch_core_type *data) { retro_ctx_environ_info_t info; settings_t *settings = config_get_ptr(); if (!core_init_symbols(data)) return false; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_INIT, NULL); /* auto overrides: apply overrides */ if(settings->auto_overrides_enable) { if (config_load_override()) runloop_ctl(RUNLOOP_CTL_SET_OVERRIDES_ACTIVE, NULL); else runloop_ctl(RUNLOOP_CTL_UNSET_OVERRIDES_ACTIVE, NULL); } /* Auto-remap: apply shader preset files */ if(settings->auto_shaders_enable) config_load_shader_preset(); /* reset video format to libretro's default */ video_driver_set_pixel_format(RETRO_PIXEL_FORMAT_0RGB1555); info.env = rarch_environment_cb; core_set_environment(&info); /* Auto-remap: apply remap files */ if(settings->auto_remaps_enable) config_load_remap(); /* Per-core saves: reset redirection paths */ rarch_ctl(RARCH_CTL_SET_PATHS_REDIRECT, NULL); if (!core_init()) return false; if (!event_init_content()) return false; if (!core_load(settings->input.poll_type_behavior)) return false; runloop_ctl(RUNLOOP_CTL_SET_FRAME_LIMIT, NULL); return true; } static void command_event_disable_overrides(void) { if (!runloop_ctl(RUNLOOP_CTL_IS_OVERRIDES_ACTIVE, NULL)) return; /* reload the original config */ config_unload_override(); runloop_ctl(RUNLOOP_CTL_UNSET_OVERRIDES_ACTIVE, NULL); } static void command_event_restore_default_shader_preset(void) { if (!path_is_empty(RARCH_PATH_DEFAULT_SHADER_PRESET)) { /* auto shader preset: reload the original shader */ settings_t *settings = config_get_ptr(); RARCH_LOG("%s %s\n", msg_hash_to_str(MSG_RESTORING_DEFAULT_SHADER_PRESET_TO), path_get(RARCH_PATH_DEFAULT_SHADER_PRESET)); strlcpy(settings->path.shader, path_get(RARCH_PATH_DEFAULT_SHADER_PRESET), sizeof(settings->path.shader)); } path_clear(RARCH_PATH_DEFAULT_SHADER_PRESET); } static bool command_event_save_auto_state(void) { char savestate_name_auto[PATH_MAX_LENGTH] = {0}; bool ret = false; bool contentless = false; bool is_inited = false; settings_t *settings = config_get_ptr(); global_t *global = global_get_ptr(); if (!settings || !settings->savestate_auto_save) return false; if (!global) return false; if (rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) return false; content_get_status(&contentless, &is_inited); if (contentless) return false; #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; #endif fill_pathname_noext(savestate_name_auto, global->name.savestate, file_path_str(FILE_PATH_AUTO_EXTENSION), sizeof(savestate_name_auto)); ret = content_save_state((const char*)savestate_name_auto, true, true); RARCH_LOG("%s \"%s\" %s.\n", msg_hash_to_str(MSG_AUTO_SAVE_STATE_TO), savestate_name_auto, ret ? "succeeded" : "failed"); return true; } static bool command_event_save_config(const char *config_path, char *s, size_t len) { if (string_is_empty(config_path) || !config_save_file(config_path)) { snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_FAILED_SAVING_CONFIG_TO), path_get(RARCH_PATH_CONFIG)); RARCH_ERR("%s\n", s); return false; } snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_SAVED_NEW_CONFIG_TO), path_get(RARCH_PATH_CONFIG)); RARCH_LOG("%s\n", s); return true; } /** * command_event_save_core_config: * * Saves a new (core) configuration to a file. Filename is based * on heuristics to avoid typing. * * Returns: true (1) on success, otherwise false (0). **/ static bool command_event_save_core_config(void) { char config_dir[PATH_MAX_LENGTH]; char config_name[PATH_MAX_LENGTH]; char config_path[PATH_MAX_LENGTH]; char msg[128]; bool ret = false; bool found_path = false; bool overrides_active = false; settings_t *settings = config_get_ptr(); config_dir[0] = config_name[0] = config_path[0] = msg[0] = '\0'; if (!string_is_empty(settings->directory.menu_config)) strlcpy(config_dir, settings->directory.menu_config, sizeof(config_dir)); else if (!path_is_empty(RARCH_PATH_CONFIG)) /* Fallback */ fill_pathname_basedir(config_dir, path_get(RARCH_PATH_CONFIG), sizeof(config_dir)); else { runloop_msg_queue_push(msg_hash_to_str(MSG_CONFIG_DIRECTORY_NOT_SET), 1, 180, true); RARCH_ERR("%s\n", msg_hash_to_str(MSG_CONFIG_DIRECTORY_NOT_SET)); return false; } /* Infer file name based on libretro core. */ if (!string_is_empty(path_get(RARCH_PATH_CORE)) && path_file_exists(path_get(RARCH_PATH_CORE))) { unsigned i; RARCH_LOG("%s\n", msg_hash_to_str(MSG_USING_CORE_NAME_FOR_NEW_CONFIG)); /* In case of collision, find an alternative name. */ for (i = 0; i < 16; i++) { char tmp[64] = {0}; fill_pathname_base_noext( config_name, path_get(RARCH_PATH_CORE), sizeof(config_name)); fill_pathname_join(config_path, config_dir, config_name, sizeof(config_path)); if (i) snprintf(tmp, sizeof(tmp), "-%u%s", i, file_path_str(FILE_PATH_CONFIG_EXTENSION)); else strlcpy(tmp, file_path_str(FILE_PATH_CONFIG_EXTENSION), sizeof(tmp)); strlcat(config_path, tmp, sizeof(config_path)); if (!path_file_exists(config_path)) { found_path = true; break; } } } if (!found_path) { /* Fallback to system time... */ RARCH_WARN("%s\n", msg_hash_to_str(MSG_CANNOT_INFER_NEW_CONFIG_PATH)); fill_dated_filename(config_name, file_path_str(FILE_PATH_CONFIG_EXTENSION), sizeof(config_name)); fill_pathname_join(config_path, config_dir, config_name, sizeof(config_path)); } if (runloop_ctl(RUNLOOP_CTL_IS_OVERRIDES_ACTIVE, NULL)) { /* Overrides block config file saving, * make it appear as overrides weren't enabled * for a manual save. */ runloop_ctl(RUNLOOP_CTL_UNSET_OVERRIDES_ACTIVE, NULL); overrides_active = true; } command_event_save_config(config_path, msg, sizeof(msg)); runloop_msg_queue_push(msg, 1, 180, true); if (overrides_active) runloop_ctl(RUNLOOP_CTL_SET_OVERRIDES_ACTIVE, NULL); else runloop_ctl(RUNLOOP_CTL_UNSET_OVERRIDES_ACTIVE, NULL); return ret; } /** * event_save_current_config: * * Saves current configuration file to disk, and (optionally) * autosave state. **/ static void command_event_save_current_config(enum override_type type) { char msg[128]; msg[0] = '\0'; switch (type) { case OVERRIDE_NONE: if (!path_is_empty(RARCH_PATH_CONFIG)) command_event_save_config(path_get(RARCH_PATH_CONFIG), msg, sizeof(msg)); break; case OVERRIDE_GAME: case OVERRIDE_CORE: if (config_save_overrides(type)) { strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_SAVED_SUCCESSFULLY), sizeof(msg)); RARCH_LOG("[overrides] %s\n", msg); /* set overrides to active so the original config can be restored after closing content */ runloop_ctl(RUNLOOP_CTL_SET_OVERRIDES_ACTIVE, NULL); } else { strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_ERROR_SAVING), sizeof(msg)); RARCH_ERR("[overrides] %s\n", msg); } break; } if (!string_is_empty(msg)) runloop_msg_queue_push(msg, 1, 180, true); } static void command_event_undo_save_state(char *s, size_t len) { if (content_undo_save_buf_is_empty()) { strlcpy(s, msg_hash_to_str(MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET), len); return; } if (!content_undo_save_state()) return; } static void command_event_undo_load_state(char *s, size_t len) { if (content_undo_load_buf_is_empty()) { strlcpy(s, msg_hash_to_str(MSG_NO_STATE_HAS_BEEN_LOADED_YET), len); return; } if (!content_undo_load_state()) { snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE), "RAM"); return; } #ifdef HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL); #endif strlcpy(s, msg_hash_to_str(MSG_UNDID_LOAD_STATE), len); } static void command_event_main_state(unsigned cmd) { retro_ctx_size_info_t info; char path[PATH_MAX_LENGTH]; char msg[128]; global_t *global = global_get_ptr(); bool push_msg = true; path[0] = msg[0] = '\0'; if (global) { settings_t *settings = config_get_ptr(); if (settings->state_slot > 0) snprintf(path, sizeof(path), "%s%d", global->name.savestate, settings->state_slot); else if (settings->state_slot < 0) fill_pathname_join_delim(path, global->name.savestate, "auto", '.', sizeof(path)); else strlcpy(path, global->name.savestate, sizeof(path)); } core_serialize_size(&info); if (info.size) { switch (cmd) { case CMD_EVENT_SAVE_STATE: content_save_state(path, true, false); push_msg = false; break; case CMD_EVENT_LOAD_STATE: if (content_load_state(path, false, false)) { #ifdef HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL); #endif } push_msg = false; break; case CMD_EVENT_UNDO_LOAD_STATE: command_event_undo_load_state(msg, sizeof(msg)); break; case CMD_EVENT_UNDO_SAVE_STATE: command_event_undo_save_state(msg, sizeof(msg)); break; } } else strlcpy(msg, msg_hash_to_str( MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES), sizeof(msg)); if (push_msg) runloop_msg_queue_push(msg, 2, 180, true); RARCH_LOG("%s\n", msg); } void handle_quit_event(void) { command_event(CMD_EVENT_AUTOSAVE_STATE, NULL); command_event(CMD_EVENT_DISABLE_OVERRIDES, NULL); command_event(CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET, NULL); #ifdef HAVE_DYNAMIC #ifdef HAVE_MENU menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_DEINIT, NULL); #endif #endif runloop_ctl(RUNLOOP_CTL_SET_SHUTDOWN, NULL); #ifdef HAVE_MENU rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL); #endif } static bool command_event_resize_windowed_scale(void) { unsigned idx = 0; unsigned *window_scale = NULL; settings_t *settings = config_get_ptr(); if (runloop_ctl(RUNLOOP_CTL_GET_WINDOWED_SCALE, &window_scale)) { if (!window_scale || *window_scale == 0) return false; settings->video.scale = *window_scale; } if (!settings->video.fullscreen) command_event(CMD_EVENT_REINIT, NULL); runloop_ctl(RUNLOOP_CTL_SET_WINDOWED_SCALE, &idx); return true; } /** * command_event: * @cmd : Event command index. * * Performs program event command with index @cmd. * * Returns: true (1) on success, otherwise false (0). **/ bool command_event(enum event_command cmd, void *data) { bool boolean = false; switch (cmd) { case CMD_EVENT_MENU_REFRESH: #ifdef HAVE_MENU menu_driver_ctl(RARCH_MENU_CTL_REFRESH, NULL); #endif break; case CMD_EVENT_SET_PER_GAME_RESOLUTION: #if defined(GEKKO) { unsigned width = 0, height = 0; command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL); if (video_driver_get_video_output_size(&width, &height)) { char msg[128] = {0}; video_driver_set_video_mode(width, height, true); if (width == 0 || height == 0) snprintf(msg, sizeof(msg), "%s: DEFAULT", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCREEN_RESOLUTION)); else snprintf(msg, sizeof(msg),"%s: %dx%d", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCREEN_RESOLUTION), width, height); runloop_msg_queue_push(msg, 1, 100, true); } } #endif break; case CMD_EVENT_LOAD_CORE_PERSIST: { #ifdef HAVE_MENU bool *ptr = NULL; struct retro_system_info *system = NULL; menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_DEINIT, NULL); menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_GET, &system); if (menu_driver_ctl(RARCH_MENU_CTL_LOAD_NO_CONTENT_GET, &ptr)) { core_info_ctx_find_t info_find; #if defined(HAVE_DYNAMIC) if (string_is_empty(path_get(RARCH_PATH_CORE))) return false; #endif libretro_get_system_info( path_get(RARCH_PATH_CORE), system, ptr); info_find.path = path_get(RARCH_PATH_CORE); if (!core_info_load(&info_find)) { #ifdef HAVE_DYNAMIC return false; #endif } } #endif } break; case CMD_EVENT_LOAD_CORE: command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL); #ifndef HAVE_DYNAMIC command_event(CMD_EVENT_QUIT, NULL); #endif break; case CMD_EVENT_LOAD_STATE: { #ifdef HAVE_CHEEVOS settings_t *settings = config_get_ptr(); #endif /* Immutable - disallow savestate load when * we absolutely cannot change game state. */ if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) return false; #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; #endif command_event_main_state(cmd); } break; case CMD_EVENT_UNDO_LOAD_STATE: command_event_main_state(cmd); break; case CMD_EVENT_UNDO_SAVE_STATE: command_event_main_state(cmd); break; case CMD_EVENT_RESIZE_WINDOWED_SCALE: return command_event_resize_windowed_scale(); case CMD_EVENT_MENU_TOGGLE: #ifdef HAVE_MENU if (menu_driver_is_alive()) rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL); else rarch_ctl(RARCH_CTL_MENU_RUNNING, NULL); #endif break; case CMD_EVENT_CONTROLLERS_INIT: command_event_init_controllers(); break; case CMD_EVENT_RESET: RARCH_LOG("%s.\n", msg_hash_to_str(MSG_RESET)); runloop_msg_queue_push(msg_hash_to_str(MSG_RESET), 1, 120, true); #ifdef HAVE_CHEEVOS cheevos_set_cheats(); #endif core_reset(); #ifdef HAVE_CHEEVOS cheevos_reset_game(); #endif #if HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_RESET, NULL); #endif break; case CMD_EVENT_SAVE_STATE: { settings_t *settings = config_get_ptr(); #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; #endif if (settings->savestate_auto_index) settings->state_slot++; command_event_main_state(cmd); } break; case CMD_EVENT_SAVE_STATE_DECREMENT: { settings_t *settings = config_get_ptr(); /* Slot -1 is (auto) slot. */ if (settings->state_slot >= 0) settings->state_slot--; } break; case CMD_EVENT_SAVE_STATE_INCREMENT: { settings_t *settings = config_get_ptr(); settings->state_slot++; } break; case CMD_EVENT_TAKE_SCREENSHOT: if (!take_screenshot(path_get(RARCH_PATH_BASENAME), false)) return false; break; case CMD_EVENT_UNLOAD_CORE: { bool contentless = false; bool is_inited = false; content_ctx_info_t content_info = {0}; content_get_status(&contentless, &is_inited); command_event(CMD_EVENT_AUTOSAVE_STATE, NULL); command_event(CMD_EVENT_DISABLE_OVERRIDES, NULL); command_event(CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET, NULL); if (is_inited) if (!task_push_start_dummy_core(&content_info)) return false; #ifdef HAVE_DYNAMIC #ifdef HAVE_MENU menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_DEINIT, NULL); #endif path_clear(RARCH_PATH_CORE); #else core_unload_game(); core_unload(); #endif } break; case CMD_EVENT_QUIT: handle_quit_event(); break; case CMD_EVENT_CHEEVOS_HARDCORE_MODE_TOGGLE: #ifdef HAVE_CHEEVOS cheevos_toggle_hardcore_mode(); #endif break; case CMD_EVENT_REINIT: video_driver_reinit(); /* Poll input to avoid possibly stale data to corrupt things. */ input_driver_poll(); command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, (void*)(intptr_t)-1); #ifdef HAVE_MENU menu_display_set_framebuffer_dirty_flag(); if (menu_driver_is_alive()) command_event(CMD_EVENT_VIDEO_SET_BLOCKING_STATE, NULL); #endif break; case CMD_EVENT_CHEATS_DEINIT: cheat_manager_state_free(); break; case CMD_EVENT_CHEATS_INIT: command_event(CMD_EVENT_CHEATS_DEINIT, NULL); command_event_init_cheats(); break; case CMD_EVENT_CHEATS_APPLY: cheat_manager_apply_cheats(); break; case CMD_EVENT_REWIND_DEINIT: { #ifdef HAVE_CHEEVOS settings_t *settings = config_get_ptr(); #endif #ifdef HAVE_NETWORKING if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) return false; #endif #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; #endif state_manager_event_deinit(); } break; case CMD_EVENT_REWIND_INIT: { settings_t *settings = config_get_ptr(); #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; #endif #ifdef HAVE_NETWORKING if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) #endif { if (settings->rewind_enable) state_manager_event_init(settings->rewind_buffer_size); } } break; case CMD_EVENT_REWIND_TOGGLE: { settings_t *settings = config_get_ptr(); if (settings->rewind_enable) command_event(CMD_EVENT_REWIND_INIT, NULL); else command_event(CMD_EVENT_REWIND_DEINIT, NULL); } break; case CMD_EVENT_AUTOSAVE_DEINIT: #ifdef HAVE_THREADS if (!rarch_ctl(RARCH_CTL_IS_SRAM_USED, NULL)) return false; autosave_deinit(); #endif break; case CMD_EVENT_AUTOSAVE_INIT: command_event(CMD_EVENT_AUTOSAVE_DEINIT, NULL); #ifdef HAVE_THREADS autosave_init(); #endif break; case CMD_EVENT_AUTOSAVE_STATE: command_event_save_auto_state(); break; case CMD_EVENT_AUDIO_STOP: if (!audio_driver_alive()) return false; if (!audio_driver_stop()) return false; break; case CMD_EVENT_AUDIO_START: { settings_t *settings = config_get_ptr(); if (audio_driver_alive()) return false; if (settings && !settings->audio.mute_enable && !audio_driver_start(runloop_ctl(RUNLOOP_CTL_IS_SHUTDOWN, NULL))) { RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_START_AUDIO_DRIVER)); audio_driver_unset_active(); } } break; case CMD_EVENT_AUDIO_MUTE_TOGGLE: { settings_t *settings = config_get_ptr(); const char *msg = !settings->audio.mute_enable ? msg_hash_to_str(MSG_AUDIO_MUTED): msg_hash_to_str(MSG_AUDIO_UNMUTED); if (!audio_driver_toggle_mute()) { RARCH_ERR("%s.\n", msg_hash_to_str(MSG_FAILED_TO_UNMUTE_AUDIO)); return false; } runloop_msg_queue_push(msg, 1, 180, true); RARCH_LOG("%s\n", msg); } break; case CMD_EVENT_OVERLAY_DEINIT: #ifdef HAVE_OVERLAY input_overlay_free(overlay_ptr); overlay_ptr = NULL; #endif break; case CMD_EVENT_OVERLAY_INIT: { settings_t *settings = config_get_ptr(); command_event(CMD_EVENT_OVERLAY_DEINIT, NULL); #ifdef HAVE_OVERLAY if (settings->input.overlay_enable) task_push_overlay_load_default(input_overlay_loaded, NULL); #endif } break; case CMD_EVENT_OVERLAY_NEXT: { settings_t *settings = config_get_ptr(); #ifdef HAVE_OVERLAY input_overlay_next(overlay_ptr, settings->input.overlay_opacity); #endif } break; case CMD_EVENT_DSP_FILTER_DEINIT: audio_driver_dsp_filter_free(); break; case CMD_EVENT_DSP_FILTER_INIT: { settings_t *settings = config_get_ptr(); command_event(CMD_EVENT_DSP_FILTER_DEINIT, NULL); if (string_is_empty(settings->path.audio_dsp_plugin)) break; audio_driver_dsp_filter_init(settings->path.audio_dsp_plugin); } break; case CMD_EVENT_GPU_RECORD_DEINIT: video_driver_gpu_record_deinit(); break; case CMD_EVENT_RECORD_DEINIT: if (!recording_deinit()) return false; break; case CMD_EVENT_RECORD_INIT: command_event(CMD_EVENT_HISTORY_DEINIT, NULL); if (!recording_init()) return false; break; case CMD_EVENT_HISTORY_DEINIT: if (g_defaults.content_history) { playlist_write_file(g_defaults.content_history); playlist_free(g_defaults.content_history); } g_defaults.content_history = NULL; #ifdef HAVE_FFMPEG if (g_defaults.video_history) { playlist_write_file(g_defaults.video_history); playlist_free(g_defaults.video_history); } g_defaults.video_history = NULL; if (g_defaults.music_history) { playlist_write_file(g_defaults.music_history); playlist_free(g_defaults.music_history); } g_defaults.music_history = NULL; #endif #ifdef HAVE_IMAGEVIEWER if (g_defaults.image_history) { playlist_write_file(g_defaults.image_history); playlist_free(g_defaults.image_history); } g_defaults.image_history = NULL; #endif break; case CMD_EVENT_HISTORY_INIT: { settings_t *settings = config_get_ptr(); command_event(CMD_EVENT_HISTORY_DEINIT, NULL); if (!settings->history_list_enable) return false; RARCH_LOG("%s: [%s].\n", msg_hash_to_str(MSG_LOADING_HISTORY_FILE), settings->path.content_history); g_defaults.content_history = playlist_init( settings->path.content_history, settings->content_history_size); #ifdef HAVE_FFMPEG RARCH_LOG("%s: [%s].\n", msg_hash_to_str(MSG_LOADING_HISTORY_FILE), settings->path.content_music_history); g_defaults.music_history = playlist_init( settings->path.content_music_history, settings->content_history_size); RARCH_LOG("%s: [%s].\n", msg_hash_to_str(MSG_LOADING_HISTORY_FILE), settings->path.content_video_history); g_defaults.video_history = playlist_init( settings->path.content_video_history, settings->content_history_size); #endif #ifdef HAVE_IMAGEVIEWER RARCH_LOG("%s: [%s].\n", msg_hash_to_str(MSG_LOADING_HISTORY_FILE), settings->path.content_image_history); g_defaults.image_history = playlist_init( settings->path.content_image_history, settings->content_history_size); #endif } break; case CMD_EVENT_CORE_INFO_DEINIT: core_info_deinit_list(); break; case CMD_EVENT_CORE_INFO_INIT: { settings_t *settings = config_get_ptr(); command_event(CMD_EVENT_CORE_INFO_DEINIT, NULL); if (!string_is_empty(settings->directory.libretro)) core_info_init_list(); } break; case CMD_EVENT_CORE_DEINIT: { struct retro_hw_render_callback *hwr = NULL; content_reset_savestate_backups(); hwr = video_driver_get_hw_context(); command_event_deinit_core(true); if (hwr) memset(hwr, 0, sizeof(*hwr)); break; } case CMD_EVENT_CORE_INIT: content_reset_savestate_backups(); if (!command_event_init_core((enum rarch_core_type*)data)) return false; break; case CMD_EVENT_VIDEO_APPLY_STATE_CHANGES: video_driver_apply_state_changes(); break; case CMD_EVENT_VIDEO_SET_NONBLOCKING_STATE: boolean = true; /* fall-through */ case CMD_EVENT_VIDEO_SET_BLOCKING_STATE: video_driver_set_nonblock_state(boolean); break; case CMD_EVENT_VIDEO_SET_ASPECT_RATIO: video_driver_set_aspect_ratio(); break; case CMD_EVENT_AUDIO_SET_NONBLOCKING_STATE: boolean = true; /* fall-through */ case CMD_EVENT_AUDIO_SET_BLOCKING_STATE: audio_driver_set_nonblocking_state(boolean); break; case CMD_EVENT_OVERLAY_SET_SCALE_FACTOR: { #ifdef HAVE_OVERLAY settings_t *settings = config_get_ptr(); input_overlay_set_scale_factor(overlay_ptr, settings->input.overlay_scale); #endif } break; case CMD_EVENT_OVERLAY_SET_ALPHA_MOD: { #ifdef HAVE_OVERLAY settings_t *settings = config_get_ptr(); input_overlay_set_alpha_mod(overlay_ptr, settings->input.overlay_opacity); #endif } break; case CMD_EVENT_AUDIO_REINIT: { int flags = DRIVER_AUDIO_MASK; driver_ctl(RARCH_DRIVER_CTL_UNINIT, &flags); drivers_init(flags); } break; case CMD_EVENT_RESET_CONTEXT: { /* RARCH_DRIVER_CTL_UNINIT clears the callback struct so we * need to make sure to keep a copy */ struct retro_hw_render_callback hwr_copy; int flags = DRIVERS_CMD_ALL; struct retro_hw_render_callback *hwr = video_driver_get_hw_context(); const struct retro_hw_render_context_negotiation_interface *iface = video_driver_get_context_negotiation_interface(); memcpy(&hwr_copy, hwr, sizeof(hwr_copy)); driver_ctl(RARCH_DRIVER_CTL_UNINIT, &flags); memcpy(hwr, &hwr_copy, sizeof(*hwr)); video_driver_set_context_negotiation_interface(iface); drivers_init(flags); } break; case CMD_EVENT_SHUTDOWN: #if defined(__linux__) && !defined(ANDROID) runloop_msg_queue_push(msg_hash_to_str(MSG_VALUE_SHUTTING_DOWN), 1, 180, true); command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL); command_event(CMD_EVENT_QUIT, NULL); system("shutdown -P now"); #endif break; case CMD_EVENT_REBOOT: #if defined(__linux__) && !defined(ANDROID) runloop_msg_queue_push(msg_hash_to_str(MSG_VALUE_REBOOTING), 1, 180, true); command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL); command_event(CMD_EVENT_QUIT, NULL); system("shutdown -r now"); #endif break; case CMD_EVENT_RESUME: rarch_ctl(RARCH_CTL_MENU_RUNNING_FINISHED, NULL); if (ui_companion_is_on_foreground()) ui_companion_driver_toggle(); break; case CMD_EVENT_RESTART_RETROARCH: if (!frontend_driver_set_fork(FRONTEND_FORK_RESTART)) return false; #ifndef HAVE_DYNAMIC command_event(CMD_EVENT_QUIT, NULL); #endif break; case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG: command_event_save_current_config(OVERRIDE_NONE); break; case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG_OVERRIDE_CORE: command_event_save_current_config(OVERRIDE_CORE); break; case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG_OVERRIDE_GAME: command_event_save_current_config(OVERRIDE_GAME); break; case CMD_EVENT_MENU_SAVE_CONFIG: if (!command_event_save_core_config()) return false; break; case CMD_EVENT_SHADERS_APPLY_CHANGES: #ifdef HAVE_MENU menu_shader_manager_apply_changes(); #endif break; case CMD_EVENT_PAUSE_CHECKS: { bool is_paused = false; bool is_idle = false; bool is_slowmotion = false; bool is_perfcnt_enable = false; runloop_get_status(&is_paused, &is_idle, &is_slowmotion, &is_perfcnt_enable); if (is_paused) { RARCH_LOG("%s\n", msg_hash_to_str(MSG_PAUSED)); command_event(CMD_EVENT_AUDIO_STOP, NULL); runloop_msg_queue_push(msg_hash_to_str(MSG_PAUSED), 1, 1, true); if (!is_idle) video_driver_cached_frame(); } else { RARCH_LOG("%s\n", msg_hash_to_str(MSG_UNPAUSED)); command_event(CMD_EVENT_AUDIO_START, NULL); } } break; case CMD_EVENT_PAUSE_TOGGLE: boolean = runloop_ctl(RUNLOOP_CTL_IS_PAUSED, NULL); boolean = !boolean; runloop_ctl(RUNLOOP_CTL_SET_PAUSED, &boolean); command_event(CMD_EVENT_PAUSE_CHECKS, NULL); break; case CMD_EVENT_UNPAUSE: boolean = false; runloop_ctl(RUNLOOP_CTL_SET_PAUSED, &boolean); command_event(CMD_EVENT_PAUSE_CHECKS, NULL); break; case CMD_EVENT_PAUSE: boolean = true; runloop_ctl(RUNLOOP_CTL_SET_PAUSED, &boolean); command_event(CMD_EVENT_PAUSE_CHECKS, NULL); break; case CMD_EVENT_MENU_PAUSE_LIBRETRO: #ifdef HAVE_MENU if (menu_driver_is_alive()) { settings_t *settings = config_get_ptr(); if (settings->menu.pause_libretro) command_event(CMD_EVENT_AUDIO_STOP, NULL); else command_event(CMD_EVENT_AUDIO_START, NULL); } else { settings_t *settings = config_get_ptr(); if (settings->menu.pause_libretro) command_event(CMD_EVENT_AUDIO_START, NULL); } #endif break; case CMD_EVENT_SHADER_DIR_DEINIT: dir_free_shader(); break; case CMD_EVENT_SHADER_DIR_INIT: command_event(CMD_EVENT_SHADER_DIR_DEINIT, NULL); if (!dir_init_shader()) return false; break; case CMD_EVENT_BSV_MOVIE_DEINIT: bsv_movie_ctl(BSV_MOVIE_CTL_DEINIT, NULL); break; case CMD_EVENT_BSV_MOVIE_INIT: command_event(CMD_EVENT_BSV_MOVIE_DEINIT, NULL); bsv_movie_ctl(BSV_MOVIE_CTL_INIT, NULL); break; #ifdef HAVE_NETWORKING case CMD_EVENT_NETPLAY_DEINIT: deinit_netplay(); break; case CMD_EVENT_NETWORK_DEINIT: network_deinit(); break; case CMD_EVENT_NETWORK_INIT: network_init(); break; case CMD_EVENT_NETPLAY_INIT: { char *hostname = (char *) data; settings_t *settings = config_get_ptr(); command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); if (!init_netplay( NULL, hostname ? hostname : settings->netplay.server, settings->netplay.port)) { command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); return false; } } break; case CMD_EVENT_NETPLAY_INIT_DIRECT: { settings_t *settings = config_get_ptr(); command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); if (!init_netplay( data, NULL, settings->netplay.port)) { command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); return false; } } break; case CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED: { /* buf is expected to be address:port, there must be a better way to do this but for now I'll just use a string list */ char *buf = (char *)data; static struct string_list *hostname = NULL; hostname = string_split(buf, ":"); command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); if (!init_netplay_deferred( hostname->elems[0].data, atoi(hostname->elems[1].data))) { command_event(CMD_EVENT_NETPLAY_DEINIT, NULL); return false; } } break; case CMD_EVENT_NETPLAY_FLIP_PLAYERS: netplay_driver_ctl(RARCH_NETPLAY_CTL_FLIP_PLAYERS, NULL); break; case CMD_EVENT_NETPLAY_GAME_WATCH: netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL); break; #else case CMD_EVENT_NETPLAY_DEINIT: case CMD_EVENT_NETWORK_DEINIT: case CMD_EVENT_NETWORK_INIT: case CMD_EVENT_NETPLAY_INIT: case CMD_EVENT_NETPLAY_INIT_DIRECT: case CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED: case CMD_EVENT_NETPLAY_FLIP_PLAYERS: case CMD_EVENT_NETPLAY_GAME_WATCH: return false; #endif case CMD_EVENT_FULLSCREEN_TOGGLE: { settings_t *settings = config_get_ptr(); if (!video_driver_has_windowed()) return false; /* If we go fullscreen we drop all drivers and * reinitialize to be safe. */ settings->video.fullscreen = !settings->video.fullscreen; command_event(CMD_EVENT_REINIT, NULL); if (settings->video.fullscreen) video_driver_hide_mouse(); else video_driver_show_mouse(); } break; case CMD_EVENT_COMMAND_DEINIT: input_driver_deinit_command(); break; case CMD_EVENT_COMMAND_INIT: command_event(CMD_EVENT_COMMAND_DEINIT, NULL); input_driver_init_command(); break; case CMD_EVENT_REMOTE_DEINIT: input_driver_deinit_remote(); break; case CMD_EVENT_REMOTE_INIT: command_event(CMD_EVENT_REMOTE_DEINIT, NULL); input_driver_init_remote(); break; case CMD_EVENT_TEMPORARY_CONTENT_DEINIT: content_deinit(); break; case CMD_EVENT_LOG_FILE_DEINIT: retro_main_log_file_deinit(); break; case CMD_EVENT_DISK_APPEND_IMAGE: { const char *path = (const char*)data; if (string_is_empty(path)) return false; return command_event_disk_control_append_image(path); } case CMD_EVENT_DISK_EJECT_TOGGLE: { rarch_system_info_t *info = NULL; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); if (info && info->disk_control_cb.get_num_images) { const struct retro_disk_control_callback *control = (const struct retro_disk_control_callback*) &info->disk_control_cb; if (control) { bool new_state = !control->get_eject_state(); command_event_disk_control_set_eject(new_state, true); } } else runloop_msg_queue_push( msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS), 1, 120, true); } break; case CMD_EVENT_DISK_NEXT: { rarch_system_info_t *info = NULL; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); if (info && info->disk_control_cb.get_num_images) { const struct retro_disk_control_callback *control = (const struct retro_disk_control_callback*) &info->disk_control_cb; if (!control) return false; if (!control->get_eject_state()) return false; command_event_check_disk_next(control); } else runloop_msg_queue_push( msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS), 1, 120, true); } break; case CMD_EVENT_DISK_PREV: { rarch_system_info_t *info = NULL; runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); if (info && info->disk_control_cb.get_num_images) { const struct retro_disk_control_callback *control = (const struct retro_disk_control_callback*) &info->disk_control_cb; if (!control) return false; if (!control->get_eject_state()) return false; command_event_check_disk_prev(control); } else runloop_msg_queue_push( msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS), 1, 120, true); } break; case CMD_EVENT_RUMBLE_STOP: { unsigned i; for (i = 0; i < MAX_USERS; i++) { input_driver_set_rumble_state(i, RETRO_RUMBLE_STRONG, 0); input_driver_set_rumble_state(i, RETRO_RUMBLE_WEAK, 0); } } break; case CMD_EVENT_GRAB_MOUSE_TOGGLE: { bool ret = false; static bool grab_mouse_state = false; grab_mouse_state = !grab_mouse_state; if (grab_mouse_state) ret = input_driver_grab_mouse(); else ret = input_driver_ungrab_mouse(); if (!ret) return false; RARCH_LOG("%s: %s.\n", msg_hash_to_str(MSG_GRAB_MOUSE_STATE), grab_mouse_state ? "yes" : "no"); if (grab_mouse_state) video_driver_hide_mouse(); else video_driver_show_mouse(); } break; case CMD_EVENT_GAME_FOCUS_TOGGLE: { static bool game_focus_state = false; intptr_t mode = (intptr_t)data; /* mode = -1: restores current game focus state * mode = 1: force set game focus, instead of toggling * any other: toggle */ if (mode == 1) game_focus_state = true; else if (mode != -1) game_focus_state = !game_focus_state; RARCH_LOG("%s: %s.\n", "Game focus is: ", game_focus_state ? "on" : "off"); if (game_focus_state) { input_driver_grab_mouse(); video_driver_hide_mouse(); input_driver_set_hotkey_block(); input_driver_keyboard_mapping_set_block(1); if (mode != -1) runloop_msg_queue_push(msg_hash_to_str(MSG_GAME_FOCUS_ON), 1, 120, true); } else { input_driver_ungrab_mouse(); video_driver_show_mouse(); input_driver_unset_hotkey_block(); input_driver_keyboard_mapping_set_block(0); if (mode != -1) runloop_msg_queue_push(msg_hash_to_str(MSG_GAME_FOCUS_OFF), 1, 120, true); } } break; case CMD_EVENT_PERFCNT_REPORT_FRONTEND_LOG: rarch_perf_log(); break; case CMD_EVENT_VOLUME_UP: command_event_set_volume(0.5f); break; case CMD_EVENT_VOLUME_DOWN: command_event_set_volume(-0.5f); break; case CMD_EVENT_SET_FRAME_LIMIT: runloop_ctl(RUNLOOP_CTL_SET_FRAME_LIMIT, NULL); break; case CMD_EVENT_DISABLE_OVERRIDES: command_event_disable_overrides(); break; case CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET: command_event_restore_default_shader_preset(); break; case CMD_EVENT_NONE: return false; } return true; }