RetroArch/runloop.c
2023-06-20 17:56:45 +02:00

8137 lines
270 KiB
C
Raw Blame History

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2021 - Daniel De Matteis
* Copyright (C) 2012-2015 - Michael Lelli
* Copyright (C) 2014-2017 - Jean-Andr<64> Santoni
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2016-2019 - Andr<64>s Su<53>rez (input mapper code)
* Copyright (C) 2016-2017 - Gregor Richards (network code)
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef _WIN32
#ifdef _XBOX
#include <xtl.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#if defined(DEBUG) && defined(HAVE_DRMINGW)
#include "exchndl.h"
#endif
#endif
#if defined(DINGUX)
#include <sys/types.h>
#include <unistd.h>
#endif
#if (defined(__linux__) || defined(__unix__) || defined(DINGUX)) && !defined(EMSCRIPTEN)
#include <signal.h>
#endif
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
#ifndef LEGACY_WIN32
#define LEGACY_WIN32
#endif
#endif
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
#include <objbase.h>
#include <process.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <locale.h>
#include <boolean.h>
#include <clamping.h>
#include <string/stdstring.h>
#include <dynamic/dylib.h>
#include <file/config_file.h>
#include <lists/string_list.h>
#include <memalign.h>
#include <retro_math.h>
#include <retro_timers.h>
#include <encodings/utf.h>
#include <libretro.h>
#ifdef HAVE_VULKAN
#include <libretro_vulkan.h>
#endif
#define VFS_FRONTEND
#include <vfs/vfs_implementation.h>
#include <features/features_cpu.h>
#include <compat/strl.h>
#include <compat/strcasestr.h>
#include <compat/getopt.h>
#include <compat/posix_string.h>
#include <streams/file_stream.h>
#include <file/file_path.h>
#include <retro_miscellaneous.h>
#include <queues/message_queue.h>
#include <lists/dir_list.h>
#ifdef EMSCRIPTEN
#include <emscripten/emscripten.h>
#endif
#ifdef HAVE_LIBNX
#include <switch.h>
#endif
#if defined(HAVE_LAKKA) || defined(HAVE_LIBNX)
#include "switch_performance_profiles.h"
#endif
#if defined(ANDROID)
#include "play_feature_delivery/play_feature_delivery.h"
#endif
#ifdef HAVE_PRESENCE
#include "network/presence.h"
#endif
#ifdef HAVE_DISCORD
#include "network/discord.h"
#endif
#include "config.def.h"
#include "runtime_file.h"
#include "runloop.h"
#include "camera/camera_driver.h"
#include "location_driver.h"
#include "record/record_driver.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_NETWORKING
#include <net/net_compat.h>
#include <net/net_socket.h>
#endif
#include <audio/audio_resampler.h>
#include "audio/audio_driver.h"
#include "gfx/gfx_animation.h"
#include "gfx/gfx_display.h"
#include "gfx/gfx_thumbnail.h"
#include "gfx/video_filter.h"
#include "input/input_osk.h"
#ifdef HAVE_RUNAHEAD
#include "runahead.h"
#endif
#ifdef HAVE_MENU
#include "menu/menu_cbs.h"
#include "menu/menu_driver.h"
#include "menu/menu_input.h"
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
#include "menu/menu_shader.h"
#endif
#ifdef HAVE_GFX_WIDGETS
#include "gfx/gfx_widgets.h"
#endif
#include "input/input_keymaps.h"
#include "input/input_remapping.h"
#ifdef HAVE_MICROPHONE
#include "audio/microphone_driver.h"
#endif
#ifdef HAVE_CHEEVOS
#include "cheevos/cheevos.h"
#include "cheevos/cheevos_menu.h"
#endif
#ifdef HAVE_NETWORKING
#include "network/netplay/netplay.h"
#include "network/netplay/netplay_private.h"
#ifdef HAVE_WIFI
#include "network/wifi_driver.h"
#endif
#endif
#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#endif
#include "autosave.h"
#include "command.h"
#include "config.features.h"
#include "cores/internal_cores.h"
#include "content.h"
#include "core_info.h"
#include "dynamic.h"
#include "defaults.h"
#include "msg_hash.h"
#include "paths.h"
#include "file_path_special.h"
#include "ui/ui_companion_driver.h"
#include "verbosity.h"
#include "frontend/frontend_driver.h"
#ifdef HAVE_THREADS
#include "gfx/video_thread_wrapper.h"
#endif
#include "gfx/video_display_server.h"
#ifdef HAVE_CRTSWITCHRES
#include "gfx/video_crt_switch.h"
#endif
#ifdef HAVE_BLUETOOTH
#include "bluetooth/bluetooth_driver.h"
#endif
#include "misc/cpufreq/cpufreq.h"
#include "led/led_driver.h"
#include "midi_driver.h"
#include "location_driver.h"
#include "core.h"
#include "configuration.h"
#include "list_special.h"
#include "core_option_manager.h"
#ifdef HAVE_CHEATS
#include "cheat_manager.h"
#endif
#ifdef HAVE_REWIND
#include "state_manager.h"
#endif
#include "tasks/task_content.h"
#include "tasks/task_file_transfer.h"
#include "tasks/task_powerstate.h"
#include "tasks/tasks_internal.h"
#include "performance_counters.h"
#include "version.h"
#include "version_git.h"
#include "retroarch.h"
#include "accessibility.h"
#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
#include "SDL.h"
#endif
#ifdef HAVE_LAKKA
#include "lakka.h"
#endif
#if defined(HAVE_COCOATOUCH) && TARGET_OS_IOS
#include "JITSupport.h"
#endif
#define SHADER_FILE_WATCH_DELAY_MSEC 500
#define QUIT_DELAY_USEC 3 * 1000000 /* 3 seconds */
#define DEFAULT_NETWORK_GAMEPAD_PORT 55400
#define UDP_FRAME_PACKETS 16
#ifdef HAVE_ZLIB
#define DEFAULT_EXT "zip"
#else
#define DEFAULT_EXT ""
#endif
#ifdef HAVE_DYNAMIC
#define SYMBOL(x) do { \
function_t func = dylib_proc(lib_handle_local, #x); \
memcpy(&current_core->x, &func, sizeof(func)); \
if (!current_core->x) { RARCH_ERR("Failed to load symbol: \"%s\"\n", #x); retroarch_fail(1, "runloop_init_libretro_symbols()"); } \
} while (0)
#else
#define SYMBOL(x) current_core->x = x
#endif
#define SYMBOL_DUMMY(x) current_core->x = libretro_dummy_##x
#ifdef HAVE_FFMPEG
#define SYMBOL_FFMPEG(x) current_core->x = libretro_ffmpeg_##x
#endif
#ifdef HAVE_MPV
#define SYMBOL_MPV(x) current_core->x = libretro_mpv_##x
#endif
#ifdef HAVE_IMAGEVIEWER
#define SYMBOL_IMAGEVIEWER(x) current_core->x = libretro_imageviewer_##x
#endif
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
#define SYMBOL_NETRETROPAD(x) current_core->x = libretro_netretropad_##x
#endif
#if defined(HAVE_VIDEOPROCESSOR)
#define SYMBOL_VIDEOPROCESSOR(x) current_core->x = libretro_videoprocessor_##x
#endif
#define CORE_SYMBOLS(x) \
x(retro_init); \
x(retro_deinit); \
x(retro_api_version); \
x(retro_get_system_info); \
x(retro_get_system_av_info); \
x(retro_set_environment); \
x(retro_set_video_refresh); \
x(retro_set_audio_sample); \
x(retro_set_audio_sample_batch); \
x(retro_set_input_poll); \
x(retro_set_input_state); \
x(retro_set_controller_port_device); \
x(retro_reset); \
x(retro_run); \
x(retro_serialize_size); \
x(retro_serialize); \
x(retro_unserialize); \
x(retro_cheat_reset); \
x(retro_cheat_set); \
x(retro_load_game); \
x(retro_load_game_special); \
x(retro_unload_game); \
x(retro_get_region); \
x(retro_get_memory_data); \
x(retro_get_memory_size);
#ifdef _WIN32
#define PERF_LOG_FMT "[PERF]: Avg (%s): %I64u ticks, %I64u runs.\n"
#else
#define PERF_LOG_FMT "[PERF]: Avg (%s): %llu ticks, %llu runs.\n"
#endif
static runloop_state_t runloop_state = {0};
/* GLOBAL POINTER GETTERS */
runloop_state_t *runloop_state_get_ptr(void)
{
return &runloop_state;
}
bool state_manager_frame_is_reversed(void)
{
#ifdef HAVE_REWIND
return (runloop_state.rewind_st.flags & STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED) > 0;
#else
return false;
#endif
}
content_state_t *content_state_get_ptr(void)
{
return &runloop_state.content_st;
}
/* Get the current subsystem rom id */
unsigned content_get_subsystem_rom_id(void)
{
return runloop_state.content_st.pending_subsystem_rom_id;
}
/* Get the current subsystem */
int content_get_subsystem(void)
{
return runloop_state.content_st.pending_subsystem_id;
}
struct retro_perf_counter **retro_get_perf_counter_libretro(void)
{
return runloop_state.perf_counters_libretro;
}
unsigned retro_get_perf_count_libretro(void)
{
return runloop_state.perf_ptr_libretro;
}
void runloop_performance_counter_register(struct retro_perf_counter *perf)
{
if ( perf->registered
|| runloop_state.perf_ptr_libretro >= MAX_COUNTERS)
return;
runloop_state.perf_counters_libretro[runloop_state.perf_ptr_libretro++] = perf;
perf->registered = true;
}
void runloop_log_counters(
struct retro_perf_counter **counters, unsigned num)
{
int i;
for (i = 0; i < (int)num; i++)
{
if (counters[i]->call_cnt)
{
RARCH_LOG(PERF_LOG_FMT,
counters[i]->ident,
(uint64_t)counters[i]->total /
(uint64_t)counters[i]->call_cnt,
(uint64_t)counters[i]->call_cnt);
}
}
}
static void runloop_perf_log(void)
{
RARCH_LOG("[PERF]: Performance counters (libretro):\n");
runloop_log_counters(runloop_state.perf_counters_libretro,
runloop_state.perf_ptr_libretro);
}
static bool runloop_environ_cb_get_system_info(unsigned cmd, void *data)
{
runloop_state_t *runloop_st = &runloop_state;
rarch_system_info_t *system = &runloop_st->system;
switch (cmd)
{
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
*runloop_st->load_no_content_hook = *(const bool*)data;
break;
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
{
size_t i, j, size;
const struct retro_subsystem_info *info =
(const struct retro_subsystem_info*)data;
settings_t *settings = config_get_ptr();
unsigned log_level = settings->uints.libretro_log_level;
runloop_st->subsystem_current_count = 0;
RARCH_LOG("[Environ]: SET_SUBSYSTEM_INFO.\n");
for (i = 0; info[i].ident; i++)
{
if (log_level != RETRO_LOG_DEBUG)
continue;
RARCH_DBG("Subsystem ID: %d\nSpecial game type: %s\n Ident: %s\n ID: %u\n Content:\n",
i,
info[i].desc,
info[i].ident,
info[i].id
);
for (j = 0; j < info[i].num_roms; j++)
{
RARCH_DBG(" %s (%s)\n",
info[i].roms[j].desc, info[i].roms[j].required ?
"required" : "optional");
}
}
size = i;
if (log_level == RETRO_LOG_DEBUG)
{
RARCH_DBG("Subsystems: %d\n", i);
if (size > SUBSYSTEM_MAX_SUBSYSTEMS)
RARCH_WARN("Subsystems exceed subsystem max, clamping to %d\n", SUBSYSTEM_MAX_SUBSYSTEMS);
}
if (system)
{
for (i = 0; i < size && i < SUBSYSTEM_MAX_SUBSYSTEMS; i++)
{
struct retro_subsystem_info *subsys_info = &runloop_st->subsystem_data[i];
struct retro_subsystem_rom_info *subsys_rom_info = runloop_st->subsystem_data_roms[i];
/* Nasty, but have to do it like this since
* the pointers are const char *
* (if we don't free them, we get a memory leak) */
if (!string_is_empty(subsys_info->desc))
free((char *)subsys_info->desc);
if (!string_is_empty(subsys_info->ident))
free((char *)subsys_info->ident);
subsys_info->desc = strdup(info[i].desc);
subsys_info->ident = strdup(info[i].ident);
subsys_info->id = info[i].id;
subsys_info->num_roms = info[i].num_roms;
if (log_level == RETRO_LOG_DEBUG)
if (subsys_info->num_roms > SUBSYSTEM_MAX_SUBSYSTEM_ROMS)
RARCH_WARN("Subsystems exceed subsystem max roms, clamping to %d\n", SUBSYSTEM_MAX_SUBSYSTEM_ROMS);
for (j = 0; j < subsys_info->num_roms && j < SUBSYSTEM_MAX_SUBSYSTEM_ROMS; j++)
{
/* Nasty, but have to do it like this since
* the pointers are const char *
* (if we don't free them, we get a memory leak) */
if (!string_is_empty(subsys_rom_info[j].desc))
free((char *)
subsys_rom_info[j].desc);
if (!string_is_empty(
subsys_rom_info[j].valid_extensions))
free((char *)
subsys_rom_info[j].valid_extensions);
subsys_rom_info[j].desc =
strdup(info[i].roms[j].desc);
subsys_rom_info[j].valid_extensions =
strdup(info[i].roms[j].valid_extensions);
subsys_rom_info[j].required =
info[i].roms[j].required;
subsys_rom_info[j].block_extract =
info[i].roms[j].block_extract;
subsys_rom_info[j].need_fullpath =
info[i].roms[j].need_fullpath;
}
subsys_info->roms = subsys_rom_info;
}
runloop_st->subsystem_current_count =
size <= SUBSYSTEM_MAX_SUBSYSTEMS
? (unsigned)size
: SUBSYSTEM_MAX_SUBSYSTEMS;
}
break;
}
default:
return false;
}
return true;
}
#ifdef HAVE_DYNAMIC
/**
* libretro_get_environment_info:
* @func : Function pointer for get_environment_info.
* @load_no_content : If true, core should be able to auto-start
* without any content loaded.
*
* Sets environment callback in order to get statically known
* information from it.
*
* Fetched via environment callbacks instead of
* retro_get_system_info(), as this info is part of extensions.
*
* Should only be called once right after core load to
* avoid overwriting the "real" environ callback.
*
* For statically linked cores, pass retro_set_environment as argument.
*/
void libretro_get_environment_info(
void (*func)(retro_environment_t),
bool *load_no_content)
{
runloop_state_t *runloop_st = &runloop_state;
runloop_st->load_no_content_hook = load_no_content;
/* load_no_content gets set in this callback. */
func(runloop_environ_cb_get_system_info);
/* It's possible that we just set get_system_info callback
* to the currently running core.
*
* Make sure we reset it to the actual environment callback.
* Ignore any environment callbacks here in case we're running
* on the non-current core. */
runloop_st->flags |= RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB;
func(runloop_environment_cb);
runloop_st->flags &= ~RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB;
}
static dylib_t load_dynamic_core(const char *path, char *buf,
size_t size)
{
#if defined(ANDROID)
/* Can't resolve symlinks when dealing with cores
* installed via play feature delivery, because the
* source files have non-standard file names (which
* will not be recognised by regular core handling
* routines) */
bool resolve_symlinks = !play_feature_delivery_enabled();
#else
bool resolve_symlinks = true;
#endif
/* Can't lookup symbols in itself on UWP */
#if !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
if (dylib_proc(NULL, "retro_init"))
{
/* Try to verify that -lretro was not linked in from other modules
* since loading it dynamically and with -l will fail hard. */
RARCH_ERR("Serious problem. RetroArch wants to load libretro cores"
" dynamically, but it is already linked.\n");
RARCH_ERR("This could happen if other modules RetroArch depends on "
"link against libretro directly.\n");
RARCH_ERR("Proceeding could cause a crash. Aborting ...\n");
retroarch_fail(1, "load_dynamic_core()");
}
#endif
/* Need to use absolute path for this setting. It can be
* saved to content history, and a relative path would
* break in that scenario. */
path_resolve_realpath(buf, size, resolve_symlinks);
return dylib_load(path);
}
static dylib_t libretro_get_system_info_lib(const char *path,
struct retro_system_info *info, bool *load_no_content)
{
dylib_t lib = dylib_load(path);
void (*proc)(struct retro_system_info*);
if (!lib)
return NULL;
proc = (void (*)(struct retro_system_info*))
dylib_proc(lib, "retro_get_system_info");
if (!proc)
{
dylib_close(lib);
return NULL;
}
proc(info);
if (load_no_content)
{
void (*set_environ)(retro_environment_t);
*load_no_content = false;
set_environ = (void (*)(retro_environment_t))
dylib_proc(lib, "retro_set_environment");
if (set_environ)
libretro_get_environment_info(set_environ, load_no_content);
}
return lib;
}
#endif
static void runloop_update_runtime_log(
runloop_state_t *runloop_st,
const char *dir_runtime_log,
const char *dir_playlist,
bool log_per_core)
{
/* Initialise runtime log file */
runtime_log_t *runtime_log = runtime_log_init(
runloop_st->runtime_content_path,
runloop_st->runtime_core_path,
dir_runtime_log,
dir_playlist,
log_per_core);
if (!runtime_log)
return;
/* Add additional runtime */
runtime_log_add_runtime_usec(runtime_log,
runloop_st->core_runtime_usec);
/* Update 'last played' entry */
runtime_log_set_last_played_now(runtime_log);
/* Save runtime log file */
runtime_log_save(runtime_log);
/* Clean up */
free(runtime_log);
}
void runloop_runtime_log_deinit(
runloop_state_t *runloop_st,
bool content_runtime_log,
bool content_runtime_log_aggregate,
const char *dir_runtime_log,
const char *dir_playlist)
{
if (verbosity_is_enabled())
{
char log[256] = {0};
unsigned hours = 0;
unsigned minutes = 0;
unsigned seconds = 0;
runtime_log_convert_usec2hms(
runloop_st->core_runtime_usec,
&hours, &minutes, &seconds);
/* TODO/FIXME - localize */
snprintf(log, sizeof(log),
"[Core]: Content ran for a total of:"
" %02u hours, %02u minutes, %02u seconds.",
hours, minutes, seconds);
RARCH_LOG("%s\n", log);
}
/* Only write to file if content has run for a non-zero length of time */
if (runloop_st->core_runtime_usec > 0)
{
/* Per core logging */
if (content_runtime_log)
runloop_update_runtime_log(runloop_st, dir_runtime_log, dir_playlist, true);
/* Aggregate logging */
if (content_runtime_log_aggregate)
runloop_update_runtime_log(runloop_st, dir_runtime_log, dir_playlist, false);
}
/* Reset runtime + content/core paths, to prevent any
* possibility of duplicate logging */
runloop_st->core_runtime_usec = 0;
memset(runloop_st->runtime_content_path, 0,
sizeof(runloop_st->runtime_content_path));
memset(runloop_st->runtime_core_path, 0,
sizeof(runloop_st->runtime_core_path));
}
static bool runloop_clear_all_thread_waits(
unsigned clear_threads, void *data)
{
/* Does this need to treat the microphone driver the same way? */
if (clear_threads > 0)
audio_driver_start(false);
else
audio_driver_stop();
return true;
}
static bool dynamic_verify_hw_context(
const char *video_ident,
bool driver_switch_enable,
enum retro_hw_context_type type,
unsigned minor, unsigned major)
{
if (!driver_switch_enable)
{
switch (type)
{
case RETRO_HW_CONTEXT_VULKAN:
if (!string_is_equal(video_ident, "vulkan"))
return false;
break;
#if defined(HAVE_OPENGL_CORE)
case RETRO_HW_CONTEXT_OPENGL_CORE:
if (!string_is_equal(video_ident, "glcore"))
return false;
break;
#else
case RETRO_HW_CONTEXT_OPENGL_CORE:
#endif
case RETRO_HW_CONTEXT_OPENGLES2:
case RETRO_HW_CONTEXT_OPENGLES3:
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
case RETRO_HW_CONTEXT_OPENGL:
if (!string_is_equal(video_ident, "gl") &&
!string_is_equal(video_ident, "glcore"))
return false;
break;
case RETRO_HW_CONTEXT_D3D10:
if (!string_is_equal(video_ident, "d3d10"))
return false;
break;
case RETRO_HW_CONTEXT_D3D11:
if (!string_is_equal(video_ident, "d3d11"))
return false;
break;
case RETRO_HW_CONTEXT_D3D12:
if (!string_is_equal(video_ident, "d3d12"))
return false;
break;
default:
break;
}
}
return true;
}
static bool dynamic_request_hw_context(enum retro_hw_context_type type,
unsigned minor, unsigned major)
{
switch (type)
{
case RETRO_HW_CONTEXT_NONE:
RARCH_LOG("Requesting no HW context.\n");
break;
case RETRO_HW_CONTEXT_VULKAN:
#ifdef HAVE_VULKAN
RARCH_LOG("Requesting Vulkan context.\n");
break;
#else
RARCH_ERR("Requesting Vulkan context, but RetroArch is not compiled against Vulkan. Cannot use HW context.\n");
return false;
#endif
#if defined(HAVE_OPENGLES)
#if (defined(HAVE_OPENGLES2) || defined(HAVE_OPENGLES3))
case RETRO_HW_CONTEXT_OPENGLES2:
case RETRO_HW_CONTEXT_OPENGLES3:
RARCH_LOG("Requesting OpenGLES%u context.\n",
type == RETRO_HW_CONTEXT_OPENGLES2 ? 2 : 3);
break;
#if defined(HAVE_OPENGLES3)
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
#ifndef HAVE_OPENGLES3_2
if (major == 3 && minor == 2)
{
RARCH_ERR("Requesting OpenGLES%u.%u context, but RetroArch is compiled against a lesser version. Cannot use HW context.\n",
major, minor);
return false;
}
#endif
#if !defined(HAVE_OPENGLES3_2) && !defined(HAVE_OPENGLES3_1)
if (major == 3 && minor == 1)
{
RARCH_ERR("Requesting OpenGLES%u.%u context, but RetroArch is compiled against a lesser version. Cannot use HW context.\n",
major, minor);
return false;
}
#endif
RARCH_LOG("Requesting OpenGLES%u.%u context.\n",
major, minor);
break;
#endif
#endif
case RETRO_HW_CONTEXT_OPENGL:
case RETRO_HW_CONTEXT_OPENGL_CORE:
RARCH_ERR("Requesting OpenGL context, but RetroArch "
"is compiled against OpenGLES. Cannot use HW context.\n");
return false;
#elif defined(HAVE_OPENGL) || defined(HAVE_OPENGL_CORE)
case RETRO_HW_CONTEXT_OPENGLES2:
case RETRO_HW_CONTEXT_OPENGLES3:
RARCH_ERR("Requesting OpenGLES%u context, but RetroArch "
"is compiled against OpenGL. Cannot use HW context.\n",
type == RETRO_HW_CONTEXT_OPENGLES2 ? 2 : 3);
return false;
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
RARCH_ERR("Requesting OpenGLES%u.%u context, but RetroArch "
"is compiled against OpenGL. Cannot use HW context.\n",
major, minor);
return false;
case RETRO_HW_CONTEXT_OPENGL:
RARCH_LOG("Requesting OpenGL context.\n");
break;
case RETRO_HW_CONTEXT_OPENGL_CORE:
/* TODO/FIXME - we should do a check here to see if
* the requested core GL version is supported */
RARCH_LOG("Requesting core OpenGL context (%u.%u).\n",
major, minor);
break;
#endif
#if defined(HAVE_D3D11)
case RETRO_HW_CONTEXT_D3D11:
RARCH_LOG("Requesting D3D11 context.\n");
break;
#endif
#ifdef HAVE_D3D10
case RETRO_HW_CONTEXT_D3D10:
RARCH_LOG("Requesting D3D10 context.\n");
break;
#endif
#ifdef HAVE_D3D12
case RETRO_HW_CONTEXT_D3D12:
RARCH_LOG("Requesting D3D12 context.\n");
break;
#endif
#if defined(HAVE_D3D9)
case RETRO_HW_CONTEXT_D3D9:
RARCH_LOG("Requesting D3D9 context.\n");
break;
#endif
default:
RARCH_LOG("Requesting unknown context.\n");
return false;
}
return true;
}
static void libretro_log_cb(
enum retro_log_level level,
const char *fmt, ...)
{
va_list vp;
settings_t *settings = config_get_ptr();
unsigned libretro_log_level = settings->uints.libretro_log_level;
if ((unsigned)level < libretro_log_level)
return;
if (!verbosity_is_enabled())
return;
va_start(vp, fmt);
switch (level)
{
case RETRO_LOG_DEBUG:
RARCH_LOG_V("[libretro DEBUG]", fmt, vp);
break;
case RETRO_LOG_INFO:
RARCH_LOG_OUTPUT_V("[libretro INFO]", fmt, vp);
break;
case RETRO_LOG_WARN:
RARCH_WARN_V("[libretro WARN]", fmt, vp);
break;
case RETRO_LOG_ERROR:
RARCH_ERR_V("[libretro ERROR]", fmt, vp);
break;
default:
break;
}
va_end(vp);
}
static size_t mmap_add_bits_down(size_t n)
{
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
/* double shift to avoid warnings on 32bit (it's dead code,
* but compilers suck) */
if (sizeof(size_t) > 4)
n |= n >> 16 >> 16;
return n;
}
static size_t mmap_inflate(size_t addr, size_t mask)
{
while (mask)
{
size_t tmp = (mask - 1) & ~mask;
/* to put in an 1 bit instead, OR in tmp+1 */
addr = ((addr & ~tmp) << 1) | (addr & tmp);
mask = mask & (mask - 1);
}
return addr;
}
static size_t mmap_reduce(size_t addr, size_t mask)
{
while (mask)
{
size_t tmp = (mask - 1) & ~mask;
addr = (addr & tmp) | ((addr >> 1) & ~tmp);
mask = (mask & (mask - 1)) >> 1;
}
return addr;
}
static size_t mmap_highest_bit(size_t n)
{
n = mmap_add_bits_down(n);
return n ^ (n >> 1);
}
static bool mmap_preprocess_descriptors(
rarch_memory_descriptor_t *first, unsigned count)
{
size_t top_addr = 1;
rarch_memory_descriptor_t *desc = NULL;
const rarch_memory_descriptor_t *end = first + count;
size_t highest_reachable = 0;
for (desc = first; desc < end; desc++)
{
if (desc->core.select != 0)
top_addr |= desc->core.select;
else
top_addr |= desc->core.start + desc->core.len - 1;
}
top_addr = mmap_add_bits_down(top_addr);
for (desc = first; desc < end; desc++)
{
if (desc->core.select == 0)
{
if (desc->core.len == 0)
return false;
if ((desc->core.len & (desc->core.len - 1)) != 0)
return false;
desc->core.select = top_addr
& ~mmap_inflate(mmap_add_bits_down(desc->core.len - 1),
desc->core.disconnect);
}
if (desc->core.len == 0)
desc->core.len = mmap_add_bits_down(
mmap_reduce(top_addr & ~desc->core.select,
desc->core.disconnect)) + 1;
if (desc->core.start & ~desc->core.select)
return false;
highest_reachable = mmap_inflate(desc->core.len - 1,
desc->core.disconnect);
/* Disconnect unselected bits that are too high to ever
* index into the core's buffer. Higher addresses will
* repeat / mirror the buffer as long as they match select */
while (mmap_highest_bit(top_addr
& ~desc->core.select
& ~desc->core.disconnect) >
mmap_highest_bit(highest_reachable))
desc->core.disconnect |= mmap_highest_bit(top_addr
& ~desc->core.select
& ~desc->core.disconnect);
}
return true;
}
static void runloop_deinit_core_options(
bool game_options_active,
const char *path_core_options,
core_option_manager_t *core_options)
{
/* Check whether game-specific options file is being used */
if (!string_is_empty(path_core_options))
{
config_file_t *conf_tmp = NULL;
/* We only need to save configuration settings for
* the current core
* > If game-specific options file exists, have
* to read it (to ensure file only gets written
* if config values change)
* > Otherwise, create a new, empty config_file_t
* object */
if (path_is_valid(path_core_options))
conf_tmp = config_file_new_from_path_to_string(path_core_options);
if (!conf_tmp)
conf_tmp = config_file_new_alloc();
if (conf_tmp)
{
core_option_manager_flush(
core_options,
conf_tmp);
RARCH_LOG("[Core]: Saved %s-specific core options to \"%s\".\n",
game_options_active ? "game" : "folder", path_core_options);
config_file_write(conf_tmp, path_core_options, true);
config_file_free(conf_tmp);
conf_tmp = NULL;
}
path_clear(RARCH_PATH_CORE_OPTIONS);
}
else
{
const char *path = core_options->conf_path;
core_option_manager_flush(
core_options,
core_options->conf);
RARCH_LOG("[Core]: Saved core options file to \"%s\".\n", path);
config_file_write(core_options->conf, path, true);
}
if (core_options)
core_option_manager_free(core_options);
}
static bool validate_per_core_options(char *s,
size_t len, bool mkdir,
const char *core_name, const char *game_name)
{
char config_directory[PATH_MAX_LENGTH];
config_directory[0] = '\0';
if ( (!s)
|| (len < 1)
|| string_is_empty(core_name)
|| string_is_empty(game_name))
return false;
fill_pathname_application_special(config_directory,
sizeof(config_directory), APPLICATION_SPECIAL_DIRECTORY_CONFIG);
fill_pathname_join_special_ext(s,
config_directory, core_name, game_name,
".opt", len);
/* No need to make a directory if file already exists... */
if (mkdir && !path_is_valid(s))
{
char new_path[PATH_MAX_LENGTH];
fill_pathname_join_special(new_path,
config_directory, core_name, sizeof(new_path));
if (!path_is_directory(new_path))
path_mkdir(new_path);
}
return true;
}
static bool validate_game_options(
const char *core_name,
char *s, size_t len, bool mkdir)
{
const char *game_name = path_basename_nocompression(path_get(RARCH_PATH_BASENAME));
return validate_per_core_options(s, len, mkdir,
core_name, game_name);
}
/**
* game_specific_options:
*
* @return true if a game specific core
* options path has been found, otherwise false.
**/
static bool validate_game_specific_options(char **output)
{
char game_options_path[PATH_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
game_options_path[0] = '\0';
if (!validate_game_options(
runloop_st->system.info.library_name,
game_options_path,
sizeof(game_options_path), false)
|| !path_is_valid(game_options_path))
return false;
RARCH_LOG("[Core]: %s \"%s\".\n",
msg_hash_to_str(MSG_GAME_SPECIFIC_CORE_OPTIONS_FOUND_AT),
game_options_path);
*output = strdup(game_options_path);
return true;
}
static bool validate_folder_options(
char *s, size_t len, bool mkdir)
{
char folder_name[PATH_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
const char *core_name = runloop_st->system.info.library_name;
const char *game_path = path_get(RARCH_PATH_BASENAME);
folder_name[0] = '\0';
if (string_is_empty(game_path))
return false;
fill_pathname_parent_dir_name(folder_name,
game_path, sizeof(folder_name));
return validate_per_core_options(s, len, mkdir,
core_name, folder_name);
}
/**
* validate_folder_specific_options:
*
* @return true if a folder specific core
* options path has been found, otherwise false.
**/
static bool validate_folder_specific_options(
char **output)
{
char folder_options_path[PATH_MAX_LENGTH];
folder_options_path[0] ='\0';
if (!validate_folder_options(
folder_options_path,
sizeof(folder_options_path), false)
|| !path_is_valid(folder_options_path))
return false;
RARCH_LOG("[Core]: %s \"%s\".\n",
msg_hash_to_str(MSG_FOLDER_SPECIFIC_CORE_OPTIONS_FOUND_AT),
folder_options_path);
*output = strdup(folder_options_path);
return true;
}
/**
* runloop_init_core_options_path:
*
* Fetches core options path for current core/content
* - path: path from which options should be read
* from/saved to
* - src_path: in the event that 'path' file does not
* yet exist, provides source path from which initial
* options should be extracted
*
* NOTE: caller must ensure
* path and src_path are NULL-terminated
*
**/
static void runloop_init_core_options_path(
settings_t *settings,
char *path, size_t len,
char *src_path, size_t src_len)
{
char *game_options_path = NULL;
char *folder_options_path = NULL;
runloop_state_t *runloop_st = &runloop_state;
bool game_specific_options = settings->bools.game_specific_options;
/* Check whether game-specific options exist */
if (game_specific_options &&
validate_game_specific_options(&game_options_path))
{
/* Notify system that we have a valid core options
* override */
path_set(RARCH_PATH_CORE_OPTIONS, game_options_path);
runloop_st->flags &= ~RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
/* Copy options path */
strlcpy(path, game_options_path, len);
free(game_options_path);
}
/* Check whether folder-specific options exist */
else if (game_specific_options &&
validate_folder_specific_options(
&folder_options_path))
{
/* Notify system that we have a valid core options
* override */
path_set(RARCH_PATH_CORE_OPTIONS, folder_options_path);
runloop_st->flags &= ~RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
/* Copy options path */
strlcpy(path, folder_options_path, len);
free(folder_options_path);
}
else
{
char global_options_path[PATH_MAX_LENGTH];
char per_core_options_path[PATH_MAX_LENGTH];
bool per_core_options_exist = false;
bool per_core_options = !settings->bools.global_core_options;
const char *path_core_options = settings->paths.path_core_options;
per_core_options_path[0] = '\0';
if (per_core_options)
{
const char *core_name = runloop_st->system.info.library_name;
/* Get core-specific options path
* > if validate_per_core_options() returns
* false, then per-core options are disabled (due to
* unknown system errors...) */
per_core_options = validate_per_core_options(
per_core_options_path, sizeof(per_core_options_path), true,
core_name, core_name);
/* If we can use per-core options, check whether an options
* file already exists */
if (per_core_options)
per_core_options_exist = path_is_valid(per_core_options_path);
}
/* If not using per-core options, or if a per-core options
* file does not yet exist, must fetch 'global' options path */
if (!per_core_options || !per_core_options_exist)
{
const char *options_path = path_core_options;
if (!string_is_empty(options_path))
strlcpy(global_options_path,
options_path, sizeof(global_options_path));
else if (!path_is_empty(RARCH_PATH_CONFIG))
fill_pathname_resolve_relative(
global_options_path, path_get(RARCH_PATH_CONFIG),
FILE_PATH_CORE_OPTIONS_CONFIG, sizeof(global_options_path));
}
/* Allocate correct path/src_path strings */
if (per_core_options)
{
strlcpy(path, per_core_options_path, len);
if (!per_core_options_exist)
strlcpy(src_path, global_options_path, src_len);
}
else
strlcpy(path, global_options_path, len);
/* Notify system that we *do not* have a valid core options
* options override */
runloop_st->flags &= ~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
}
}
static core_option_manager_t *runloop_init_core_options(
settings_t *settings,
const struct retro_core_options_v2 *options_v2)
{
bool categories_enabled = settings->bools.core_option_category_enable;
char options_path[PATH_MAX_LENGTH];
char src_options_path[PATH_MAX_LENGTH];
/* Ensure these are NULL-terminated */
options_path[0] = '\0';
src_options_path[0] = '\0';
/* Get core options file path */
runloop_init_core_options_path(settings,
options_path, sizeof(options_path),
src_options_path, sizeof(src_options_path));
if (!string_is_empty(options_path))
return core_option_manager_new(options_path,
src_options_path, options_v2,
categories_enabled);
return NULL;
}
static core_option_manager_t *runloop_init_core_variables(
settings_t *settings, const struct retro_variable *vars)
{
char options_path[PATH_MAX_LENGTH];
char src_options_path[PATH_MAX_LENGTH];
/* Ensure these are NULL-terminated */
options_path[0] = '\0';
src_options_path[0] = '\0';
/* Get core options file path */
runloop_init_core_options_path(
settings,
options_path, sizeof(options_path),
src_options_path, sizeof(src_options_path));
if (!string_is_empty(options_path))
return core_option_manager_new_vars(options_path, src_options_path, vars);
return NULL;
}
static void runloop_core_msg_queue_push(
struct retro_system_av_info *av_info,
const struct retro_message_ext *msg)
{
double fps;
unsigned duration_frames;
enum message_queue_category category;
/* Assign category */
switch (msg->level)
{
case RETRO_LOG_WARN:
category = MESSAGE_QUEUE_CATEGORY_WARNING;
break;
case RETRO_LOG_ERROR:
category = MESSAGE_QUEUE_CATEGORY_ERROR;
break;
case RETRO_LOG_INFO:
case RETRO_LOG_DEBUG:
default:
category = MESSAGE_QUEUE_CATEGORY_INFO;
break;
}
/* Get duration in frames */
fps = (av_info && (av_info->timing.fps > 0)) ? av_info->timing.fps : 60.0;
duration_frames = (unsigned)((fps * (float)msg->duration / 1000.0f) + 0.5f);
/* Note: Do not flush the message queue here - a core
* may need to send multiple notifications simultaneously */
runloop_msg_queue_push(msg->msg,
msg->priority, duration_frames,
false, NULL, MESSAGE_QUEUE_ICON_DEFAULT,
category);
}
static void core_performance_counter_start(
struct retro_perf_counter *perf)
{
runloop_state_t *runloop_st = &runloop_state;
bool runloop_perfcnt_enable = runloop_st->perfcnt_enable;
if (runloop_perfcnt_enable)
{
perf->call_cnt++;
perf->start = cpu_features_get_perf_counter();
}
}
static void core_performance_counter_stop(struct retro_perf_counter *perf)
{
runloop_state_t *runloop_st = &runloop_state;
bool runloop_perfcnt_enable = runloop_st->perfcnt_enable;
if (runloop_perfcnt_enable)
perf->total += cpu_features_get_perf_counter() - perf->start;
}
bool runloop_environment_cb(unsigned cmd, void *data)
{
unsigned p;
runloop_state_t *runloop_st = &runloop_state;
recording_state_t *recording_st = recording_state_get_ptr();
settings_t *settings = config_get_ptr();
rarch_system_info_t *system = &runloop_st->system;
bool ignore_environment_cb = runloop_st->flags &
RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB;
if (ignore_environment_cb)
return false;
/* RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE gets called
* by every core on every frame. Handle it first,
* to avoid the overhead of traversing the subsequent
* (enormous) case statement */
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
{
if (runloop_st->core_options)
*(bool*)data = runloop_st->core_options->updated;
else
*(bool*)data = false;
return true;
}
switch (cmd)
{
case RETRO_ENVIRONMENT_GET_OVERSCAN:
{
bool video_crop_overscan = settings->bools.video_crop_overscan;
*(bool*)data = !video_crop_overscan;
RARCH_LOG("[Environ]: GET_OVERSCAN: %u\n",
(unsigned)!video_crop_overscan);
}
break;
case RETRO_ENVIRONMENT_GET_CAN_DUPE:
*(bool*)data = true;
RARCH_LOG("[Environ]: GET_CAN_DUPE: true\n");
break;
case RETRO_ENVIRONMENT_GET_VARIABLE:
{
struct retro_variable *var = (struct retro_variable*)data;
size_t opt_idx;
if (!var)
return true;
var->value = NULL;
if (!runloop_st->core_options)
{
RARCH_ERR("[Environ]: GET_VARIABLE: %s - %s.\n",
var->key, "Not implemented");
return true;
}
#ifdef HAVE_RUNAHEAD
if (runloop_st->core_options->updated)
runloop_st->flags |= RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
#endif
runloop_st->core_options->updated = false;
if (core_option_manager_get_idx(runloop_st->core_options,
var->key, &opt_idx))
var->value = core_option_manager_get_val(
runloop_st->core_options, opt_idx);
if (!var->value)
{
RARCH_ERR("[Environ]: GET_VARIABLE: %s - %s.\n",
var->key, "Invalid value");
return true;
}
RARCH_DBG("[Environ]: GET_VARIABLE: %s = \"%s\"\n",
var->key, var->value);
}
break;
case RETRO_ENVIRONMENT_SET_VARIABLE:
{
const struct retro_variable *var = (const struct retro_variable*)data;
size_t opt_idx;
size_t val_idx;
/* If core passes NULL to the callback, return
* value indicates whether callback is supported */
if (!var)
return true;
if ( string_is_empty(var->key)
|| string_is_empty(var->value))
return false;
if (!runloop_st->core_options)
{
RARCH_ERR("[Environ]: SET_VARIABLE: %s - %s.\n",
var->key, "Not implemented");
return false;
}
/* Check whether key is valid */
if (!core_option_manager_get_idx(runloop_st->core_options,
var->key, &opt_idx))
{
RARCH_ERR("[Environ]: SET_VARIABLE: %s - %s.\n",
var->key, "Invalid key");
return false;
}
/* Check whether value is valid */
if (!core_option_manager_get_val_idx(runloop_st->core_options,
opt_idx, var->value, &val_idx))
{
RARCH_ERR("[Environ]: SET_VARIABLE: %s - %s: %s\n",
var->key, "Invalid value", var->value);
return false;
}
/* Update option value if core-requested value
* is not currently set */
if (val_idx != runloop_st->core_options->opts[opt_idx].index)
core_option_manager_set_val(runloop_st->core_options,
opt_idx, val_idx, true);
RARCH_DBG("[Environ]: SET_VARIABLE: %s = \"%s\"\n",
var->key, var->value);
}
break;
/* SET_VARIABLES: Legacy path */
case RETRO_ENVIRONMENT_SET_VARIABLES:
RARCH_LOG("[Environ]: SET_VARIABLES.\n");
{
core_option_manager_t *new_vars = NULL;
if (runloop_st->core_options)
{
runloop_deinit_core_options(
runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_st->core_options);
runloop_st->flags &=
~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
runloop_st->core_options = NULL;
}
if ((new_vars = runloop_init_core_variables(
settings,
(const struct retro_variable *)data)))
runloop_st->core_options = new_vars;
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS:
RARCH_LOG("[Environ]: SET_CORE_OPTIONS.\n");
{
/* Parse core_option_definition array to
* create retro_core_options_v2 struct */
struct retro_core_options_v2 *options_v2 =
core_option_manager_convert_v1(
(const struct retro_core_option_definition*)data);
if (runloop_st->core_options)
{
runloop_deinit_core_options(
runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_st->core_options);
runloop_st->flags &=
~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
runloop_st->core_options = NULL;
}
if (options_v2)
{
/* Initialise core options */
core_option_manager_t *new_vars = runloop_init_core_options(settings, options_v2);
if (new_vars)
runloop_st->core_options = new_vars;
/* Clean up */
core_option_manager_free_converted(options_v2);
}
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL:
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL.\n");
{
/* Parse core_options_intl to create
* retro_core_options_v2 struct */
struct retro_core_options_v2 *options_v2 =
core_option_manager_convert_v1_intl(
(const struct retro_core_options_intl*)data);
if (runloop_st->core_options)
{
runloop_deinit_core_options(
runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_st->core_options);
runloop_st->flags &=
~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
runloop_st->core_options = NULL;
}
if (options_v2)
{
/* Initialise core options */
core_option_manager_t *new_vars = runloop_init_core_options(settings, options_v2);
if (new_vars)
runloop_st->core_options = new_vars;
/* Clean up */
core_option_manager_free_converted(options_v2);
}
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2:
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2.\n");
{
core_option_manager_t *new_vars = NULL;
const struct retro_core_options_v2 *options_v2 =
(const struct retro_core_options_v2 *)data;
bool categories_enabled =
settings->bools.core_option_category_enable;
if (runloop_st->core_options)
{
runloop_deinit_core_options(
runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_st->core_options);
runloop_st->flags &=
~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
runloop_st->core_options = NULL;
}
if (options_v2)
{
new_vars = runloop_init_core_options(settings, options_v2);
if (new_vars)
runloop_st->core_options = new_vars;
}
/* Return value does not indicate success.
* Callback returns 'true' if core option
* categories are supported/enabled,
* otherwise 'false'. */
return categories_enabled;
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL:
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL.\n");
{
/* Parse retro_core_options_v2_intl to create
* retro_core_options_v2 struct */
core_option_manager_t *new_vars = NULL;
struct retro_core_options_v2 *options_v2 =
core_option_manager_convert_v2_intl(
(const struct retro_core_options_v2_intl*)data);
bool categories_enabled =
settings->bools.core_option_category_enable;
if (runloop_st->core_options)
{
runloop_deinit_core_options(
runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_st->core_options);
runloop_st->flags &=
~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
runloop_st->core_options = NULL;
}
if (options_v2)
{
/* Initialise core options */
new_vars = runloop_init_core_options(settings, options_v2);
if (new_vars)
runloop_st->core_options = new_vars;
/* Clean up */
core_option_manager_free_converted(options_v2);
}
/* Return value does not indicate success.
* Callback returns 'true' if core option
* categories are supported/enabled,
* otherwise 'false'. */
return categories_enabled;
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY:
RARCH_DBG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY.\n");
{
const struct retro_core_option_display *core_options_display =
(const struct retro_core_option_display *)data;
if (runloop_st->core_options && core_options_display)
core_option_manager_set_visible(
runloop_st->core_options,
core_options_display->key,
core_options_display->visible);
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK:
RARCH_DBG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK.\n");
{
const struct retro_core_options_update_display_callback
*update_display_callback =
(const struct retro_core_options_update_display_callback*)data;
if (update_display_callback &&
update_display_callback->callback)
runloop_st->core_options_callback.update_display =
update_display_callback->callback;
else
runloop_st->core_options_callback.update_display = NULL;
}
break;
case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION:
RARCH_LOG("[Environ]: GET_MESSAGE_INTERFACE_VERSION.\n");
/* Current API version is 1 */
*(unsigned *)data = 1;
break;
case RETRO_ENVIRONMENT_SET_MESSAGE:
{
const struct retro_message *msg = (const struct retro_message*)data;
#if defined(HAVE_GFX_WIDGETS)
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
if (p_dispwidget->active)
gfx_widget_set_libretro_message(
msg->msg,
roundf((float)msg->frames / 60.0f * 1000.0f));
else
#endif
runloop_msg_queue_push(msg->msg, 3, msg->frames,
true, NULL, MESSAGE_QUEUE_ICON_DEFAULT,
MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("[Environ]: SET_MESSAGE: %s\n", msg->msg);
break;
}
case RETRO_ENVIRONMENT_SET_MESSAGE_EXT:
{
const struct retro_message_ext *msg =
(const struct retro_message_ext*)data;
/* Log message, if required */
if (msg->target != RETRO_MESSAGE_TARGET_OSD)
{
switch (msg->level)
{
case RETRO_LOG_DEBUG:
RARCH_DBG("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
break;
case RETRO_LOG_WARN:
RARCH_WARN("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
break;
case RETRO_LOG_ERROR:
RARCH_ERR("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
break;
case RETRO_LOG_INFO:
default:
RARCH_LOG("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
break;
}
}
/* Display message via OSD, if required */
if (msg->target != RETRO_MESSAGE_TARGET_LOG)
{
switch (msg->type)
{
/* Handle 'status' messages */
case RETRO_MESSAGE_TYPE_STATUS:
/* Note: We need to lock a mutex here. Strictly
* speaking, 'core_status_msg' is not part
* of the message queue, but:
* - It may be implemented as a queue in the future
* - It seems unnecessary to create a new slock_t
* object for this type of message when
* _runloop_msg_queue_lock is already available
* We therefore just call runloop_msg_queue_lock()/
* runloop_msg_queue_unlock() in this case */
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
/* If a message is already set, only overwrite
* it if the new message has the same or higher
* priority */
if (!runloop_st->core_status_msg.set ||
(runloop_st->core_status_msg.priority <= msg->priority))
{
if (!string_is_empty(msg->msg))
{
strlcpy(runloop_st->core_status_msg.str, msg->msg,
sizeof(runloop_st->core_status_msg.str));
runloop_st->core_status_msg.duration = (float)msg->duration;
runloop_st->core_status_msg.set = true;
}
else
{
/* Ensure sane behaviour if core sends an
* empty message */
runloop_st->core_status_msg.str[0] = '\0';
runloop_st->core_status_msg.priority = 0;
runloop_st->core_status_msg.duration = 0.0f;
runloop_st->core_status_msg.set = false;
}
}
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
break;
#if defined(HAVE_GFX_WIDGETS)
/* Handle 'alternate' non-queued notifications */
case RETRO_MESSAGE_TYPE_NOTIFICATION_ALT:
{
video_driver_state_t *video_st =
video_state_get_ptr();
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
if (p_dispwidget->active)
gfx_widget_set_libretro_message(
msg->msg, msg->duration);
else
runloop_core_msg_queue_push(
&video_st->av_info, msg);
}
break;
/* Handle 'progress' messages */
case RETRO_MESSAGE_TYPE_PROGRESS:
{
video_driver_state_t *video_st =
video_state_get_ptr();
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
if (p_dispwidget->active)
gfx_widget_set_progress_message(
msg->msg, msg->duration,
msg->priority, msg->progress);
else
runloop_core_msg_queue_push(
&video_st->av_info, msg);
}
break;
#endif
/* Handle standard (queued) notifications */
case RETRO_MESSAGE_TYPE_NOTIFICATION:
default:
{
video_driver_state_t *video_st =
video_state_get_ptr();
runloop_core_msg_queue_push(
&video_st->av_info, msg);
}
break;
}
}
break;
}
case RETRO_ENVIRONMENT_SET_ROTATION:
{
unsigned rotation = *(const unsigned*)data;
bool video_allow_rotate = settings->bools.video_allow_rotate;
RARCH_LOG("[Environ]: SET_ROTATION: %u\n", rotation);
if (system)
system->core_requested_rotation = rotation;
if (!video_allow_rotate)
return false;
if (system)
system->rotation = rotation;
if (!video_driver_set_rotation(rotation))
return false;
break;
}
case RETRO_ENVIRONMENT_SHUTDOWN:
{
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
/* This case occurs when a core (internally)
* requests a shutdown event */
RARCH_LOG("[Environ]: SHUTDOWN.\n");
runloop_st->flags |= RUNLOOP_FLAG_CORE_SHUTDOWN_INITIATED
| RUNLOOP_FLAG_SHUTDOWN_INITIATED;
#ifdef HAVE_MENU
/* Ensure that menu stack is flushed appropriately
* after the core has stopped running */
if (menu_st)
{
const char *content_path = path_get(RARCH_PATH_CONTENT);
menu_st->flags |= MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH;
if (!string_is_empty(content_path))
strlcpy(menu_st->pending_env_shutdown_content_path,
content_path,
sizeof(menu_st->pending_env_shutdown_content_path));
else
menu_st->pending_env_shutdown_content_path[0] = '\0';
}
#endif
break;
}
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
if (system)
{
system->performance_level = *(const unsigned*)data;
RARCH_LOG("[Environ]: PERFORMANCE_LEVEL: %u.\n",
system->performance_level);
}
break;
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
{
const char *dir_system = settings->paths.directory_system;
bool systemfiles_in_content_dir = settings->bools.systemfiles_in_content_dir;
if (string_is_empty(dir_system) || systemfiles_in_content_dir)
{
const char *fullpath = path_get(RARCH_PATH_CONTENT);
if (!string_is_empty(fullpath))
{
char tmp_path[PATH_MAX_LENGTH];
if (string_is_empty(dir_system))
RARCH_WARN("[Environ]: SYSTEM DIR is empty, assume CONTENT DIR %s\n",
fullpath);
strlcpy(tmp_path, fullpath, sizeof(tmp_path));
path_basedir(tmp_path);
dir_set(RARCH_DIR_SYSTEM, tmp_path);
}
*(const char**)data = dir_get_ptr(RARCH_DIR_SYSTEM);
RARCH_LOG("[Environ]: SYSTEM_DIRECTORY: \"%s\".\n",
dir_system);
}
else
{
*(const char**)data = dir_system;
RARCH_LOG("[Environ]: SYSTEM_DIRECTORY: \"%s\".\n",
dir_system);
}
}
break;
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
RARCH_LOG("[Environ]: GET_SAVE_DIRECTORY.\n");
*(const char**)data = runloop_st->savefile_dir;
break;
case RETRO_ENVIRONMENT_GET_USERNAME:
*(const char**)data = *settings->paths.username ?
settings->paths.username : NULL;
RARCH_LOG("[Environ]: GET_USERNAME: \"%s\".\n",
settings->paths.username);
break;
case RETRO_ENVIRONMENT_GET_LANGUAGE:
#ifdef HAVE_LANGEXTRA
{
unsigned user_lang = *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE);
*(unsigned *)data = user_lang;
RARCH_LOG("[Environ]: GET_LANGUAGE: \"%u\".\n", user_lang);
}
#endif
break;
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
{
video_driver_state_t *video_st =
video_state_get_ptr();
enum retro_pixel_format pix_fmt =
*(const enum retro_pixel_format*)data;
switch (pix_fmt)
{
case RETRO_PIXEL_FORMAT_0RGB1555:
RARCH_LOG("[Environ]: SET_PIXEL_FORMAT: 0RGB1555.\n");
break;
case RETRO_PIXEL_FORMAT_RGB565:
RARCH_LOG("[Environ]: SET_PIXEL_FORMAT: RGB565.\n");
break;
case RETRO_PIXEL_FORMAT_XRGB8888:
RARCH_LOG("[Environ]: SET_PIXEL_FORMAT: XRGB8888.\n");
break;
default:
return false;
}
video_st->pix_fmt = pix_fmt;
break;
}
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
{
static const char *libretro_btn_desc[] = {
"B (bottom)", "Y (left)", "Select", "Start",
"D-Pad Up", "D-Pad Down", "D-Pad Left", "D-Pad Right",
"A (right)", "X (up)",
"L", "R", "L2", "R2", "L3", "R3",
};
if (system)
{
unsigned retro_id;
const struct retro_input_descriptor *desc = NULL;
memset((void*)&system->input_desc_btn, 0,
sizeof(system->input_desc_btn));
desc = (const struct retro_input_descriptor*)data;
for (; desc->description; desc++)
{
unsigned retro_port = desc->port;
retro_id = desc->id;
if (desc->port >= MAX_USERS)
continue;
if (desc->id >= RARCH_FIRST_CUSTOM_BIND)
continue;
switch (desc->device)
{
case RETRO_DEVICE_JOYPAD:
system->input_desc_btn[retro_port]
[retro_id] = desc->description;
break;
case RETRO_DEVICE_ANALOG:
switch (retro_id)
{
case RETRO_DEVICE_ID_ANALOG_X:
switch (desc->index)
{
case RETRO_DEVICE_INDEX_ANALOG_LEFT:
system->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_X_PLUS] = desc->description;
system->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_X_MINUS] = desc->description;
break;
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
system->input_desc_btn[retro_port]
[RARCH_ANALOG_RIGHT_X_PLUS] = desc->description;
system->input_desc_btn[retro_port]
[RARCH_ANALOG_RIGHT_X_MINUS] = desc->description;
break;
}
break;
case RETRO_DEVICE_ID_ANALOG_Y:
switch (desc->index)
{
case RETRO_DEVICE_INDEX_ANALOG_LEFT:
system->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_Y_PLUS] = desc->description;
system->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_Y_MINUS] = desc->description;
break;
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
system->input_desc_btn[retro_port]
[RARCH_ANALOG_RIGHT_Y_PLUS] = desc->description;
system->input_desc_btn[retro_port]
[RARCH_ANALOG_RIGHT_Y_MINUS] = desc->description;
break;
}
break;
case RETRO_DEVICE_ID_JOYPAD_R2:
switch (desc->index)
{
case RETRO_DEVICE_INDEX_ANALOG_BUTTON:
system->input_desc_btn[retro_port]
[retro_id] = desc->description;
break;
}
break;
case RETRO_DEVICE_ID_JOYPAD_L2:
switch (desc->index)
{
case RETRO_DEVICE_INDEX_ANALOG_BUTTON:
system->input_desc_btn[retro_port]
[retro_id] = desc->description;
break;
}
break;
}
break;
}
}
RARCH_LOG("[Environ]: SET_INPUT_DESCRIPTORS:\n");
{
unsigned log_level = settings->uints.libretro_log_level;
if (log_level == RETRO_LOG_DEBUG)
{
unsigned input_driver_max_users = settings->uints.input_max_users;
for (p = 0; p < input_driver_max_users; p++)
{
unsigned mapped_port = settings->uints.input_remap_ports[p];
for (retro_id = 0; retro_id < RARCH_FIRST_CUSTOM_BIND; retro_id++)
{
const char *description = system->input_desc_btn[mapped_port][retro_id];
if (!description)
continue;
RARCH_DBG(" RetroPad, Port %u, Button \"%s\" => \"%s\"\n",
p + 1, libretro_btn_desc[retro_id], description);
}
}
}
}
runloop_st->current_core.flags |=
RETRO_CORE_FLAG_HAS_SET_INPUT_DESCRIPTORS;
}
break;
}
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
{
input_driver_state_t
*input_st = input_state_get_ptr();
const struct retro_keyboard_callback *info =
(const struct retro_keyboard_callback*)data;
retro_keyboard_event_t *frontend_key_event = &runloop_st->frontend_key_event;
retro_keyboard_event_t *key_event = &runloop_st->key_event;
RARCH_LOG("[Environ]: SET_KEYBOARD_CALLBACK.\n");
if (key_event)
*key_event = info->callback;
if (frontend_key_event && key_event)
*frontend_key_event = *key_event;
/* If a core calls RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK,
* then it is assumed that Game Focus mode is desired */
input_st->game_focus_state.core_requested = true;
break;
}
case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION:
RARCH_LOG("[Environ]: GET_DISK_CONTROL_INTERFACE_VERSION.\n");
/* Current API version is 1 */
*(unsigned *)data = 1;
break;
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE:
{
const struct retro_disk_control_callback *control_cb =
(const struct retro_disk_control_callback*)data;
if (system)
{
RARCH_LOG("[Environ]: SET_DISK_CONTROL_INTERFACE.\n");
disk_control_set_callback(&system->disk_control, control_cb);
}
}
break;
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE:
{
const struct retro_disk_control_ext_callback *control_cb =
(const struct retro_disk_control_ext_callback*)data;
if (system)
{
RARCH_LOG("[Environ]: SET_DISK_CONTROL_EXT_INTERFACE.\n");
disk_control_set_ext_callback(&system->disk_control, control_cb);
}
}
break;
case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER:
{
unsigned *cb = (unsigned*)data;
settings_t *settings = config_get_ptr();
const char *video_driver_name = settings->arrays.video_driver;
bool driver_switch_enable = settings->bools.driver_switch_enable;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER, video driver name: %s.\n", video_driver_name);
if (string_is_equal(video_driver_name, "glcore"))
{
*cb = RETRO_HW_CONTEXT_OPENGL_CORE;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_OPENGL_CORE.\n");
}
else if (string_is_equal(video_driver_name, "gl"))
{
*cb = RETRO_HW_CONTEXT_OPENGL;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_OPENGL.\n");
}
else if (string_is_equal(video_driver_name, "vulkan"))
{
*cb = RETRO_HW_CONTEXT_VULKAN;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_VULKAN.\n");
}
else if (string_is_equal(video_driver_name, "d3d11"))
{
*cb = RETRO_HW_CONTEXT_D3D11;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_D3D11.\n");
}
else if (string_is_equal(video_driver_name, "d3d12"))
{
*cb = RETRO_HW_CONTEXT_D3D12;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_D3D12.\n");
}
else
{
*cb = RETRO_HW_CONTEXT_NONE;
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_NONE.\n");
}
if (!driver_switch_enable)
{
RARCH_LOG("[Environ]: Driver switching disabled, GET_PREFERRED_HW_RENDER will be ignored.\n");
return false;
}
break;
}
case RETRO_ENVIRONMENT_SET_HW_RENDER:
case RETRO_ENVIRONMENT_SET_HW_RENDER | RETRO_ENVIRONMENT_EXPERIMENTAL:
{
settings_t *settings = config_get_ptr();
struct retro_hw_render_callback *cb =
(struct retro_hw_render_callback*)data;
video_driver_state_t *video_st =
video_state_get_ptr();
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(video_st);
if (!cb)
{
RARCH_ERR("[Environ]: SET_HW_RENDER - No valid callback passed, returning...\n");
return false;
}
RARCH_LOG("[Environ]: SET_HW_RENDER, context type: %s.\n", hw_render_context_name(cb->context_type, cb->version_major, cb->version_minor));
if (!dynamic_request_hw_context(
cb->context_type, cb->version_minor, cb->version_major))
{
RARCH_ERR("[Environ]: SET_HW_RENDER - Dynamic request HW context failed.\n");
return false;
}
if (!dynamic_verify_hw_context(
settings->arrays.video_driver,
settings->bools.driver_switch_enable,
cb->context_type, cb->version_minor, cb->version_major))
{
RARCH_ERR("[Environ]: SET_HW_RENDER: Dynamic verify HW context failed.\n");
return false;
}
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGL_CORE)
/* TODO/FIXME - should check first if an OpenGL
* driver is running */
if (cb->context_type == RETRO_HW_CONTEXT_OPENGL_CORE)
{
/* Ensure that the rest of the frontend knows
* we have a core context */
gfx_ctx_flags_t flags;
flags.flags = 0;
BIT32_SET(flags.flags, GFX_CTX_FLAGS_GL_CORE_CONTEXT);
video_context_driver_set_flags(&flags);
}
#endif
cb->get_current_framebuffer = video_driver_get_current_framebuffer;
cb->get_proc_address = video_driver_get_proc_address;
/* Old ABI. Don't copy garbage. */
if (cmd & RETRO_ENVIRONMENT_EXPERIMENTAL)
{
memcpy(hwr,
cb, offsetof(struct retro_hw_render_callback, stencil));
memset((uint8_t*)hwr + offsetof(struct retro_hw_render_callback, stencil),
0, sizeof(*cb) - offsetof(struct retro_hw_render_callback, stencil));
}
else
memcpy(hwr, cb, sizeof(*cb));
RARCH_DBG("Reached end of SET_HW_RENDER.\n");
break;
}
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
{
bool state = *(const bool*)data;
RARCH_LOG("[Environ]: SET_SUPPORT_NO_GAME: %s.\n", state ? "yes" : "no");
if (state)
content_set_does_not_need_content();
else
content_unset_does_not_need_content();
break;
}
case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH:
{
const char **path = (const char**)data;
RARCH_LOG("[Environ]: GET_LIBRETRO_PATH.\n");
#ifdef HAVE_DYNAMIC
*path = path_get(RARCH_PATH_CORE);
#else
*path = NULL;
#endif
break;
}
case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK:
#ifdef HAVE_THREADS
{
recording_state_t
*recording_st = recording_state_get_ptr();
audio_driver_state_t
*audio_st = audio_state_get_ptr();
const struct
retro_audio_callback *cb = (const struct retro_audio_callback*)data;
RARCH_LOG("[Environ]: SET_AUDIO_CALLBACK.\n");
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
return false;
#endif
if (recording_st->data) /* A/V sync is a must. */
return false;
if (cb)
audio_st->callback = *cb;
}
break;
#else
return false;
#endif
case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
{
const struct retro_frame_time_callback *info =
(const struct retro_frame_time_callback*)data;
RARCH_LOG("[Environ]: SET_FRAME_TIME_CALLBACK.\n");
#ifdef HAVE_NETWORKING
/* retro_run() will be called in very strange and
* mysterious ways, have to disable it. */
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
return false;
#endif
runloop_st->frame_time = *info;
break;
}
case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK:
{
const struct retro_audio_buffer_status_callback *info =
(const struct retro_audio_buffer_status_callback*)data;
RARCH_LOG("[Environ]: SET_AUDIO_BUFFER_STATUS_CALLBACK.\n");
if (info)
runloop_st->audio_buffer_status.callback = info->callback;
else
runloop_st->audio_buffer_status.callback = NULL;
break;
}
case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY:
{
unsigned audio_latency_default = settings->uints.audio_latency;
unsigned audio_latency_current =
(runloop_st->audio_latency > audio_latency_default) ?
runloop_st->audio_latency : audio_latency_default;
unsigned audio_latency_new;
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY.\n");
/* Sanitise input latency value */
runloop_st->audio_latency = 0;
if (data)
runloop_st->audio_latency = *(const unsigned*)data;
if (runloop_st->audio_latency > 512)
{
RARCH_WARN("[Environ]: Requested audio latency of %u ms - limiting to maximum of 512 ms.\n",
runloop_st->audio_latency);
runloop_st->audio_latency = 512;
}
/* Determine new set-point latency value */
if (runloop_st->audio_latency >= audio_latency_default)
audio_latency_new = runloop_st->audio_latency;
else
{
if (runloop_st->audio_latency != 0)
RARCH_WARN("[Environ]: Requested audio latency of %u ms is less than frontend default of %u ms."
" Using frontend default...\n",
runloop_st->audio_latency, audio_latency_default);
audio_latency_new = audio_latency_default;
}
/* Check whether audio driver requires reinitialisation
* (Identical to RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO,
* without video driver initialisation) */
if (audio_latency_new != audio_latency_current)
{
recording_state_t
*recording_st = recording_state_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
bool video_fullscreen = settings->bools.video_fullscreen;
int reinit_flags = DRIVERS_CMD_ALL &
~(DRIVER_VIDEO_MASK | DRIVER_INPUT_MASK | DRIVER_MENU_MASK);
RARCH_LOG("[Environ]: Setting audio latency to %u ms.\n", audio_latency_new);
command_event(CMD_EVENT_REINIT, &reinit_flags);
video_driver_set_aspect_ratio();
/* Cannot continue recording with different
* parameters.
* Take the easiest route out and just restart
* the recording. */
if (recording_st->data)
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_RESTARTING_RECORDING_DUE_TO_DRIVER_REINIT),
2, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
command_event(CMD_EVENT_RECORD_INIT, NULL);
}
/* Hide mouse cursor in fullscreen mode */
if (video_fullscreen)
{
if ( video_st->poke
&& video_st->poke->show_mouse)
video_st->poke->show_mouse(video_st->data, false);
}
}
break;
}
case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
{
struct retro_rumble_interface *iface =
(struct retro_rumble_interface*)data;
RARCH_LOG("[Environ]: GET_RUMBLE_INTERFACE.\n");
iface->set_rumble_state = input_set_rumble_state;
break;
}
case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES:
{
uint64_t *mask = (uint64_t*)data;
input_driver_state_t
*input_st = input_state_get_ptr();
RARCH_LOG("[Environ]: GET_INPUT_DEVICE_CAPABILITIES.\n");
if ( !input_st->current_driver->get_capabilities
|| !input_st->current_data)
return false;
*mask = input_driver_get_capabilities();
break;
}
case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE:
{
settings_t *settings = config_get_ptr();
bool input_sensors_enable = settings->bools.input_sensors_enable;
struct retro_sensor_interface *iface = (struct retro_sensor_interface*)data;
RARCH_LOG("[Environ]: GET_SENSOR_INTERFACE.\n");
if (!input_sensors_enable)
return false;
iface->set_sensor_state = input_set_sensor_state;
iface->get_sensor_input = input_get_sensor_state;
break;
}
case RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE:
{
struct retro_camera_callback *cb =
(struct retro_camera_callback*)data;
camera_driver_state_t *camera_st = camera_state_get_ptr();
RARCH_LOG("[Environ]: GET_CAMERA_INTERFACE.\n");
cb->start = driver_camera_start;
cb->stop = driver_camera_stop;
camera_st->cb = *cb;
camera_st->active = (cb->caps != 0);
break;
}
case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
{
struct retro_location_callback *cb =
(struct retro_location_callback*)data;
location_driver_state_t
*location_st = location_state_get_ptr();
RARCH_LOG("[Environ]: GET_LOCATION_INTERFACE.\n");
cb->start = driver_location_start;
cb->stop = driver_location_stop;
cb->get_position = driver_location_get_position;
cb->set_interval = driver_location_set_interval;
if (system)
system->location_cb = *cb;
location_st->active = false;
break;
}
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
{
struct retro_log_callback *cb = (struct retro_log_callback*)data;
RARCH_LOG("[Environ]: GET_LOG_INTERFACE.\n");
cb->log = libretro_log_cb;
break;
}
case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
{
struct retro_perf_callback *cb = (struct retro_perf_callback*)data;
RARCH_LOG("[Environ]: GET_PERF_INTERFACE.\n");
cb->get_time_usec = cpu_features_get_time_usec;
cb->get_cpu_features = cpu_features_get;
cb->get_perf_counter = cpu_features_get_perf_counter;
cb->perf_register = runloop_performance_counter_register;
cb->perf_start = core_performance_counter_start;
cb->perf_stop = core_performance_counter_stop;
cb->perf_log = runloop_perf_log;
break;
}
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
{
const char **dir = (const char**)data;
const char *dir_core_assets = settings->paths.directory_core_assets;
*dir = *dir_core_assets ? dir_core_assets : NULL;
RARCH_LOG("[Environ]: CORE_ASSETS_DIRECTORY: \"%s\".\n",
dir_core_assets);
break;
}
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
/**
* Update the system Audio/Video information.
* Will reinitialize audio/video drivers if needed.
* Used by RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO.
**/
{
const struct retro_system_av_info **info = (const struct retro_system_av_info**)&data;
video_driver_state_t *video_st = video_state_get_ptr();
struct retro_system_av_info *av_info = &video_st->av_info;
if (data)
{
int reinit_flags = DRIVERS_CMD_ALL;
settings_t *settings = config_get_ptr();
float refresh_rate = (*info)->timing.fps;
unsigned crt_switch_resolution = settings->uints.crt_switch_resolution;
bool video_fullscreen = settings->bools.video_fullscreen;
bool video_switch_refresh_rate = false;
bool no_video_reinit = true;
/* Refresh rate switch for regular displays */
if (video_display_server_has_resolution_list())
video_switch_refresh_rate_maybe(&refresh_rate, &video_switch_refresh_rate);
no_video_reinit = (
crt_switch_resolution == 0
&& video_switch_refresh_rate == false
&& data
&& ((*info)->geometry.max_width == av_info->geometry.max_width)
&& ((*info)->geometry.max_height == av_info->geometry.max_height));
/* First set new refresh rate and display rate, then after REINIT do
* another display rate change to make sure the change stays */
if (video_switch_refresh_rate && video_display_server_set_refresh_rate(refresh_rate))
video_monitor_set_refresh_rate(refresh_rate);
/* When not doing video reinit, we also must not do input and menu
* reinit, otherwise the input driver crashes and the menu gets
* corrupted. */
if (no_video_reinit)
reinit_flags =
DRIVERS_CMD_ALL &
~(DRIVER_VIDEO_MASK | DRIVER_INPUT_MASK |
DRIVER_MENU_MASK);
RARCH_LOG("[Environ]: SET_SYSTEM_AV_INFO: %ux%u, Aspect: %.3f, FPS: %.2f, Sample rate: %.2f Hz.\n",
(*info)->geometry.base_width, (*info)->geometry.base_height,
(*info)->geometry.aspect_ratio,
(*info)->timing.fps,
(*info)->timing.sample_rate);
memcpy(av_info, *info, sizeof(*av_info));
video_st->core_frame_time = 1000000 /
((video_st->av_info.timing.fps > 0.0) ?
video_st->av_info.timing.fps : 60.0);
command_event(CMD_EVENT_REINIT, &reinit_flags);
if (no_video_reinit)
video_driver_set_aspect_ratio();
if (video_switch_refresh_rate)
video_display_server_set_refresh_rate(refresh_rate);
/* Cannot continue recording with different parameters.
* Take the easiest route out and just restart
* the recording. */
if (recording_st->data)
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_RESTARTING_RECORDING_DUE_TO_DRIVER_REINIT),
2, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
command_event(CMD_EVENT_RECORD_INIT, NULL);
}
/* Hide mouse cursor in fullscreen after
* a RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO call. */
if (video_fullscreen)
{
if ( video_st->poke
&& video_st->poke->show_mouse)
video_st->poke->show_mouse(video_st->data, false);
}
/* Recalibrate frame delay target when video reinits
* and pause frame delay when video does not reinit */
if (settings->bools.video_frame_delay_auto)
{
if (no_video_reinit)
video_st->frame_delay_pause = true;
else
video_st->frame_delay_target = 0;
}
return true;
}
return false;
}
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
{
unsigned i;
const struct retro_subsystem_info *info =
(const struct retro_subsystem_info*)data;
unsigned log_level = settings->uints.libretro_log_level;
RARCH_DBG("[Environ]: SET_SUBSYSTEM_INFO.\n");
for (i = 0; info[i].ident; i++)
{
unsigned j;
if (log_level != RETRO_LOG_DEBUG)
continue;
RARCH_DBG("Special game type: %s\n Ident: %s\n ID: %u\n Content:\n",
info[i].desc,
info[i].ident,
info[i].id
);
for (j = 0; j < info[i].num_roms; j++)
{
RARCH_DBG(" %s (%s)\n",
info[i].roms[j].desc, info[i].roms[j].required ?
"required" : "optional");
}
}
if (system)
{
struct retro_subsystem_info *info_ptr = NULL;
free(system->subsystem.data);
system->subsystem.data = NULL;
system->subsystem.size = 0;
info_ptr = (struct retro_subsystem_info*)
malloc(i * sizeof(*info_ptr));
if (!info_ptr)
return false;
system->subsystem.data = info_ptr;
memcpy(system->subsystem.data, info,
i * sizeof(*system->subsystem.data));
system->subsystem.size = i;
runloop_st->current_core.flags |=
RETRO_CORE_FLAG_HAS_SET_SUBSYSTEMS;
}
break;
}
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
{
unsigned i, j;
const struct retro_controller_info *info
= (const struct retro_controller_info*)data;
unsigned log_level = settings->uints.libretro_log_level;
RARCH_LOG("[Environ]: SET_CONTROLLER_INFO.\n");
for (i = 0; info[i].types; i++)
{
if (log_level != RETRO_LOG_DEBUG)
continue;
RARCH_DBG(" Controller port: %u\n", i + 1);
for (j = 0; j < info[i].num_types; j++)
RARCH_DBG(" %s (ID: %u)\n", info[i].types[j].desc,
info[i].types[j].id);
}
if (system)
{
struct retro_controller_info *info_ptr = NULL;
free(system->ports.data);
system->ports.data = NULL;
system->ports.size = 0;
info_ptr = (struct retro_controller_info*)calloc(i, sizeof(*info_ptr));
if (!info_ptr)
return false;
system->ports.data = info_ptr;
memcpy(system->ports.data, info,
i * sizeof(*system->ports.data));
system->ports.size = i;
}
break;
}
case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
{
if (system)
{
unsigned i;
const struct retro_memory_map *mmaps =
(const struct retro_memory_map*)data;
rarch_memory_descriptor_t *descriptors = NULL;
unsigned int log_level = settings->uints.libretro_log_level;
RARCH_LOG("[Environ]: SET_MEMORY_MAPS.\n");
free((void*)system->mmaps.descriptors);
system->mmaps.descriptors = 0;
system->mmaps.num_descriptors = 0;
descriptors = (rarch_memory_descriptor_t*)calloc(mmaps->num_descriptors,
sizeof(*descriptors));
if (!descriptors)
return false;
system->mmaps.descriptors = descriptors;
system->mmaps.num_descriptors = mmaps->num_descriptors;
for (i = 0; i < mmaps->num_descriptors; i++)
system->mmaps.descriptors[i].core = mmaps->descriptors[i];
mmap_preprocess_descriptors(descriptors, mmaps->num_descriptors);
#ifdef HAVE_CHEEVOS
rcheevos_refresh_memory();
#endif
#ifdef HAVE_CHEATS
if (cheat_manager_state.memory_initialized)
{
cheat_manager_initialize_memory(NULL, 0, true);
cheat_manager_apply_retro_cheats();
}
#endif
if (log_level != RETRO_LOG_DEBUG)
break;
if (sizeof(void *) == 8)
RARCH_DBG(" ndx flags ptr offset start select disconn len addrspace\n");
else
RARCH_DBG(" ndx flags ptr offset start select disconn len addrspace\n");
for (i = 0; i < system->mmaps.num_descriptors; i++)
{
const rarch_memory_descriptor_t *desc =
&system->mmaps.descriptors[i];
char flags[7];
flags[0] = 'M';
if ((desc->core.flags & RETRO_MEMDESC_MINSIZE_8) == RETRO_MEMDESC_MINSIZE_8)
flags[1] = '8';
else if ((desc->core.flags & RETRO_MEMDESC_MINSIZE_4) == RETRO_MEMDESC_MINSIZE_4)
flags[1] = '4';
else if ((desc->core.flags & RETRO_MEMDESC_MINSIZE_2) == RETRO_MEMDESC_MINSIZE_2)
flags[1] = '2';
else
flags[1] = '1';
flags[2] = 'A';
if ((desc->core.flags & RETRO_MEMDESC_ALIGN_8) == RETRO_MEMDESC_ALIGN_8)
flags[3] = '8';
else if ((desc->core.flags & RETRO_MEMDESC_ALIGN_4) == RETRO_MEMDESC_ALIGN_4)
flags[3] = '4';
else if ((desc->core.flags & RETRO_MEMDESC_ALIGN_2) == RETRO_MEMDESC_ALIGN_2)
flags[3] = '2';
else
flags[3] = '1';
flags[4] = (desc->core.flags & RETRO_MEMDESC_BIGENDIAN) ? 'B' : 'b';
flags[5] = (desc->core.flags & RETRO_MEMDESC_CONST) ? 'C' : 'c';
flags[6] = 0;
RARCH_DBG(" %03u %s %p %08X %08X %08X %08X %08X %s\n",
i + 1, flags, desc->core.ptr, desc->core.offset, desc->core.start,
desc->core.select, desc->core.disconnect, desc->core.len,
desc->core.addrspace ? desc->core.addrspace : "");
}
}
else
{
RARCH_WARN("[Environ]: SET_MEMORY_MAPS, but system pointer not initialized..\n");
}
break;
}
case RETRO_ENVIRONMENT_SET_GEOMETRY:
{
video_driver_state_t *video_st = video_state_get_ptr();
struct retro_system_av_info *av_info = &video_st->av_info;
struct retro_game_geometry *geom = (struct retro_game_geometry*)&av_info->geometry;
const struct retro_game_geometry *in_geom= (const struct retro_game_geometry*)data;
if (!geom)
return false;
/* Can potentially be called every frame,
* don't do anything unless required. */
if ( (geom->base_width != in_geom->base_width)
|| (geom->base_height != in_geom->base_height)
|| (geom->aspect_ratio != in_geom->aspect_ratio))
{
geom->base_width = in_geom->base_width;
geom->base_height = in_geom->base_height;
geom->aspect_ratio = in_geom->aspect_ratio;
RARCH_LOG("[Environ]: SET_GEOMETRY: %ux%u, Aspect: %.3f.\n",
geom->base_width, geom->base_height, geom->aspect_ratio);
/* Forces recomputation of aspect ratios if
* using core-dependent aspect ratios. */
video_driver_set_aspect_ratio();
/* Ignore frame delay target temporarily */
if (settings->bools.video_frame_delay_auto)
video_st->frame_delay_pause = true;
/* TODO: Figure out what to do, if anything, with
recording. */
}
else
{
RARCH_LOG("[Environ]: SET_GEOMETRY.\n");
}
break;
}
case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER:
{
video_driver_state_t *video_st = video_state_get_ptr();
struct retro_framebuffer *fb = (struct retro_framebuffer*)data;
if (
video_st->poke
&& video_st->poke->get_current_software_framebuffer
&& video_st->poke->get_current_software_framebuffer(
video_st->data, fb))
return true;
return false;
}
case RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE:
{
video_driver_state_t *video_st = video_state_get_ptr();
const struct retro_hw_render_interface **iface = (const struct retro_hw_render_interface **)data;
if (
video_st->poke
&& video_st->poke->get_hw_render_interface
&& video_st->poke->get_hw_render_interface(
video_st->data, iface))
return true;
return false;
}
case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS:
#ifdef HAVE_CHEEVOS
{
bool state = *(const bool*)data;
RARCH_LOG("[Environ]: SET_SUPPORT_ACHIEVEMENTS: %s.\n", state ? "yes" : "no");
rcheevos_set_support_cheevos(state);
}
#endif
break;
case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE:
{
video_driver_state_t *video_st = video_state_get_ptr();
const struct retro_hw_render_context_negotiation_interface *iface =
(const struct retro_hw_render_context_negotiation_interface*)data;
RARCH_LOG("[Environ]: SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE.\n");
video_st->hw_render_context_negotiation = iface;
break;
}
case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
{
uint64_t *quirks = (uint64_t *) data;
RARCH_LOG("[Environ]: SET_SERIALIZATION_QUIRKS.\n");
runloop_st->current_core.serialization_quirks_v = *quirks;
break;
}
case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
#ifdef HAVE_LIBNX
RARCH_LOG("[Environ]: SET_HW_SHARED_CONTEXT - ignored for now.\n");
/* TODO/FIXME - Force this off for now for Switch
* until shared HW context can work there */
return false;
#else
RARCH_LOG("[Environ]: SET_HW_SHARED_CONTEXT.\n");
runloop_st->flags |= RUNLOOP_FLAG_CORE_SET_SHARED_CONTEXT;
#endif
break;
case RETRO_ENVIRONMENT_GET_VFS_INTERFACE:
{
const uint32_t supported_vfs_version = 3;
static struct retro_vfs_interface vfs_iface =
{
/* VFS API v1 */
retro_vfs_file_get_path_impl,
retro_vfs_file_open_impl,
retro_vfs_file_close_impl,
retro_vfs_file_size_impl,
retro_vfs_file_tell_impl,
retro_vfs_file_seek_impl,
retro_vfs_file_read_impl,
retro_vfs_file_write_impl,
retro_vfs_file_flush_impl,
retro_vfs_file_remove_impl,
retro_vfs_file_rename_impl,
/* VFS API v2 */
retro_vfs_file_truncate_impl,
/* VFS API v3 */
retro_vfs_stat_impl,
retro_vfs_mkdir_impl,
retro_vfs_opendir_impl,
retro_vfs_readdir_impl,
retro_vfs_dirent_get_name_impl,
retro_vfs_dirent_is_dir_impl,
retro_vfs_closedir_impl
};
struct retro_vfs_interface_info *vfs_iface_info = (struct retro_vfs_interface_info *) data;
if (vfs_iface_info->required_interface_version <= supported_vfs_version)
{
RARCH_LOG("[Environ]: GET_VFS_INTERFACE. Core requested version >= V%d, providing V%d.\n",
vfs_iface_info->required_interface_version, supported_vfs_version);
vfs_iface_info->required_interface_version = supported_vfs_version;
vfs_iface_info->iface = &vfs_iface;
system->supports_vfs = true;
}
else
{
RARCH_WARN("[Environ]: GET_VFS_INTERFACE. Core requested version V%d which is higher than what we support (V%d).\n",
vfs_iface_info->required_interface_version, supported_vfs_version);
return false;
}
break;
}
case RETRO_ENVIRONMENT_GET_LED_INTERFACE:
{
struct retro_led_interface *ledintf = (struct retro_led_interface *)data;
if (ledintf)
ledintf->set_led_state = led_driver_set_led;
RARCH_LOG("[Environ]: GET_LED_INTERFACE.\n");
break;
}
case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE:
{
int result = 0;
video_driver_state_t *video_st = video_state_get_ptr();
audio_driver_state_t *audio_st = audio_state_get_ptr();
if ( !(audio_st->flags & AUDIO_FLAG_SUSPENDED)
&& (audio_st->flags & AUDIO_FLAG_ACTIVE))
result |= 2;
if ( (video_st->flags & VIDEO_FLAG_ACTIVE)
&& !(video_st->current_video->frame == video_null.frame))
result |= 1;
#ifdef HAVE_RUNAHEAD
if (audio_st->flags & AUDIO_FLAG_HARD_DISABLE)
result |= 8;
#endif
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_REPLAYING, NULL))
result &= ~(1|2);
#endif
#if defined(HAVE_RUNAHEAD) || defined(HAVE_NETWORKING)
/* Deprecated.
Use RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT instead. */
/* TODO/FIXME: Get rid of this ugly hack. */
if (runloop_st->flags & RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE)
result |= 4;
#endif
if (data)
{
int* result_p = (int*)data;
*result_p = result;
}
break;
}
case RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT:
{
int result = RETRO_SAVESTATE_CONTEXT_NORMAL;
#if defined(HAVE_RUNAHEAD) || defined(HAVE_NETWORKING)
if (runloop_st->flags & RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE)
{
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
result = RETRO_SAVESTATE_CONTEXT_ROLLBACK_NETPLAY;
else
#endif
{
#ifdef HAVE_RUNAHEAD
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
settings_t *settings = config_get_ptr();
if ( settings->bools.run_ahead_secondary_instance
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE)
&& secondary_core_ensure_exists(runloop_st, settings))
result = RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_BINARY;
else
#endif
result = RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE;
#endif
}
}
#endif
if (data)
*(int*)data = result;
break;
}
case RETRO_ENVIRONMENT_GET_MIDI_INTERFACE:
{
struct retro_midi_interface *midi_interface =
(struct retro_midi_interface *)data;
if (midi_interface)
{
midi_interface->input_enabled = midi_driver_input_enabled;
midi_interface->output_enabled = midi_driver_output_enabled;
midi_interface->read = midi_driver_read;
midi_interface->write = midi_driver_write;
midi_interface->flush = midi_driver_flush;
}
break;
}
case RETRO_ENVIRONMENT_GET_FASTFORWARDING:
*(bool *)data = ((runloop_st->flags & RUNLOOP_FLAG_FASTMOTION) > 0);
break;
case RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE:
{
struct retro_fastforwarding_override *fastforwarding_override =
(struct retro_fastforwarding_override *)data;
/* Record new retro_fastforwarding_override parameters
* and schedule application on the the next call of
* runloop_check_state() */
if (fastforwarding_override)
{
memcpy(&runloop_st->fastmotion_override.next,
fastforwarding_override,
sizeof(runloop_st->fastmotion_override.next));
runloop_st->fastmotion_override.pending = true;
}
break;
}
case RETRO_ENVIRONMENT_GET_THROTTLE_STATE:
{
video_driver_state_t *video_st = video_state_get_ptr();
audio_driver_state_t *audio_st = audio_state_get_ptr();
struct retro_throttle_state *throttle_state
= (struct retro_throttle_state *)data;
bool menu_opened = false;
bool core_paused = runloop_st->flags & RUNLOOP_FLAG_PAUSED;
bool no_audio = ((audio_st->flags & AUDIO_FLAG_SUSPENDED)
|| !(audio_st->flags & AUDIO_FLAG_ACTIVE));
float core_fps = (float)video_st->av_info.timing.fps;
#ifdef HAVE_REWIND
if (runloop_st->rewind_st.flags
& STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED)
{
throttle_state->mode = RETRO_THROTTLE_REWINDING;
throttle_state->rate = 0.0f;
break; /* ignore vsync */
}
#endif
#ifdef HAVE_MENU
menu_opened = menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE;
if (menu_opened)
#ifdef HAVE_NETWORKING
core_paused = settings->bools.menu_pause_libretro &&
netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
#else
core_paused = settings->bools.menu_pause_libretro;
#endif
#endif
if (core_paused)
{
throttle_state->mode = RETRO_THROTTLE_FRAME_STEPPING;
throttle_state->rate = 0.0f;
break; /* ignore vsync */
}
/* Base mode and rate. */
throttle_state->mode = RETRO_THROTTLE_NONE;
throttle_state->rate = core_fps;
if (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
{
throttle_state->mode = RETRO_THROTTLE_FAST_FORWARD;
throttle_state->rate *= runloop_get_fastforward_ratio(
settings, &runloop_st->fastmotion_override.current);
}
else if ((runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
&& !no_audio)
{
throttle_state->mode = RETRO_THROTTLE_SLOW_MOTION;
throttle_state->rate /= (settings->floats.slowmotion_ratio > 0.0f ?
settings->floats.slowmotion_ratio : 1.0f);
}
/* VSync overrides the mode if the rate is limited by the display. */
if (menu_opened || /* Menu currently always runs with vsync on. */
( settings->bools.video_vsync
&& (!(runloop_st->flags & RUNLOOP_FLAG_FORCE_NONBLOCK))
&& !(input_state_get_ptr()->flags & INP_FLAG_NONBLOCKING)))
{
float refresh_rate = video_driver_get_refresh_rate();
if (refresh_rate == 0.0f)
refresh_rate = settings->floats.video_refresh_rate;
if (refresh_rate < throttle_state->rate || !throttle_state->rate)
{
/* Keep the mode as fast forward even if vsync limits it. */
if (refresh_rate < core_fps)
throttle_state->mode = RETRO_THROTTLE_VSYNC;
throttle_state->rate = refresh_rate;
}
}
/* Special behavior while audio output is not available. */
if (no_audio && throttle_state->mode != RETRO_THROTTLE_FAST_FORWARD
&& throttle_state->mode != RETRO_THROTTLE_VSYNC)
{
/* Keep base if frame limiter matching the core is active. */
retro_time_t core_limit = (core_fps
? (retro_time_t)(1000000.0f / core_fps)
: (retro_time_t)0);
retro_time_t frame_limit = runloop_st->frame_limit_minimum_time;
if (abs((int)(core_limit - frame_limit)) > 10)
{
throttle_state->mode = RETRO_THROTTLE_UNBLOCKED;
throttle_state->rate = 0.0f;
}
}
break;
}
case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS:
/* Just falldown, the function will return true */
break;
case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION:
RARCH_LOG("[Environ]: GET_CORE_OPTIONS_VERSION.\n");
/* Current API version is 2 */
*(unsigned *)data = 2;
break;
case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE:
{
/* Try to use the polled refresh rate first. */
float target_refresh_rate = video_driver_get_refresh_rate();
/* If the above function failed [possibly because it is not
* implemented], use the refresh rate set in the config instead. */
if (target_refresh_rate == 0.0f)
{
if (settings)
target_refresh_rate = settings->floats.video_refresh_rate;
}
*(float *)data = target_refresh_rate;
break;
}
case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS:
*(unsigned *)data = settings->uints.input_max_users;
break;
/* Private environment callbacks.
*
* Should all be properly addressed in version 2.
* */
case RETRO_ENVIRONMENT_POLL_TYPE_OVERRIDE:
{
const unsigned *poll_type_data = (const unsigned*)data;
if (poll_type_data)
runloop_st->core_poll_type_override = (enum poll_type_override_t)*poll_type_data;
}
break;
case RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB:
*(retro_environment_t *)data = runloop_clear_all_thread_waits;
break;
case RETRO_ENVIRONMENT_SET_SAVE_STATE_IN_BACKGROUND:
{
bool state = *(const bool*)data;
RARCH_LOG("[Environ]: SET_SAVE_STATE_IN_BACKGROUND: %s.\n", state ? "yes" : "no");
set_save_state_in_background(state);
}
break;
case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE:
{
const struct retro_system_content_info_override *overrides =
(const struct retro_system_content_info_override *)data;
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE.\n");
/* Passing NULL always results in 'success' - this
* allows cores to test for frontend support of
* the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and
* RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks */
if (!overrides)
return true;
return content_file_override_set(overrides);
}
break;
case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT:
{
content_state_t *p_content =
content_state_get_ptr();
const struct retro_game_info_ext **game_info_ext =
(const struct retro_game_info_ext **)data;
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_GET_GAME_INFO_EXT.\n");
if (!game_info_ext)
return false;
if ( p_content
&& p_content->content_list
&& p_content->content_list->game_info_ext)
*game_info_ext = p_content->content_list->game_info_ext;
else
{
RARCH_ERR("[Environ]: Failed to retrieve extended game info.\n");
*game_info_ext = NULL;
return false;
}
}
break;
case RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE:
#ifdef HAVE_MICROPHONE
{
struct retro_microphone_interface* microphone = (struct retro_microphone_interface *)data;
microphone_driver_state_t *mic_st = microphone_state_get_ptr();
const microphone_driver_t *driver = mic_st->driver;
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE.\n");
if (!microphone)
return false;
/* User didn't provide a pointer for a response, what can we do? */
if (microphone->interface_version != RETRO_MICROPHONE_INTERFACE_VERSION)
{
RARCH_ERR("[Environ]: Core requested unexpected microphone interface version %u, only %u is available\n",
microphone->interface_version,
RETRO_MICROPHONE_INTERFACE_VERSION);
return false;
}
/* Initialize the interface... */
memset(microphone, 0, sizeof(*microphone));
if (driver == &microphone_null)
{ /* If the null driver is active... */
RARCH_ERR("[Environ]: Cannot initialize microphone interface, active driver is null\n");
return false;
}
if (!settings->bools.microphone_enable)
{ /* If mic support is off... */
RARCH_WARN("[Environ]: Will not initialize microphone interface, support is turned off\n");
return false;
}
/* The core might request a mic before the mic driver is initialized,
* so we still have to see if the frontend intends to init a mic driver. */
if (!driver && string_is_equal(settings->arrays.microphone_driver, "null"))
{ /* If we're going to load the null driver... */
RARCH_ERR("[Environ]: Cannot initialize microphone interface, configured driver is null\n");
return false;
}
microphone->interface_version = RETRO_MICROPHONE_INTERFACE_VERSION;
microphone->open_mic = microphone_driver_open_mic;
microphone->close_mic = microphone_driver_close_mic;
microphone->get_params = microphone_driver_get_effective_params;
microphone->set_mic_state = microphone_driver_set_mic_state;
microphone->get_mic_state = microphone_driver_get_mic_state;
microphone->read_mic = microphone_driver_read;
}
#else
{
struct retro_microphone_interface* microphone = (struct retro_microphone_interface *)data;
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE.\n");
if (microphone)
microphone->interface_version = 0;
RARCH_ERR("[Environ]: Core requested microphone interface, but this build does not include support\n");
return false;
}
#endif
break;
case RETRO_ENVIRONMENT_GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT:
{
struct retro_hw_render_context_negotiation_interface *iface =
(struct retro_hw_render_context_negotiation_interface*)data;
#ifdef HAVE_VULKAN
if (iface->interface_type == RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN)
iface->interface_version = RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION;
else
#endif
{
iface->interface_version = 0;
}
}
break;
case RETRO_ENVIRONMENT_GET_JIT_CAPABLE:
{
#if defined(HAVE_COCOATOUCH) && TARGET_OS_IOS
*(bool*)data = jb_has_debugger_attached();
#else
*(bool*)data = true;
#endif
}
break;
default:
RARCH_LOG("[Environ]: UNSUPPORTED (#%u).\n", cmd);
return false;
}
return true;
}
bool libretro_get_system_info(
const char *path,
struct retro_system_info *info,
bool *load_no_content)
{
struct retro_system_info dummy_info;
#ifdef HAVE_DYNAMIC
dylib_t lib;
#endif
runloop_state_t *runloop_st = &runloop_state;
if (string_ends_with_size(path,
"builtin", strlen(path), STRLEN_CONST("builtin")))
return false;
dummy_info.library_name = NULL;
dummy_info.library_version = NULL;
dummy_info.valid_extensions = NULL;
dummy_info.need_fullpath = false;
dummy_info.block_extract = false;
#ifdef HAVE_DYNAMIC
if (!(lib = libretro_get_system_info_lib(
path, &dummy_info, load_no_content)))
{
RARCH_ERR("%s: \"%s\"\n",
msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE),
path);
RARCH_ERR("Error(s): %s\n", dylib_error());
return false;
}
#else
if (load_no_content)
{
runloop_st->load_no_content_hook = load_no_content;
/* load_no_content gets set in this callback. */
retro_set_environment(runloop_environ_cb_get_system_info);
/* It's possible that we just set get_system_info callback
* to the currently running core.
*
* Make sure we reset it to the actual environment callback.
* Ignore any environment callbacks here in case we're running
* on the non-current core. */
runloop_st->flags |= RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB;
retro_set_environment(runloop_environment_cb);
runloop_st->flags &= ~RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB;
}
retro_get_system_info(&dummy_info);
#endif
memcpy(info, &dummy_info, sizeof(*info));
runloop_st->current_library_name[0] = '\0';
runloop_st->current_library_version[0] = '\0';
runloop_st->current_valid_extensions[0] = '\0';
if (!string_is_empty(dummy_info.library_name))
strlcpy(runloop_st->current_library_name,
dummy_info.library_name,
sizeof(runloop_st->current_library_name));
if (!string_is_empty(dummy_info.library_version))
strlcpy(runloop_st->current_library_version,
dummy_info.library_version,
sizeof(runloop_st->current_library_version));
if (dummy_info.valid_extensions)
strlcpy(runloop_st->current_valid_extensions,
dummy_info.valid_extensions,
sizeof(runloop_st->current_valid_extensions));
info->library_name = runloop_st->current_library_name;
info->library_version = runloop_st->current_library_version;
info->valid_extensions = runloop_st->current_valid_extensions;
#ifdef HAVE_DYNAMIC
dylib_close(lib);
#endif
return true;
}
bool runloop_init_libretro_symbols(
void *data,
enum rarch_core_type type,
struct retro_core_t *current_core,
const char *lib_path,
void *_lib_handle_p)
{
#ifdef HAVE_DYNAMIC
/* the library handle for use with the SYMBOL macro */
dylib_t lib_handle_local;
runloop_state_t *runloop_st = (runloop_state_t*)data;
#endif
switch (type)
{
case CORE_TYPE_PLAIN:
{
#ifdef HAVE_DYNAMIC
#ifdef HAVE_RUNAHEAD
dylib_t *lib_handle_p = (dylib_t*)_lib_handle_p;
if (!lib_path || !lib_handle_p)
#endif
{
const char *path = path_get(RARCH_PATH_CORE);
if (string_is_empty(path))
{
RARCH_ERR("[Core]: Frontend is built for dynamic libretro cores, but "
"path is not set. Cannot continue.\n");
retroarch_fail(1, "init_libretro_symbols()");
}
RARCH_LOG("[Core]: Loading dynamic libretro core from: \"%s\"\n",
path);
if (!(runloop_st->lib_handle = load_dynamic_core(
path,
path_get_ptr(RARCH_PATH_CORE),
path_get_realsize(RARCH_PATH_CORE)
)))
{
const char *failed_open_str =
msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE);
RARCH_ERR("%s: \"%s\"\nError(s): %s\n", failed_open_str,
path, dylib_error());
runloop_msg_queue_push(failed_open_str,
1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
return false;
}
lib_handle_local = runloop_st->lib_handle;
}
#ifdef HAVE_RUNAHEAD
else
{
/* for a secondary core, we already have a
* primary library loaded, so we can skip
* some checks and just load the library */
lib_handle_local = dylib_load(lib_path);
if (!lib_handle_local)
return false;
*lib_handle_p = lib_handle_local;
}
#endif
#endif
CORE_SYMBOLS(SYMBOL);
}
break;
case CORE_TYPE_DUMMY:
CORE_SYMBOLS(SYMBOL_DUMMY);
break;
case CORE_TYPE_FFMPEG:
#ifdef HAVE_FFMPEG
CORE_SYMBOLS(SYMBOL_FFMPEG);
#endif
break;
case CORE_TYPE_MPV:
#ifdef HAVE_MPV
CORE_SYMBOLS(SYMBOL_MPV);
#endif
break;
case CORE_TYPE_IMAGEVIEWER:
#ifdef HAVE_IMAGEVIEWER
CORE_SYMBOLS(SYMBOL_IMAGEVIEWER);
#endif
break;
case CORE_TYPE_NETRETROPAD:
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
CORE_SYMBOLS(SYMBOL_NETRETROPAD);
#endif
break;
case CORE_TYPE_VIDEO_PROCESSOR:
#if defined(HAVE_VIDEOPROCESSOR)
CORE_SYMBOLS(SYMBOL_VIDEOPROCESSOR);
#endif
break;
}
return true;
}
uint32_t runloop_get_flags(void)
{
runloop_state_t *runloop_st = &runloop_state;
return runloop_st->flags;
}
void runloop_system_info_free(void)
{
runloop_state_t *runloop_st = &runloop_state;
rarch_system_info_t *sys_info = &runloop_st->system;
if (sys_info->subsystem.data)
free(sys_info->subsystem.data);
if (sys_info->ports.data)
free(sys_info->ports.data);
if (sys_info->mmaps.descriptors)
free((void *)sys_info->mmaps.descriptors);
sys_info->subsystem.data = NULL;
sys_info->subsystem.size = 0;
sys_info->ports.data = NULL;
sys_info->ports.size = 0;
sys_info->mmaps.descriptors = NULL;
sys_info->mmaps.num_descriptors = 0;
sys_info->info.library_name = NULL;
sys_info->info.library_version = NULL;
sys_info->info.valid_extensions = NULL;
sys_info->info.need_fullpath = false;
sys_info->info.block_extract = false;
runloop_st->key_event = NULL;
runloop_st->frontend_key_event = NULL;
memset(&runloop_st->system, 0, sizeof(rarch_system_info_t));
}
static void runloop_frame_time_free(runloop_state_t *runloop_st)
{
memset(&runloop_st->frame_time, 0,
sizeof(struct retro_frame_time_callback));
runloop_st->frame_time_last = 0;
runloop_st->max_frames = 0;
}
static void runloop_audio_buffer_status_free(runloop_state_t *runloop_st)
{
memset(&runloop_st->audio_buffer_status, 0,
sizeof(struct retro_audio_buffer_status_callback));
runloop_st->audio_latency = 0;
}
static void runloop_fastmotion_override_free(runloop_state_t *runloop_st)
{
video_driver_state_t
*video_st = video_state_get_ptr();
settings_t *settings = config_get_ptr();
float fastforward_ratio = settings->floats.fastforward_ratio;
bool reset_frame_limit = runloop_st->fastmotion_override.current.fastforward &&
(runloop_st->fastmotion_override.current.ratio >= 0.0f) &&
(runloop_st->fastmotion_override.current.ratio != fastforward_ratio);
runloop_st->fastmotion_override.current.ratio = 0.0f;
runloop_st->fastmotion_override.current.fastforward = false;
runloop_st->fastmotion_override.current.notification = false;
runloop_st->fastmotion_override.current.inhibit_toggle = false;
runloop_st->fastmotion_override.next.ratio = 0.0f;
runloop_st->fastmotion_override.next.fastforward = false;
runloop_st->fastmotion_override.next.notification = false;
runloop_st->fastmotion_override.next.inhibit_toggle = false;
runloop_st->fastmotion_override.pending = false;
if (reset_frame_limit)
runloop_set_frame_limit(&video_st->av_info, fastforward_ratio);
}
void runloop_state_free(runloop_state_t *runloop_st)
{
runloop_frame_time_free(runloop_st);
runloop_audio_buffer_status_free(runloop_st);
input_game_focus_free();
runloop_fastmotion_override_free(runloop_st);
/* Only a single core options callback is used at present */
runloop_st->core_options_callback.update_display = NULL;
runloop_st->video_swap_interval_auto = 1;
}
/**
* uninit_libretro_symbols:
*
* Frees libretro core.
*
* Frees all core options, associated state, and
* unbinds all libretro callback symbols.
**/
static void uninit_libretro_symbols(
struct retro_core_t *current_core)
{
runloop_state_t *runloop_st = &runloop_state;
input_driver_state_t *input_st = input_state_get_ptr();
audio_driver_state_t *audio_st = audio_state_get_ptr();
camera_driver_state_t *camera_st = camera_state_get_ptr();
location_driver_state_t *location_st = location_state_get_ptr();
#ifdef HAVE_DYNAMIC
if (runloop_st->lib_handle)
dylib_close(runloop_st->lib_handle);
runloop_st->lib_handle = NULL;
#endif
memset(current_core, 0, sizeof(struct retro_core_t));
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_SET_SHARED_CONTEXT;
if (runloop_st->core_options)
{
runloop_deinit_core_options(
runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_st->core_options);
runloop_st->flags &=
~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
runloop_st->core_options = NULL;
}
runloop_system_info_free();
audio_st->callback.callback = NULL;
audio_st->callback.set_state = NULL;
runloop_state_free(runloop_st);
camera_st->active = false;
location_st->active = false;
/* Core has finished utilising the input driver;
* reset 'analog input requested' flags */
memset(&input_st->analog_requested, 0,
sizeof(input_st->analog_requested));
/* Performance counters no longer valid. */
runloop_st->perf_ptr_libretro = 0;
memset(runloop_st->perf_counters_libretro, 0,
sizeof(runloop_st->perf_counters_libretro));
}
static retro_time_t runloop_core_runtime_tick(
runloop_state_t *runloop_st,
float slowmotion_ratio,
retro_time_t current_time)
{
video_driver_state_t *video_st = video_state_get_ptr();
retro_time_t frame_time =
(1.0 / video_st->av_info.timing.fps) * 1000000;
bool runloop_slowmotion = runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION;
bool runloop_fastmotion = runloop_st->flags & RUNLOOP_FLAG_FASTMOTION;
/* Account for slow motion */
if (runloop_slowmotion)
return (retro_time_t)((double)frame_time * slowmotion_ratio);
/* Account for fast forward */
if (runloop_fastmotion)
{
/* Doing it this way means we miss the first frame after
* turning fast forward on, but it saves the overhead of
* having to do:
* retro_time_t current_usec = cpu_features_get_time_usec();
* core_runtime_last = current_usec;
* every frame when fast forward is off. */
retro_time_t current_usec = current_time;
retro_time_t potential_frame_time = current_usec -
runloop_st->core_runtime_last;
runloop_st->core_runtime_last = current_usec;
if (potential_frame_time < frame_time)
return potential_frame_time;
}
return frame_time;
}
static bool core_unload_game(void)
{
runloop_state_t *runloop_st = &runloop_state;
video_driver_state_t *video_st = video_state_get_ptr();
video_driver_free_hw_context();
video_st->frame_cache_data = NULL;
if ((runloop_st->current_core.flags & RETRO_CORE_FLAG_GAME_LOADED))
{
RARCH_LOG("[Core]: Unloading game..\n");
runloop_st->current_core.retro_unload_game();
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
runloop_st->current_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
}
audio_driver_stop();
#ifdef HAVE_MICROPHONE
microphone_driver_stop();
#endif
return true;
}
static void runloop_apply_fastmotion_override(runloop_state_t *runloop_st, settings_t *settings)
{
float fastforward_ratio_current;
video_driver_state_t *video_st = video_state_get_ptr();
bool frame_time_counter_reset_after_fastforwarding = settings ?
settings->bools.frame_time_counter_reset_after_fastforwarding : false;
float fastforward_ratio_default = settings ?
settings->floats.fastforward_ratio : 0.0f;
float fastforward_ratio_last =
(runloop_st->fastmotion_override.current.fastforward &&
(runloop_st->fastmotion_override.current.ratio >= 0.0f)) ?
runloop_st->fastmotion_override.current.ratio :
fastforward_ratio_default;
#if defined(HAVE_GFX_WIDGETS)
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
#endif
memcpy(&runloop_st->fastmotion_override.current,
&runloop_st->fastmotion_override.next,
sizeof(runloop_st->fastmotion_override.current));
/* Check if 'fastmotion' state has changed */
if (((runloop_st->flags & RUNLOOP_FLAG_FASTMOTION) > 0) !=
runloop_st->fastmotion_override.current.fastforward)
{
input_driver_state_t *input_st = input_state_get_ptr();
if (runloop_st->fastmotion_override.current.fastforward)
runloop_st->flags |= RUNLOOP_FLAG_FASTMOTION;
else
runloop_st->flags &= ~RUNLOOP_FLAG_FASTMOTION;
if (input_st)
{
if (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
input_st->flags |= INP_FLAG_NONBLOCKING;
else
input_st->flags &= ~INP_FLAG_NONBLOCKING;
}
if (!(runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
runloop_st->fastforward_after_frames = 1;
driver_set_nonblock_state();
/* Reset frame time counter when toggling
* fast-forward off, if required */
if ( !(runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
&& frame_time_counter_reset_after_fastforwarding)
video_st->frame_time_count = 0;
/* Ensure fast forward widget is disabled when
* toggling fast-forward off
* (required if RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE
* is called during core de-initialisation) */
#if defined(HAVE_GFX_WIDGETS)
if ( p_dispwidget->active
&& !(runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
video_st->flags &= ~VIDEO_FLAG_WIDGETS_FAST_FORWARD;
#endif
}
/* Update frame limit, if required */
fastforward_ratio_current = (runloop_st->fastmotion_override.current.fastforward &&
(runloop_st->fastmotion_override.current.ratio >= 0.0f)) ?
runloop_st->fastmotion_override.current.ratio :
fastforward_ratio_default;
if (fastforward_ratio_current != fastforward_ratio_last)
runloop_set_frame_limit(&video_st->av_info,
fastforward_ratio_current);
}
void runloop_event_deinit_core(void)
{
video_driver_state_t
*video_st = video_state_get_ptr();
runloop_state_t *runloop_st = &runloop_state;
settings_t *settings = config_get_ptr();
core_unload_game();
video_st->frame_cache_data = NULL;
if (runloop_st->current_core.flags & RETRO_CORE_FLAG_INITED)
{
RARCH_LOG("[Core]: Unloading core..\n");
runloop_st->current_core.retro_deinit();
}
/* retro_deinit() may call
* RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE
* (i.e. to ensure that fastforwarding is
* disabled on core close)
* > Check for any pending updates */
if (runloop_st->fastmotion_override.pending)
{
runloop_apply_fastmotion_override(runloop_st,
settings);
runloop_st->fastmotion_override.pending = false;
}
if ( (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CORE_ACTIVE)
|| (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE)
|| (runloop_st->flags & RUNLOOP_FLAG_REMAPS_GAME_ACTIVE)
|| !string_is_empty(runloop_st->name.remapfile)
)
{
input_remapping_deinit(settings->bools.remap_save_on_exit);
input_remapping_set_defaults(true);
}
else
input_remapping_restore_global_config(true);
RARCH_LOG("[Core]: Unloading core symbols..\n");
uninit_libretro_symbols(&runloop_st->current_core);
runloop_st->current_core.flags &= ~RETRO_CORE_FLAG_SYMBOLS_INITED;
/* Restore original refresh rate, if it has been changed
* automatically in SET_SYSTEM_AV_INFO */
if (video_st->video_refresh_rate_original)
{
/* Set the av_info fps also to the original refresh rate */
/* to avoid re-initialization problems */
struct retro_system_av_info *av_info = &video_st->av_info;
av_info->timing.fps = video_st->video_refresh_rate_original;
video_display_server_restore_refresh_rate();
}
/* Recalibrate frame delay target */
if (settings->bools.video_frame_delay_auto)
video_st->frame_delay_target = 0;
driver_uninit(DRIVERS_CMD_ALL, 0);
#ifdef HAVE_CONFIGFILE
if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE)
{
/* Reload the original config */
config_unload_override();
}
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
runloop_st->runtime_shader_preset_path[0] = '\0';
#endif
}
static bool runloop_path_init_subsystem(runloop_state_t *runloop_st)
{
unsigned i, j;
const struct retro_subsystem_info *info = NULL;
rarch_system_info_t *system = &runloop_st->system;
bool subsystem_path_empty = path_is_empty(RARCH_PATH_SUBSYSTEM);
const char *savefile_dir = runloop_st->savefile_dir;
if (!system || subsystem_path_empty)
return false;
/* For subsystems, we know exactly which RAM types are supported. */
/* We'll handle this error gracefully later. */
if ((info = libretro_find_subsystem_info(
system->subsystem.data,
system->subsystem.size,
path_get(RARCH_PATH_SUBSYSTEM))))
{
unsigned num_content = MIN(info->num_roms,
subsystem_path_empty ?
0 : (unsigned)runloop_st->subsystem_fullpaths->size);
for (i = 0; i < num_content; i++)
{
for (j = 0; j < info->roms[i].num_memory; j++)
{
char ext[32];
union string_list_elem_attr attr;
char savename[PATH_MAX_LENGTH];
char path[PATH_MAX_LENGTH];
size_t _len = 0;
const struct retro_subsystem_memory_info *mem =
(const struct retro_subsystem_memory_info*)
&info->roms[i].memory[j];
ext[ _len] = '.';
ext[++_len] = '\0';
strlcpy(ext + _len, mem->extension, sizeof(ext) - _len);
strlcpy(savename,
runloop_st->subsystem_fullpaths->elems[i].data,
sizeof(savename));
path_remove_extension(savename);
if (path_is_directory(savefile_dir))
{
/* Use SRAM dir */
/* Redirect content fullpath to save directory. */
strlcpy(path, savefile_dir, sizeof(path));
fill_pathname_dir(path, savename, ext, sizeof(path));
}
else
fill_pathname(path, savename, ext, sizeof(path));
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
path);
attr.i = mem->type;
string_list_append((struct string_list*)savefile_ptr_get(),
path, attr);
}
}
}
/* Let other relevant paths be inferred
from the main SRAM location. */
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL))
{
size_t len = strlcpy(runloop_st->name.savefile,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.savefile));
strlcpy(runloop_st->name.savefile + len,
".srm",
sizeof(runloop_st->name.savefile) - len);
}
if (path_is_directory(runloop_st->name.savefile))
{
fill_pathname_dir(runloop_st->name.savefile,
runloop_st->runtime_content_path_basename,
".srm",
sizeof(runloop_st->name.savefile));
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
runloop_st->name.savefile);
}
return true;
}
static void runloop_path_init_savefile_internal(runloop_state_t *runloop_st)
{
path_deinit_savefile();
path_init_savefile_new();
if (!runloop_path_init_subsystem(runloop_st))
path_init_savefile_rtc(runloop_st->name.savefile);
}
static void runloop_path_init_savefile(runloop_state_t *runloop_st)
{
bool should_sram_be_used =
(runloop_st->flags & RUNLOOP_FLAG_USE_SRAM)
&& !(runloop_st->flags & RUNLOOP_FLAG_IS_SRAM_SAVE_DISABLED);
if (should_sram_be_used)
runloop_st->flags |= RUNLOOP_FLAG_USE_SRAM;
else
runloop_st->flags &= ~RUNLOOP_FLAG_USE_SRAM;
if (!(runloop_st->flags & RUNLOOP_FLAG_USE_SRAM))
{
RARCH_LOG("[SRAM]: %s\n",
msg_hash_to_str(MSG_SRAM_WILL_NOT_BE_SAVED));
return;
}
command_event(CMD_EVENT_AUTOSAVE_INIT, NULL);
}
static bool event_init_content(
runloop_state_t *runloop_st,
settings_t *settings,
input_driver_state_t *input_st)
{
#ifdef HAVE_CHEEVOS
bool cheevos_enable =
settings->bools.cheevos_enable;
bool cheevos_hardcore_mode_enable =
settings->bools.cheevos_hardcore_mode_enable;
#endif
const enum rarch_core_type current_core_type = runloop_st->current_core_type;
uint8_t flags = content_get_flags();
if (current_core_type == CORE_TYPE_PLAIN)
runloop_st->flags |= RUNLOOP_FLAG_USE_SRAM;
else
runloop_st->flags &= ~RUNLOOP_FLAG_USE_SRAM;
/* No content to be loaded for dummy core,
* just successfully exit. */
if (current_core_type == CORE_TYPE_DUMMY)
return true;
content_set_subsystem_info();
/* If core is contentless, just initialise SRAM
* interface, otherwise fill all content-related
* paths */
if (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)
runloop_path_init_savefile_internal(runloop_st);
else
runloop_path_fill_names();
if (!content_init())
return false;
command_event_set_savestate_auto_index(settings);
command_event_set_replay_auto_index(settings);
runloop_path_init_savefile(runloop_st);
if (!event_load_save_files(runloop_st->flags &
RUNLOOP_FLAG_IS_SRAM_LOAD_DISABLED))
RARCH_LOG("[SRAM]: %s\n",
msg_hash_to_str(MSG_SKIPPING_SRAM_LOAD));
/*
Since the operations are asynchronous we can't
guarantee users will not use auto_load_state to cheat on
achievements so we forbid auto_load_state from happening
if cheevos_enable and cheevos_hardcode_mode_enable
are true.
*/
#ifdef HAVE_CHEEVOS
if (!cheevos_enable || !cheevos_hardcore_mode_enable)
#endif
{
#ifdef HAVE_BSV_MOVIE
/* ignore entry state if we're doing bsv playback (we do want it
for bsv recording though) */
if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK))
#endif
{
if (runloop_st->entry_state_slot && !command_event_load_entry_state(settings))
{
/* loading the state failed, reset entry slot */
runloop_st->entry_state_slot = 0;
}
}
#ifdef HAVE_BSV_MOVIE
/* ignore autoload state if we're doing bsv playback or recording */
if (!(input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_START_RECORDING | BSV_FLAG_MOVIE_START_PLAYBACK)))
#endif
{
if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load)
command_event_load_auto_state();
}
}
#ifdef HAVE_BSV_MOVIE
movie_stop(input_st);
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_RECORDING)
{
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
#ifndef HAVE_THREADS
/* Hack: the regular scheduler doesn't do the right thing here at
least in emscripten builds. I would expect that the check in
task_movie.c:343 should defer recording until the movie task
is done, but maybe that task isn't enqueued again yet when the
movie-record task is checked? Or the finder call in
content_load_state_in_progress is not correct? Either way,
the load happens after the recording starts rather than the
right way around.
*/
task_queue_wait(NULL,NULL);
#endif
movie_start_record(input_st, input_st->bsv_movie_state.movie_start_path);
}
else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK)
{
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
movie_start_playback(input_st, input_st->bsv_movie_state.movie_start_path);
}
#endif
command_event(CMD_EVENT_NETPLAY_INIT, NULL);
return true;
}
static void runloop_runtime_log_init(runloop_state_t *runloop_st)
{
const char *content_path = path_get(RARCH_PATH_CONTENT);
const char *core_path = path_get(RARCH_PATH_CORE);
runloop_st->core_runtime_last = cpu_features_get_time_usec();
runloop_st->core_runtime_usec = 0;
/* Have to cache content and core path here, otherwise
* logging fails if new content is loaded without
* closing existing content
* i.e. RARCH_PATH_CONTENT and RARCH_PATH_CORE get
* updated when the new content is loaded, which
* happens *before* command_event_runtime_log_deinit
* -> using RARCH_PATH_CONTENT and RARCH_PATH_CORE
* directly in command_event_runtime_log_deinit
* can therefore lead to the runtime of the currently
* loaded content getting written to the *new*
* content's log file... */
memset(runloop_st->runtime_content_path,
0, sizeof(runloop_st->runtime_content_path));
memset(runloop_st->runtime_core_path,
0, sizeof(runloop_st->runtime_core_path));
if (!string_is_empty(content_path))
strlcpy(runloop_st->runtime_content_path,
content_path,
sizeof(runloop_st->runtime_content_path));
if (!string_is_empty(core_path))
strlcpy(runloop_st->runtime_core_path,
core_path,
sizeof(runloop_st->runtime_core_path));
}
void runloop_set_frame_limit(
const struct retro_system_av_info *av_info,
float fastforward_ratio)
{
runloop_state_t *runloop_st = &runloop_state;
if (fastforward_ratio < 1.0f)
runloop_st->frame_limit_minimum_time = 0.0f;
else
runloop_st->frame_limit_minimum_time = (retro_time_t)
roundf(1000000.0f /
(av_info->timing.fps * fastforward_ratio));
}
float runloop_get_fastforward_ratio(
settings_t *settings,
struct retro_fastforwarding_override *fastmotion_override)
{
if ( fastmotion_override->fastforward
&& (fastmotion_override->ratio >= 0.0f))
return fastmotion_override->ratio;
return settings->floats.fastforward_ratio;
}
void runloop_set_video_swap_interval(
bool vrr_runloop_enable,
bool crt_switching_active,
unsigned swap_interval_config,
float audio_max_timing_skew,
float video_refresh_rate,
double input_fps)
{
runloop_state_t *runloop_st = &runloop_state;
float core_hz = input_fps;
float timing_hz = crt_switching_active ?
input_fps : video_refresh_rate;
float swap_ratio;
unsigned swap_integer;
float timing_skew;
/* If automatic swap interval selection is
* disabled, just record user-set value */
if (swap_interval_config != 0)
{
runloop_st->video_swap_interval_auto =
swap_interval_config;
return;
}
/* > If VRR is enabled, swap interval is irrelevant,
* just set to 1
* > If core fps is higher than display refresh rate,
* set swap interval to 1
* > If core fps or display refresh rate are zero,
* set swap interval to 1 */
if ( (vrr_runloop_enable)
|| (core_hz > timing_hz)
|| (core_hz <= 0.0f)
|| (timing_hz <= 0.0f))
{
runloop_st->video_swap_interval_auto = 1;
return;
}
/* Check whether display refresh rate is an integer
* multiple of core fps (within timing skew tolerance) */
swap_ratio = timing_hz / core_hz;
swap_integer = (unsigned)(swap_ratio + 0.5f);
/* > Sanity check: swap interval must be in the
* range [1,4] - if we are outside this, then
* bail... */
if ((swap_integer < 1) || (swap_integer > 4))
{
runloop_st->video_swap_interval_auto = 1;
return;
}
timing_skew = fabs(1.0f - core_hz / (timing_hz / (float)swap_integer));
runloop_st->video_swap_interval_auto =
(timing_skew <= audio_max_timing_skew) ?
swap_integer : 1;
}
unsigned runloop_get_video_swap_interval(
unsigned swap_interval_config)
{
runloop_state_t *runloop_st = &runloop_state;
return (swap_interval_config == 0) ?
runloop_st->video_swap_interval_auto :
swap_interval_config;
}
/*
Returns rotation requested by the core regardless of if it has been
applied with the final video rotation
*/
unsigned int retroarch_get_core_requested_rotation(void)
{
return runloop_state.system.core_requested_rotation;
}
/*
Returns final rotation including both user chosen video rotation
and core requested rotation if allowed by video_allow_rotate
*/
unsigned int retroarch_get_rotation(void)
{
settings_t *settings = config_get_ptr();
return settings->uints.video_rotation + runloop_state.system.rotation;
}
static void retro_run_null(void) { } /* Stub function callback impl. */
static bool core_verify_api_version(runloop_state_t *runloop_st)
{
unsigned api_version = runloop_st->current_core.retro_api_version();
if (api_version != RETRO_API_VERSION)
{
RARCH_WARN("[Core]: %s\n", msg_hash_to_str(MSG_LIBRETRO_ABI_BREAK));
return false;
}
RARCH_LOG("[Core]: %s: %u, %s: %u\n",
msg_hash_to_str(MSG_VERSION_OF_LIBRETRO_API),
api_version,
msg_hash_to_str(MSG_COMPILED_AGAINST_API),
RETRO_API_VERSION
);
return true;
}
static int16_t core_input_state_poll_late(unsigned port,
unsigned device, unsigned idx, unsigned id)
{
runloop_state_t *runloop_st = &runloop_state;
if (!(runloop_st->current_core.flags & RETRO_CORE_FLAG_INPUT_POLLED))
input_driver_poll();
runloop_st->current_core.flags |= RETRO_CORE_FLAG_INPUT_POLLED;
return input_driver_state_wrapper(port, device, idx, id);
}
static void core_input_state_poll_maybe(void)
{
runloop_state_t *runloop_st = &runloop_state;
const enum poll_type_override_t
core_poll_type_override = runloop_st->core_poll_type_override;
unsigned new_poll_type = (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
? (core_poll_type_override - 1)
: runloop_st->current_core.poll_type;
if (new_poll_type == POLL_TYPE_NORMAL)
input_driver_poll();
}
static retro_input_state_t core_input_state_poll_return_cb(void)
{
runloop_state_t *runloop_st = &runloop_state;
const enum poll_type_override_t
core_poll_type_override = runloop_st->core_poll_type_override;
unsigned new_poll_type = (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
? (core_poll_type_override - 1)
: runloop_st->current_core.poll_type;
if (new_poll_type == POLL_TYPE_LATE)
return core_input_state_poll_late;
return input_driver_state_wrapper;
}
/**
* core_init_libretro_cbs:
* @data : pointer to retro_callbacks object
*
* Initializes libretro callbacks, and binds the libretro callbacks
* to default callback functions.
**/
static void core_init_libretro_cbs(runloop_state_t *runloop_st,
struct retro_callbacks *cbs)
{
retro_input_state_t state_cb = core_input_state_poll_return_cb();
runloop_st->current_core.retro_set_video_refresh(video_driver_frame);
runloop_st->current_core.retro_set_audio_sample(audio_driver_sample);
runloop_st->current_core.retro_set_audio_sample_batch(audio_driver_sample_batch);
runloop_st->current_core.retro_set_input_state(state_cb);
runloop_st->current_core.retro_set_input_poll(core_input_state_poll_maybe);
runloop_st->input_poll_callback_original = core_input_state_poll_maybe;
core_set_default_callbacks(cbs);
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
core_set_netplay_callbacks();
#endif
}
static bool runloop_event_load_core(runloop_state_t *runloop_st,
unsigned poll_type_behavior)
{
video_driver_state_t *video_st = video_state_get_ptr();
runloop_st->current_core.poll_type = poll_type_behavior;
if (!core_verify_api_version(runloop_st))
return false;
core_init_libretro_cbs(runloop_st, &runloop_st->retro_ctx);
runloop_st->current_core.retro_get_system_av_info(&video_st->av_info);
video_st->core_frame_time = 1000000 /
((video_st->av_info.timing.fps > 0.0) ?
video_st->av_info.timing.fps : 60.0);
return true;
}
bool runloop_event_init_core(
settings_t *settings,
void *input_data,
enum rarch_core_type type,
const char *old_savefile_dir,
const char *old_savestate_dir)
{
size_t len;
runloop_state_t *runloop_st = &runloop_state;
input_driver_state_t *input_st = (input_driver_state_t*)input_data;
video_driver_state_t *video_st = video_state_get_ptr();
#ifdef HAVE_CONFIGFILE
bool auto_overrides_enable = settings->bools.auto_overrides_enable;
bool auto_remaps_enable = false;
const char *dir_input_remapping = NULL;
#endif
bool show_set_initial_disk_msg = false;
unsigned poll_type_behavior = 0;
float fastforward_ratio = 0.0f;
rarch_system_info_t *sys_info = &runloop_st->system;
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
{
/* We need this in order for core_info_current_supports_netplay
to work correctly at init_netplay,
called later at event_init_content. */
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
}
#endif
/* Load symbols */
if (!runloop_init_libretro_symbols(runloop_st,
type, &runloop_st->current_core, NULL, NULL))
return false;
#ifdef HAVE_RUNAHEAD
/* remember last core type created, so creating a
* secondary core will know what core type to use. */
runloop_st->last_core_type = type;
#endif
if (!runloop_st->current_core.retro_run)
runloop_st->current_core.retro_run = retro_run_null;
runloop_st->current_core.flags |= RETRO_CORE_FLAG_SYMBOLS_INITED;
runloop_st->current_core.retro_get_system_info(&sys_info->info);
if (!sys_info->info.library_name)
sys_info->info.library_name = msg_hash_to_str(MSG_UNKNOWN);
if (!sys_info->info.library_version)
sys_info->info.library_version = "v0";
len = strlcpy(
video_st->title_buf,
msg_hash_to_str(MSG_PROGRAM),
sizeof(video_st->title_buf));
video_st->title_buf[ len] = ' ';
video_st->title_buf[++len] = '\0';
len += strlcpy(video_st->title_buf + len,
sys_info->info.library_name,
sizeof(video_st->title_buf) - len);
video_st->title_buf[ len] = ' ';
video_st->title_buf[++len] = '\0';
strlcpy(video_st->title_buf + len,
sys_info->info.library_version,
sizeof(video_st->title_buf) - len);
strlcpy(sys_info->valid_extensions,
sys_info->info.valid_extensions ?
sys_info->info.valid_extensions : DEFAULT_EXT,
sizeof(sys_info->valid_extensions));
#ifdef HAVE_CONFIGFILE
if (auto_overrides_enable)
config_load_override(&runloop_st->system);
#endif
/* Cannot access these settings-related parameters
* until *after* config overrides have been loaded */
#ifdef HAVE_CONFIGFILE
auto_remaps_enable = settings->bools.auto_remaps_enable;
dir_input_remapping = settings->paths.directory_input_remapping;
#endif
show_set_initial_disk_msg = settings->bools.notification_show_set_initial_disk;
poll_type_behavior = settings->uints.input_poll_type_behavior;
fastforward_ratio = runloop_get_fastforward_ratio(
settings, &runloop_st->fastmotion_override.current);
#ifdef HAVE_CHEEVOS
/* assume the core supports achievements unless it tells us otherwise */
rcheevos_set_support_cheevos(true);
#endif
/* Load auto-shaders on the next occasion */
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
video_st->flags |= VIDEO_FLAG_SHADER_PRESETS_NEED_RELOAD;
runloop_st->shader_delay_timer.timer_begin = false; /* not initialized */
runloop_st->shader_delay_timer.timer_end = false; /* not expired */
#endif
/* reset video format to libretro's default */
video_st->pix_fmt = RETRO_PIXEL_FORMAT_0RGB1555;
runloop_st->current_core.retro_set_environment(runloop_environment_cb);
/* Load any input remap files
* > Note that we always cache the current global
* input settings when initialising a core
* (regardless of whether remap files are loaded)
* so settings can be restored when the core is
* unloaded - i.e. core remapping options modified
* at runtime should not 'bleed through' into the
* master config file */
input_remapping_cache_global_config();
#ifdef HAVE_CONFIGFILE
if (auto_remaps_enable)
config_load_remap(dir_input_remapping, &runloop_st->system);
#endif
/* Per-core saves: reset redirection paths */
runloop_path_set_redirect(settings, old_savefile_dir, old_savestate_dir);
video_st->frame_cache_data = NULL;
runloop_st->current_core.retro_init();
runloop_st->current_core.flags |= RETRO_CORE_FLAG_INITED;
/* Attempt to set initial disk index */
disk_control_set_initial_index(
&sys_info->disk_control,
path_get(RARCH_PATH_CONTENT),
runloop_st->savefile_dir);
if (!event_init_content(runloop_st, settings, input_st))
{
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_RUNNING;
return false;
}
/* Verify that initial disk index was set correctly */
disk_control_verify_initial_index(&sys_info->disk_control,
show_set_initial_disk_msg);
if (!runloop_event_load_core(runloop_st, poll_type_behavior))
return false;
runloop_set_frame_limit(&video_st->av_info, fastforward_ratio);
runloop_st->frame_limit_last_time = cpu_features_get_time_usec();
runloop_runtime_log_init(runloop_st);
return true;
}
void runloop_pause_checks(void)
{
#ifdef HAVE_PRESENCE
presence_userdata_t userdata;
#endif
runloop_state_t *runloop_st = &runloop_state;
bool is_paused = runloop_st->flags & RUNLOOP_FLAG_PAUSED;
bool is_idle = runloop_st->flags & RUNLOOP_FLAG_IDLE;
#if defined(HAVE_GFX_WIDGETS)
video_driver_state_t *video_st = video_state_get_ptr();
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
bool widgets_active = p_dispwidget->active;
if (widgets_active)
{
if (is_paused)
video_st->flags |= VIDEO_FLAG_WIDGETS_PAUSED;
else
video_st->flags &= ~VIDEO_FLAG_WIDGETS_PAUSED;
}
#endif
if (is_paused)
{
#if defined(HAVE_GFX_WIDGETS)
if (!widgets_active)
#endif
runloop_msg_queue_push(msg_hash_to_str(MSG_PAUSED), 1,
1, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (!is_idle)
video_driver_cached_frame();
#ifdef HAVE_PRESENCE
userdata.status = PRESENCE_GAME_PAUSED;
command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
#endif
#ifndef HAVE_LAKKA_SWITCH
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
#endif
#endif /* #ifndef HAVE_LAKKA_SWITCH */
}
else
{
#ifndef HAVE_LAKKA_SWITCH
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
#endif
#endif /* #ifndef HAVE_LAKKA_SWITCH */
}
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
if (p_dispwidget->ai_service_overlay_state == 1)
gfx_widgets_ai_service_overlay_unload();
#endif
/* Signal/reset paused rewind to take the initial step */
runloop_st->run_frames_and_pause = -1;
}
struct string_list *path_get_subsystem_list(void)
{
runloop_state_t *runloop_st = &runloop_state;
return runloop_st->subsystem_fullpaths;
}
void runloop_path_fill_names(void)
{
runloop_state_t *runloop_st = &runloop_state;
#ifdef HAVE_BSV_MOVIE
input_driver_state_t *input_st = input_state_get_ptr();
#endif
runloop_path_init_savefile_internal(runloop_st);
#ifdef HAVE_BSV_MOVIE
strlcpy(input_st->bsv_movie_state.movie_auto_path,
runloop_st->name.replay,
sizeof(input_st->bsv_movie_state.movie_auto_path));
#endif
if (string_is_empty(runloop_st->runtime_content_path_basename))
return;
if (string_is_empty(runloop_st->name.ups))
{
size_t len = strlcpy(runloop_st->name.ups,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.ups));
strlcpy(runloop_st->name.ups + len,
".ups",
sizeof(runloop_st->name.ups) - len);
}
if (string_is_empty(runloop_st->name.bps))
{
size_t len = strlcpy(runloop_st->name.bps,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.bps));
strlcpy(runloop_st->name.bps + len,
".bps",
sizeof(runloop_st->name.bps) - len);
}
if (string_is_empty(runloop_st->name.ips))
{
size_t len = strlcpy(runloop_st->name.ips,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.ips));
strlcpy(runloop_st->name.ips + len,
".ips",
sizeof(runloop_st->name.ips) - len);
}
}
/* Creates folder and core options stub file for subsequent runs */
bool core_options_create_override(bool game_specific)
{
char options_path[PATH_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
config_file_t *conf = NULL;
options_path[0] = '\0';
if (game_specific)
{
/* Get options file path (game-specific) */
if (!validate_game_options(
runloop_st->system.info.library_name,
options_path,
sizeof(options_path), true))
goto error;
}
else
{
/* Sanity check - cannot create a folder-specific
* override if a game-specific override is
* already active */
if (runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE)
goto error;
/* Get options file path (folder-specific) */
if (!validate_folder_options(
options_path,
sizeof(options_path), true))
goto error;
}
/* Open config file */
if (!(conf = config_file_new_from_path_to_string(options_path)))
if (!(conf = config_file_new_alloc()))
goto error;
/* Write config file */
core_option_manager_flush(runloop_st->core_options, conf);
if (!config_file_write(conf, options_path, true))
goto error;
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_OPTIONS_FILE_CREATED_SUCCESSFULLY),
1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
path_set(RARCH_PATH_CORE_OPTIONS, options_path);
if (game_specific)
runloop_st->flags |= RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
else
runloop_st->flags &= ~RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
if (!game_specific)
runloop_st->flags |= RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
else
runloop_st->flags &= ~RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
config_file_free(conf);
return true;
error:
runloop_msg_queue_push(
msg_hash_to_str(MSG_ERROR_SAVING_CORE_OPTIONS_FILE),
1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (conf)
config_file_free(conf);
return false;
}
bool core_options_remove_override(bool game_specific)
{
char new_options_path[PATH_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
settings_t *settings = config_get_ptr();
core_option_manager_t *coreopts = runloop_st->core_options;
bool per_core_options = !settings->bools.global_core_options;
const char *path_core_options = settings->paths.path_core_options;
const char *current_options_path = NULL;
config_file_t *conf = NULL;
bool folder_options_active = false;
new_options_path[0] = '\0';
/* Sanity check 1 - if there are no core options
* or no overrides are active, there is nothing to do */
if ( !coreopts
|| ( (!(runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE))
&& (!(runloop_st->flags & RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE))
))
return true;
/* Sanity check 2 - can only remove an override
* if the specified type is currently active */
if ( game_specific
&& !(runloop_st->flags & RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE)
)
goto error;
/* Get current options file path */
current_options_path = path_get(RARCH_PATH_CORE_OPTIONS);
if (string_is_empty(current_options_path))
goto error;
/* Remove current options file, if required */
if (path_is_valid(current_options_path))
filestream_delete(current_options_path);
/* Reload any existing 'parent' options file
* > If we have removed a game-specific config,
* check whether a folder-specific config
* exists */
if (game_specific &&
validate_folder_options(
new_options_path,
sizeof(new_options_path), false) &&
path_is_valid(new_options_path))
folder_options_active = true;
/* > If a folder-specific config does not exist,
* or we removed it, check whether we have a
* top-level config file */
if (!folder_options_active)
{
/* Try core-specific options, if enabled */
if (per_core_options)
{
const char *core_name = runloop_st->system.info.library_name;
per_core_options = validate_per_core_options(
new_options_path, sizeof(new_options_path), true,
core_name, core_name);
}
/* ...otherwise use global options */
if (!per_core_options)
{
if (!string_is_empty(path_core_options))
strlcpy(new_options_path,
path_core_options, sizeof(new_options_path));
else if (!path_is_empty(RARCH_PATH_CONFIG))
fill_pathname_resolve_relative(
new_options_path, path_get(RARCH_PATH_CONFIG),
FILE_PATH_CORE_OPTIONS_CONFIG, sizeof(new_options_path));
}
}
if (string_is_empty(new_options_path))
goto error;
/* > If we have a valid file, load it */
if (folder_options_active ||
path_is_valid(new_options_path))
{
size_t i, j;
if (!(conf = config_file_new_from_path_to_string(new_options_path)))
goto error;
for (i = 0; i < coreopts->size; i++)
{
struct config_entry_list *entry = NULL;
struct core_option *option = (struct core_option*)&coreopts->opts[i];
if (!option)
continue;
if (!(entry = config_get_entry(conf, option->key)))
continue;
if (string_is_empty(entry->value))
continue;
/* Set current config value from file entry */
for (j = 0; j < option->vals->size; j++)
{
if (string_is_equal(option->vals->elems[j].data, entry->value))
{
option->index = j;
break;
}
}
}
coreopts->updated = true;
#ifdef HAVE_CHEEVOS
rcheevos_validate_config_settings();
#endif
}
/* Update runloop status */
if (folder_options_active)
{
path_set(RARCH_PATH_CORE_OPTIONS, new_options_path);
runloop_st->flags &= ~RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
}
else
{
path_clear(RARCH_PATH_CORE_OPTIONS);
runloop_st->flags &= ~(RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE
| RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE);
/* Update config file path/object stored in
* core option manager struct */
strlcpy(coreopts->conf_path, new_options_path,
sizeof(coreopts->conf_path));
if (conf)
{
config_file_free(coreopts->conf);
coreopts->conf = conf;
conf = NULL;
}
}
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_OPTIONS_FILE_REMOVED_SUCCESSFULLY),
1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (conf)
config_file_free(conf);
return true;
error:
runloop_msg_queue_push(
msg_hash_to_str(MSG_ERROR_REMOVING_CORE_OPTIONS_FILE),
1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (conf)
config_file_free(conf);
return false;
}
void core_options_reset(void)
{
size_t i;
runloop_state_t *runloop_st = &runloop_state;
core_option_manager_t *coreopts = runloop_st->core_options;
/* If there are no core options, there
* is nothing to do */
if (!coreopts || (coreopts->size < 1))
return;
for (i = 0; i < coreopts->size; i++)
coreopts->opts[i].index = coreopts->opts[i].default_index;
coreopts->updated = true;
#ifdef HAVE_CHEEVOS
rcheevos_validate_config_settings();
#endif
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_OPTIONS_RESET),
1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
void core_options_flush(void)
{
size_t _len;
runloop_state_t *runloop_st = &runloop_state;
core_option_manager_t *coreopts = runloop_st->core_options;
const char *path_core_options = path_get(RARCH_PATH_CORE_OPTIONS);
const char *core_options_file = NULL;
bool success = false;
char msg[256];
msg[0] = '\0';
/* If there are no core options, there
* is nothing to do */
if (!coreopts || (coreopts->size < 1))
return;
/* Check whether game/folder-specific options file
* is being used */
if (!string_is_empty(path_core_options))
{
config_file_t *conf_tmp = NULL;
bool path_valid = path_is_valid(path_core_options);
/* Attempt to load existing file */
if (path_valid)
conf_tmp = config_file_new_from_path_to_string(path_core_options);
/* Create new file if required */
if (!conf_tmp)
conf_tmp = config_file_new_alloc();
if (conf_tmp)
{
core_option_manager_flush(runloop_st->core_options, conf_tmp);
success = config_file_write(conf_tmp, path_core_options, true);
config_file_free(conf_tmp);
}
}
else
{
/* We are using the 'default' core options file */
path_core_options = runloop_st->core_options->conf_path;
if (!string_is_empty(path_core_options))
{
core_option_manager_flush(
runloop_st->core_options,
runloop_st->core_options->conf);
/* We must *guarantee* that a file gets written
* to disk if any options differ from the current
* options file contents. Must therefore handle
* the case where the 'default' file does not
* exist (e.g. if it gets deleted manually while
* a core is running) */
if (!path_is_valid(path_core_options))
runloop_st->core_options->conf->modified = true;
success = config_file_write(runloop_st->core_options->conf,
path_core_options, true);
}
}
/* Get options file name for display purposes */
if (!string_is_empty(path_core_options))
core_options_file = path_basename_nocompression(path_core_options);
if (string_is_empty(core_options_file))
core_options_file = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN);
if (success)
{
/* Log result */
_len = strlcpy(msg, msg_hash_to_str(MSG_CORE_OPTIONS_FLUSHED),
sizeof(msg));
RARCH_LOG(
"[Core]: Saved core options to \"%s\".\n",
path_core_options ? path_core_options : "UNKNOWN");
}
else
{
/* Log result */
_len = strlcpy(msg, msg_hash_to_str(MSG_CORE_OPTIONS_FLUSH_FAILED),
sizeof(msg));
RARCH_LOG(
"[Core]: Failed to save core options to \"%s\".\n",
path_core_options ? path_core_options : "UNKNOWN");
}
snprintf(msg + _len, sizeof(msg) - _len, " \"%s\"",
core_options_file);
runloop_msg_queue_push(
msg, 1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
void runloop_msg_queue_push(const char *msg,
unsigned prio, unsigned duration,
bool flush,
char *title,
enum message_queue_icon icon,
enum message_queue_category category)
{
#if defined(HAVE_GFX_WIDGETS)
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
bool widgets_active = p_dispwidget->active;
#endif
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = config_get_ptr();
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
access_state_t *access_st = access_state_get_ptr();
#endif
runloop_state_t *runloop_st = &runloop_state;
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
access_st->enabled))
accessibility_speak_priority(
accessibility_enable,
accessibility_narrator_speech_speed,
(char*) msg, 0);
#endif
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
gfx_widgets_msg_queue_push(
NULL,
msg,
roundf((float)duration / 60.0f * 1000.0f),
title,
icon,
category,
prio,
flush,
#ifdef HAVE_MENU
menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE
#else
false
#endif
);
duration = duration * 60 / 1000;
}
else
#endif
{
if (flush)
msg_queue_clear(&runloop_st->msg_queue);
msg_queue_push(&runloop_st->msg_queue, msg,
prio, duration,
title, icon, category);
runloop_st->msg_queue_size = msg_queue_size(
&runloop_st->msg_queue);
}
ui_companion_driver_msg_queue_push(
msg, prio, duration, flush);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
}
#ifdef HAVE_MENU
/* Display the libretro core's framebuffer onscreen. */
static bool display_menu_libretro(
runloop_state_t *runloop_st,
input_driver_state_t *input_st,
float slowmotion_ratio,
bool libretro_running,
retro_time_t current_time)
{
bool runloop_idle = runloop_st->flags & RUNLOOP_FLAG_IDLE;
video_driver_state_t*video_st = video_state_get_ptr();
if ( video_st->poke
&& video_st->poke->set_texture_enable)
video_st->poke->set_texture_enable(video_st->data, true, false);
if (libretro_running)
{
if (!(input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT))
input_st->flags |= INP_FLAG_BLOCK_LIBRETRO_INPUT;
core_run();
runloop_st->core_runtime_usec +=
runloop_core_runtime_tick(runloop_st, slowmotion_ratio, current_time);
input_st->flags &= ~INP_FLAG_BLOCK_LIBRETRO_INPUT;
return false;
}
if (runloop_idle)
{
#ifdef HAVE_PRESENCE
presence_userdata_t userdata;
userdata.status = PRESENCE_GAME_PAUSED;
command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
#endif
return false;
}
return true;
}
#endif
#define HOTKEY_CHECK(cmd1, cmd2, cond, cond2) \
{ \
static bool old_pressed = false; \
bool pressed = BIT256_GET(current_bits, cmd1); \
if (pressed && !old_pressed) \
if (cond) \
command_event(cmd2, cond2); \
old_pressed = pressed; \
}
#define HOTKEY_CHECK3(cmd1, cmd2, cmd3, cmd4, cmd5, cmd6) \
{ \
static bool old_pressed = false; \
static bool old_pressed2 = false; \
static bool old_pressed3 = false; \
bool pressed = BIT256_GET(current_bits, cmd1); \
bool pressed2 = BIT256_GET(current_bits, cmd3); \
bool pressed3 = BIT256_GET(current_bits, cmd5); \
if (pressed && !old_pressed) \
command_event(cmd2, (void*)(intptr_t)0); \
else if (pressed2 && !old_pressed2) \
command_event(cmd4, (void*)(intptr_t)0); \
else if (pressed3 && !old_pressed3) \
command_event(cmd6, (void*)(intptr_t)0); \
old_pressed = pressed; \
old_pressed2 = pressed2; \
old_pressed3 = pressed3; \
}
static void runloop_pause_toggle(
bool *runloop_paused_hotkey,
bool pause_pressed, bool old_pause_pressed,
bool focused, bool old_focus)
{
runloop_state_t *runloop_st = &runloop_state;
if (focused)
{
if (pause_pressed && !old_pause_pressed)
{
/* Keep track of hotkey triggered pause to
* distinguish it from menu triggered pause */
*runloop_paused_hotkey = !(runloop_st->flags & RUNLOOP_FLAG_PAUSED);
command_event(CMD_EVENT_PAUSE_TOGGLE, NULL);
}
else if (!old_focus)
command_event(CMD_EVENT_UNPAUSE, NULL);
}
else if (old_focus)
command_event(CMD_EVENT_PAUSE, NULL);
}
static enum runloop_state_enum runloop_check_state(
bool error_on_init,
settings_t *settings,
retro_time_t current_time)
{
input_bits_t current_bits;
#ifdef HAVE_MENU
static input_bits_t last_input = {{0}};
#endif
uico_driver_state_t *uico_st = uico_state_get_ptr();
input_driver_state_t *input_st = input_state_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
gfx_display_t *p_disp = disp_get_ptr();
runloop_state_t *runloop_st = &runloop_state;
static bool old_focus = true;
static bool runloop_paused_hotkey = false;
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
bool is_focused = false;
bool is_alive = false;
uint64_t frame_count = 0;
bool focused = true;
bool rarch_is_initialized = runloop_st->flags & RUNLOOP_FLAG_IS_INITED;
bool runloop_paused = runloop_st->flags & RUNLOOP_FLAG_PAUSED;
bool pause_nonactive = settings->bools.pause_nonactive;
unsigned quit_gamepad_combo = settings->uints.input_quit_gamepad_combo;
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
unsigned menu_toggle_gamepad_combo = settings->uints.input_menu_toggle_gamepad_combo;
bool menu_driver_binding_state = menu_st->flags & MENU_ST_FLAG_IS_BINDING;
bool menu_is_alive = menu_st->flags & MENU_ST_FLAG_ALIVE;
bool display_kb = menu_input_dialog_get_display_kb();
#endif
#if defined(HAVE_GFX_WIDGETS)
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
bool widgets_active = p_dispwidget->active;
#endif
#ifdef HAVE_CHEEVOS
bool cheevos_hardcore_active = false;
#endif
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
if (p_dispwidget->ai_service_overlay_state == 3)
{
command_event(CMD_EVENT_PAUSE, NULL);
p_dispwidget->ai_service_overlay_state = 1;
}
#endif
#ifdef HAVE_LIBNX
/* Should be called once per frame */
if (!appletMainLoop())
return RUNLOOP_STATE_QUIT;
#endif
#ifdef _3DS
/* Should be called once per frame */
if (!aptMainLoop())
return RUNLOOP_STATE_QUIT;
#endif
BIT256_CLEAR_ALL_PTR(&current_bits);
input_st->flags &= ~(INP_FLAG_BLOCK_LIBRETRO_INPUT
| INP_FLAG_BLOCK_HOTKEY);
if (input_st->flags & INP_FLAG_KB_MAPPING_BLOCKED)
input_st->flags |= INP_FLAG_BLOCK_HOTKEY;
input_driver_collect_system_input(input_st, settings, &current_bits);
#ifdef HAVE_MENU
last_input = current_bits;
if (
((menu_toggle_gamepad_combo != INPUT_COMBO_NONE) &&
input_driver_button_combo(
menu_toggle_gamepad_combo,
current_time,
&last_input)))
BIT256_SET(current_bits, RARCH_MENU_TOGGLE);
if (menu_st->input_driver_flushing_input > 0)
{
bool input_active = bits_any_set(current_bits.data, ARRAY_SIZE(current_bits.data));
/* Don't count 'enable_hotkey' as active input */
if ( input_active
&& BIT256_GET(current_bits, RARCH_ENABLE_HOTKEY)
&& !BIT256_GET(current_bits, RARCH_MENU_TOGGLE))
input_active = false;
if (!input_active)
menu_st->input_driver_flushing_input--;
if (input_active || (menu_st->input_driver_flushing_input > 0))
{
BIT256_CLEAR_ALL(current_bits);
if (runloop_paused && !runloop_paused_hotkey && settings->bools.menu_pause_libretro)
BIT256_SET(current_bits, RARCH_PAUSE_TOGGLE);
else if (runloop_paused_hotkey)
{
/* Restore pause if pause is triggered with both hotkey and menu,
* and restore cached video frame to continue properly to
* paused state from non-paused menu */
if (settings->bools.menu_pause_libretro)
command_event(CMD_EVENT_PAUSE, NULL);
else
video_driver_cached_frame();
}
}
}
#endif
if (!VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st))
{
const ui_application_t *application = uico_st->drv
? uico_st->drv->application
: NULL;
if (application)
application->process_events();
}
frame_count = video_st->frame_count;
is_alive = video_st->current_video
? video_st->current_video->alive(video_st->data)
: true;
is_focused = VIDEO_HAS_FOCUS(video_st);
#ifdef HAVE_MENU
if (menu_driver_binding_state)
BIT256_CLEAR_ALL(current_bits);
#endif
/* Check fullscreen hotkey */
HOTKEY_CHECK(RARCH_FULLSCREEN_TOGGLE_KEY, CMD_EVENT_FULLSCREEN_TOGGLE, true, NULL);
/* Check mouse grab hotkey */
HOTKEY_CHECK(RARCH_GRAB_MOUSE_TOGGLE, CMD_EVENT_GRAB_MOUSE_TOGGLE, true, NULL);
/* Automatic mouse grab on focus */
if ( settings->bools.input_auto_mouse_grab
&& (is_focused)
&& (is_focused != (((runloop_st->flags & RUNLOOP_FLAG_FOCUSED)) > 0))
&& !(input_st->flags & INP_FLAG_GRAB_MOUSE_STATE))
command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
if (is_focused)
runloop_st->flags |= RUNLOOP_FLAG_FOCUSED;
else
runloop_st->flags &= ~RUNLOOP_FLAG_FOCUSED;
#ifdef HAVE_OVERLAY
if (settings->bools.input_overlay_enable)
{
static char prev_overlay_restore = false;
static unsigned last_width = 0;
static unsigned last_height = 0;
unsigned video_driver_width = video_st->width;
unsigned video_driver_height = video_st->height;
bool check_next_rotation = true;
bool input_overlay_hide_when_gamepad_connected = settings->bools.input_overlay_hide_when_gamepad_connected;
bool input_overlay_auto_rotate = settings->bools.input_overlay_auto_rotate;
/* Check whether overlay should be hidden
* when a gamepad is connected */
if (input_overlay_hide_when_gamepad_connected)
{
static bool last_controller_connected = false;
bool controller_connected = (input_config_get_device_name(0) != NULL);
if (controller_connected != last_controller_connected)
{
if (controller_connected)
input_overlay_deinit();
else
input_overlay_init();
last_controller_connected = controller_connected;
}
}
/* Check next overlay hotkey */
HOTKEY_CHECK(RARCH_OVERLAY_NEXT, CMD_EVENT_OVERLAY_NEXT, true, &check_next_rotation);
/* Ensure overlay is restored after displaying OSK */
if (input_st->flags & INP_FLAG_KB_LINEFEED_ENABLE)
prev_overlay_restore = true;
else if (prev_overlay_restore)
{
input_overlay_init();
prev_overlay_restore = false;
}
/* Check whether video aspect has changed */
if ((video_driver_width != last_width) ||
(video_driver_height != last_height))
{
/* Update scaling/offset factors */
command_event(CMD_EVENT_OVERLAY_SET_SCALE_FACTOR, NULL);
/* Check overlay rotation, if required */
if (input_overlay_auto_rotate)
input_overlay_auto_rotate_(
video_st->width,
video_st->height,
settings->bools.input_overlay_enable,
input_st->overlay_ptr);
last_width = video_driver_width;
last_height = video_driver_height;
}
/* Check OSK hotkey */
HOTKEY_CHECK(RARCH_OSK, CMD_EVENT_OSK_TOGGLE, true, NULL);
}
#endif
/*
* If the Aspect Ratio is FULL then update the aspect ratio to the
* current video driver aspect ratio (The full window)
*
* TODO/FIXME
* Should possibly be refactored to have last width & driver width & height
* only be done once when we are using an overlay OR using aspect ratio
* full
*/
if (settings->uints.video_aspect_ratio_idx == ASPECT_RATIO_FULL)
{
static unsigned last_width = 0;
static unsigned last_height = 0;
unsigned video_driver_width = video_st->width;
unsigned video_driver_height = video_st->height;
/* Check whether video aspect has changed */
if ((video_driver_width != last_width) ||
(video_driver_height != last_height))
{
/* Update set aspect ratio so the full matches the current video width & height */
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
last_width = video_driver_width;
last_height = video_driver_height;
}
}
/* Check quit hotkey */
{
bool trig_quit_key, quit_press_twice;
static bool quit_key = false;
static bool old_quit_key = false;
static bool runloop_exec = false;
quit_key = BIT256_GET(
current_bits, RARCH_QUIT_KEY);
trig_quit_key = quit_key && !old_quit_key;
/* Check for quit gamepad combo */
if (!trig_quit_key &&
((quit_gamepad_combo != INPUT_COMBO_NONE) &&
input_driver_button_combo(
quit_gamepad_combo,
current_time,
&current_bits)))
trig_quit_key = true;
old_quit_key = quit_key;
quit_press_twice = settings->bools.quit_press_twice;
/* Check double press if enabled */
if (trig_quit_key && quit_press_twice)
{
static retro_time_t quit_key_time = 0;
retro_time_t cur_time = current_time;
trig_quit_key = (cur_time - quit_key_time < QUIT_DELAY_USEC);
quit_key_time = cur_time;
if (!trig_quit_key)
{
float target_hz = 0.0;
runloop_environment_cb(
RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE, &target_hz);
runloop_msg_queue_push(msg_hash_to_str(MSG_PRESS_AGAIN_TO_QUIT), 1,
QUIT_DELAY_USEC * target_hz / 1000000,
true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
if (RUNLOOP_TIME_TO_EXIT(trig_quit_key))
{
bool quit_runloop = false;
#ifdef HAVE_SCREENSHOTS
unsigned runloop_max_frames = runloop_st->max_frames;
if ((runloop_max_frames != 0)
&& (frame_count >= runloop_max_frames)
&& (runloop_st->flags & RUNLOOP_FLAG_MAX_FRAMES_SCREENSHOT))
{
const char *screenshot_path = NULL;
bool fullpath = false;
if (string_is_empty(runloop_st->max_frames_screenshot_path))
screenshot_path = path_get(RARCH_PATH_BASENAME);
else
{
fullpath = true;
screenshot_path = runloop_st->max_frames_screenshot_path;
}
RARCH_LOG("Taking a screenshot before exiting...\n");
/* Take a screenshot before we exit. */
if (!take_screenshot(settings->paths.directory_screenshot,
screenshot_path,
false,
video_st->frame_cache_data && (video_st->frame_cache_data == RETRO_HW_FRAME_BUFFER_VALID),
fullpath,
false))
{
RARCH_ERR("Could not take a screenshot before exiting.\n");
}
}
#endif
if (runloop_exec)
runloop_exec = false;
if (runloop_st->flags & RUNLOOP_FLAG_CORE_SHUTDOWN_INITIATED)
{
bool load_dummy_core = false;
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_SHUTDOWN_INITIATED;
/* Check whether dummy core should be loaded
* instead of exiting RetroArch completely
* (aborts shutdown if invoked) */
if (settings->bools.load_dummy_on_core_shutdown)
{
load_dummy_core = true;
runloop_st->flags &= ~RUNLOOP_FLAG_SHUTDOWN_INITIATED;
}
/* Unload current core, and load dummy if
* required */
if (!command_event(CMD_EVENT_UNLOAD_CORE, &load_dummy_core))
{
runloop_st->flags |= RUNLOOP_FLAG_SHUTDOWN_INITIATED;
quit_runloop = true;
}
if (!load_dummy_core)
quit_runloop = true;
}
else
quit_runloop = true;
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_RUNNING;
if (quit_runloop)
{
old_quit_key = quit_key;
return RUNLOOP_STATE_QUIT;
}
}
}
#if defined(HAVE_MENU) || defined(HAVE_GFX_WIDGETS)
gfx_animation_update(
current_time,
settings->bools.menu_timedate_enable,
settings->floats.menu_ticker_speed,
video_st->width,
video_st->height);
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
bool rarch_force_fullscreen = video_st->flags &
VIDEO_FLAG_FORCE_FULLSCREEN;
bool video_is_fullscreen = settings->bools.video_fullscreen ||
rarch_force_fullscreen;
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
gfx_widgets_iterate(
p_disp,
settings,
video_st->width,
video_st->height,
video_is_fullscreen,
settings->paths.directory_assets,
settings->paths.path_font,
VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st));
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
}
#endif
#ifdef HAVE_MENU
if (menu_is_alive)
{
enum menu_action action;
static input_bits_t old_input = {{0}};
static enum menu_action
old_action = MENU_ACTION_CANCEL;
struct menu_state *menu_st = menu_state_get_ptr();
bool focused = false;
input_bits_t trigger_input = current_bits;
unsigned screensaver_timeout = settings->uints.menu_screensaver_timeout;
/* Get current time */
menu_st->current_time_us = current_time;
cbs->poll_cb();
bits_clear_bits(trigger_input.data, old_input.data,
ARRAY_SIZE(trigger_input.data));
action = (enum menu_action)menu_event(
settings,
&current_bits, &trigger_input, display_kb);
#ifdef HAVE_NETWORKING
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
focused = true;
else
#endif
{
if (pause_nonactive)
focused = is_focused && (!(uico_st->flags & UICO_ST_FLAG_IS_ON_FOREGROUND));
else
focused = (!(uico_st->flags & UICO_ST_FLAG_IS_ON_FOREGROUND));
}
if (action == old_action)
{
retro_time_t press_time = current_time;
if (action == MENU_ACTION_NOOP)
menu_st->noop_press_time = press_time - menu_st->noop_start_time;
else
menu_st->action_press_time = press_time - menu_st->action_start_time;
}
else
{
if (action == MENU_ACTION_NOOP)
{
menu_st->noop_start_time = current_time;
menu_st->noop_press_time = 0;
if (menu_st->prev_action == old_action)
menu_st->action_start_time = menu_st->prev_start_time;
else
menu_st->action_start_time = current_time;
}
else
{
if ( menu_st->prev_action == action
&& menu_st->noop_press_time < 200000) /* 250ms */
{
menu_st->action_start_time = menu_st->prev_start_time;
menu_st->action_press_time = current_time - menu_st->action_start_time;
}
else
{
menu_st->prev_start_time = current_time;
menu_st->prev_action = action;
menu_st->action_press_time = 0;
}
}
}
/* Check whether menu screensaver should be enabled */
if ( (screensaver_timeout > 0)
&& (menu_st->flags & MENU_ST_FLAG_SCREENSAVER_SUPPORTED)
&& (!(menu_st->flags & MENU_ST_FLAG_SCREENSAVER_ACTIVE))
&& ((menu_st->current_time_us - menu_st->input_last_time_us)
> ((retro_time_t)screensaver_timeout * 1000000)))
{
menu_st->flags |= MENU_ST_FLAG_SCREENSAVER_ACTIVE;
if (menu_st->driver_ctx->environ_cb)
menu_st->driver_ctx->environ_cb(MENU_ENVIRON_ENABLE_SCREENSAVER,
NULL, menu_st->userdata);
}
/* Iterate the menu driver for one frame. */
/* If the user had requested that the Quick Menu
* be spawned during the previous frame, do this now
* and exit the function to go to the next frame. */
if (menu_st->flags & MENU_ST_FLAG_PENDING_QUICK_MENU)
{
/* We are going to push a new menu; ensure
* that the current one is cached for animation
* purposes */
if (menu_st->driver_ctx && menu_st->driver_ctx->list_cache)
menu_st->driver_ctx->list_cache(menu_st->userdata,
MENU_LIST_PLAIN, MENU_ACTION_NOOP);
p_disp->flags |= GFX_DISP_FLAG_MSG_FORCE;
generic_action_ok_displaylist_push("", NULL,
"", 0, 0, 0, ACTION_OK_DL_CONTENT_SETTINGS);
menu_st->selection_ptr = 0;
menu_st->flags &= ~MENU_ST_FLAG_PENDING_QUICK_MENU;
}
else if (!menu_driver_iterate(
menu_st,
p_disp,
anim_get_ptr(),
settings,
action, current_time))
{
if (error_on_init)
{
content_ctx_info_t content_info = {0};
task_push_start_dummy_core(&content_info);
}
else
retroarch_menu_running_finished(false);
}
if (focused || !(runloop_st->flags & RUNLOOP_FLAG_IDLE))
{
bool runloop_is_inited = runloop_st->flags & RUNLOOP_FLAG_IS_INITED;
#ifdef HAVE_NETWORKING
bool menu_pause_libretro = settings->bools.menu_pause_libretro &&
netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
#else
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
#endif
bool libretro_running = !(runloop_st->flags & RUNLOOP_FLAG_PAUSED)
&& !menu_pause_libretro
&& runloop_is_inited
&& (runloop_st->current_core_type != CORE_TYPE_DUMMY);
if (menu)
{
if (BIT64_GET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER)
!= BIT64_GET(menu->state, MENU_STATE_RENDER_MESSAGEBOX))
BIT64_SET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER);
if (BIT64_GET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER))
p_disp->flags |= GFX_DISP_FLAG_FB_DIRTY;
if (BIT64_GET(menu->state, MENU_STATE_RENDER_MESSAGEBOX)
&& !string_is_empty(menu->menu_state_msg))
{
if (menu->driver_ctx->render_messagebox)
menu->driver_ctx->render_messagebox(
menu->userdata,
menu->menu_state_msg);
if (uico_st->flags & UICO_ST_FLAG_IS_ON_FOREGROUND)
{
if ( uico_st->drv
&& uico_st->drv->render_messagebox)
uico_st->drv->render_messagebox(menu->menu_state_msg);
}
}
if (BIT64_GET(menu->state, MENU_STATE_BLIT))
{
if (menu->driver_ctx->render)
menu->driver_ctx->render(
menu->userdata,
video_st->width,
video_st->height,
runloop_st->flags & RUNLOOP_FLAG_IDLE);
}
if ( (menu_st->flags & MENU_ST_FLAG_ALIVE)
&& !(runloop_st->flags & RUNLOOP_FLAG_IDLE))
if (display_menu_libretro(runloop_st, input_st,
settings->floats.slowmotion_ratio,
libretro_running, current_time))
video_driver_cached_frame();
if (menu->driver_ctx->set_texture)
menu->driver_ctx->set_texture(menu->userdata);
menu->state = 0;
}
if (settings->bools.audio_enable_menu &&
!libretro_running)
audio_driver_menu_sample();
}
old_input = current_bits;
old_action = action;
if (!focused || (runloop_st->flags & RUNLOOP_FLAG_IDLE))
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
else
#endif
#endif
{
if (runloop_st->flags & RUNLOOP_FLAG_IDLE)
{
cbs->poll_cb();
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
}
/* Check Game Focus hotkey */
{
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_TOGGLE;
HOTKEY_CHECK(RARCH_GAME_FOCUS_TOGGLE, CMD_EVENT_GAME_FOCUS_TOGGLE, true, &game_focus_cmd);
}
/* Check UI companion hotkey */
HOTKEY_CHECK(RARCH_UI_COMPANION_TOGGLE, CMD_EVENT_UI_COMPANION_TOGGLE, true, NULL);
/* Check close content hotkey */
HOTKEY_CHECK(RARCH_CLOSE_CONTENT_KEY, CMD_EVENT_CLOSE_CONTENT, true, NULL);
#ifdef HAVE_MENU
/* Check menu hotkey */
{
static bool old_pressed = false;
char *menu_driver = settings->arrays.menu_driver;
bool pressed = BIT256_GET(current_bits, RARCH_MENU_TOGGLE)
&& !string_is_equal(menu_driver, "null");
bool core_type_is_dummy = runloop_st->current_core_type == CORE_TYPE_DUMMY;
if ( (pressed && !old_pressed) ||
core_type_is_dummy)
{
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
{
if (rarch_is_initialized && !core_type_is_dummy)
retroarch_menu_running_finished(false);
}
else
retroarch_menu_running();
}
old_pressed = pressed;
}
#endif
/* Check FPS hotkey */
HOTKEY_CHECK(RARCH_FPS_TOGGLE, CMD_EVENT_FPS_TOGGLE, true, NULL);
/* Check statistics hotkey */
HOTKEY_CHECK(RARCH_STATISTICS_TOGGLE, CMD_EVENT_STATISTICS_TOGGLE, true, NULL);
/* Check netplay host hotkey */
HOTKEY_CHECK(RARCH_NETPLAY_HOST_TOGGLE, CMD_EVENT_NETPLAY_HOST_TOGGLE, true, NULL);
/* Volume stepping + acceleration */
{
static unsigned volume_hotkey_delay = 0;
static unsigned volume_hotkey_delay_active = 0;
unsigned volume_hotkey_delay_default = 6;
bool volume_hotkey_up = BIT256_GET(
current_bits, RARCH_VOLUME_UP);
bool volume_hotkey_down = BIT256_GET(
current_bits, RARCH_VOLUME_DOWN);
if ( (volume_hotkey_up && !volume_hotkey_down) ||
(volume_hotkey_down && !volume_hotkey_up))
{
if (volume_hotkey_delay > 0)
volume_hotkey_delay--;
else
{
if (volume_hotkey_up)
command_event(CMD_EVENT_VOLUME_UP, NULL);
else if (volume_hotkey_down)
command_event(CMD_EVENT_VOLUME_DOWN, NULL);
if (volume_hotkey_delay_active > 0)
volume_hotkey_delay_active--;
volume_hotkey_delay = volume_hotkey_delay_active;
}
}
else
{
volume_hotkey_delay = 0;
volume_hotkey_delay_active = volume_hotkey_delay_default;
}
}
/* Check audio mute hotkey */
HOTKEY_CHECK(RARCH_MUTE, CMD_EVENT_AUDIO_MUTE_TOGGLE, true, NULL);
#ifdef HAVE_SCREENSHOTS
/* Check screenshot hotkey */
HOTKEY_CHECK(RARCH_SCREENSHOT, CMD_EVENT_TAKE_SCREENSHOT, true, NULL);
#endif
#ifdef HAVE_CHEEVOS
if (!cheevos_hardcore_active)
#endif
{
/* Check rewind hotkey */
/* > Must do this before MENU_ITERATE to not lose rewind steps
* while menu is active when menu pause is disabled */
{
#ifdef HAVE_REWIND
char s[128];
bool rewinding = false;
static bool old_rewind_pressed = false;
bool rewind_pressed = BIT256_GET(current_bits, RARCH_REWIND);
unsigned t = 0;
s[0] = '\0';
#ifdef HAVE_MENU
/* Don't allow rewinding while menu is active */
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
rewind_pressed = false;
#endif
/* Prevent rewind hold while paused to rewind only one frame */
if ( runloop_paused
&& rewind_pressed
&& old_rewind_pressed
&& !runloop_st->run_frames_and_pause)
{
cbs->poll_cb();
return RUNLOOP_STATE_PAUSE;
}
rewinding = state_manager_check_rewind(
&runloop_st->rewind_st,
&runloop_st->current_core,
rewind_pressed,
settings->uints.rewind_granularity,
runloop_paused
#ifdef HAVE_MENU
|| ( (menu_st->flags & MENU_ST_FLAG_ALIVE)
&& settings->bools.menu_pause_libretro)
#endif
,
s, sizeof(s), &t);
old_rewind_pressed = rewind_pressed;
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
if (rewinding)
video_st->flags |= VIDEO_FLAG_WIDGETS_REWINDING;
else
video_st->flags &= ~VIDEO_FLAG_WIDGETS_REWINDING;
}
else
#endif
{
if (rewinding)
runloop_msg_queue_push(s, 0, t, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
if (rewinding && runloop_paused
#ifdef HAVE_MENU
&& !(menu_st->flags & MENU_ST_FLAG_ALIVE)
#endif
)
{
cbs->poll_cb();
/* Run a few frames on first press after pausing to
* prevent going forwards for the first frame */
if (runloop_st->run_frames_and_pause == -1)
{
runloop_st->flags &= ~RUNLOOP_FLAG_PAUSED;
runloop_st->run_frames_and_pause = 3;
}
return RUNLOOP_STATE_ITERATE;
}
#endif
}
}
/* Check pause hotkey in menu */
#ifdef HAVE_MENU
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
{
static bool old_pause_pressed = false;
bool pause_pressed = BIT256_GET(current_bits, RARCH_PAUSE_TOGGLE);
/* Decide pause hotkey */
runloop_pause_toggle(&runloop_paused_hotkey,
pause_pressed, old_pause_pressed,
focused, old_focus);
old_focus = focused;
old_pause_pressed = pause_pressed;
}
#endif
#ifdef HAVE_MENU
/* Stop checking the rest of the hotkeys if menu is alive */
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
{
float fastforward_ratio = runloop_get_fastforward_ratio(settings,
&runloop_st->fastmotion_override.current);
if (!settings->bools.menu_throttle_framerate && !fastforward_ratio)
return RUNLOOP_STATE_MENU_ITERATE;
return RUNLOOP_STATE_END;
}
#endif
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
#endif
if (pause_nonactive)
focused = is_focused;
/* Check pause hotkey */
{
static bool old_frameadvance = false;
static bool old_pause_pressed = false;
static bool pauseframeadvance = false;
bool frameadvance_pressed = false;
bool frameadvance_trigger = false;
bool pause_pressed = BIT256_GET(current_bits, RARCH_PAUSE_TOGGLE);
/* Reset frameadvance pause when triggering pause */
if (pause_pressed)
pauseframeadvance = false;
/* Allow unpausing with Start */
if (runloop_paused && settings->bools.pause_on_disconnect)
pause_pressed |= BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_START);
#ifdef HAVE_CHEEVOS
/* Make sure not to evaluate this before calling menu_driver_iterate
* as that may change its value */
cheevos_hardcore_active = rcheevos_hardcore_active();
if (cheevos_hardcore_active)
{
static int unpaused_frames = 0;
if (runloop_st->flags & RUNLOOP_FLAG_PAUSED)
unpaused_frames = 0;
else
/* Frame advance is not allowed when achievement hardcore is active */
{
/* Limit pause to approximately three times per second (depending on core framerate) */
if (unpaused_frames < 20)
{
++unpaused_frames;
pause_pressed = false;
}
}
}
else
#endif
{
frameadvance_pressed = BIT256_GET(current_bits, RARCH_FRAMEADVANCE);
frameadvance_trigger = frameadvance_pressed && !old_frameadvance;
/* FRAMEADVANCE will set us into special pause mode. */
if (frameadvance_trigger)
{
pauseframeadvance = true;
if (!(runloop_st->flags & RUNLOOP_FLAG_PAUSED))
pause_pressed = true;
}
}
/* Decide pause hotkey */
runloop_pause_toggle(&runloop_paused_hotkey,
pause_pressed, old_pause_pressed,
focused, old_focus);
old_focus = focused;
old_pause_pressed = pause_pressed;
old_frameadvance = frameadvance_pressed;
if (runloop_st->flags & RUNLOOP_FLAG_PAUSED)
{
#ifdef HAVE_REWIND
/* Frame advance must also trigger rewind save */
if (frameadvance_trigger && runloop_paused)
state_manager_check_rewind(
&runloop_st->rewind_st,
&runloop_st->current_core,
false,
settings->uints.rewind_granularity,
false,
NULL, 0, NULL);
#endif
/* Check if it's not oneshot */
#ifdef HAVE_REWIND
if (!(frameadvance_trigger || BIT256_GET(current_bits, RARCH_REWIND)))
#else
if (!frameadvance_trigger)
#endif
focused = false;
else
runloop_paused = false;
/* Drop to RUNLOOP_STATE_POLLED_AND_SLEEP if frameadvance is triggered */
if (pauseframeadvance)
runloop_paused = false;
}
}
/* Check recording hotkey */
HOTKEY_CHECK(RARCH_RECORDING_TOGGLE, CMD_EVENT_RECORDING_TOGGLE, true, NULL);
/* Check streaming hotkey */
HOTKEY_CHECK(RARCH_STREAMING_TOGGLE, CMD_EVENT_STREAMING_TOGGLE, true, NULL);
/* Check Run-Ahead hotkey */
HOTKEY_CHECK(RARCH_RUNAHEAD_TOGGLE, CMD_EVENT_RUNAHEAD_TOGGLE, true, NULL);
/* Check Preemptive Frames hotkey */
HOTKEY_CHECK(RARCH_PREEMPT_TOGGLE, CMD_EVENT_PREEMPT_TOGGLE, true, NULL);
/* Check AI Service hotkey */
HOTKEY_CHECK(RARCH_AI_SERVICE, CMD_EVENT_AI_SERVICE_TOGGLE, true, NULL);
#ifdef HAVE_NETWORKING
/* Check netplay hotkeys */
HOTKEY_CHECK(RARCH_NETPLAY_PING_TOGGLE, CMD_EVENT_NETPLAY_PING_TOGGLE, true, NULL);
HOTKEY_CHECK(RARCH_NETPLAY_GAME_WATCH, CMD_EVENT_NETPLAY_GAME_WATCH, true, NULL);
HOTKEY_CHECK(RARCH_NETPLAY_PLAYER_CHAT, CMD_EVENT_NETPLAY_PLAYER_CHAT, true, NULL);
HOTKEY_CHECK(RARCH_NETPLAY_FADE_CHAT_TOGGLE, CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE, true, NULL);
#endif
#ifdef HAVE_ACCESSIBILITY
#ifdef HAVE_TRANSLATE
/* Copy over the retropad state to a buffer for the translate service
to send off if it's run. */
if (settings->bools.ai_service_enable)
{
input_st->ai_gamepad_state[0] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_B);
input_st->ai_gamepad_state[1] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_Y);
input_st->ai_gamepad_state[2] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_SELECT);
input_st->ai_gamepad_state[3] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_START);
input_st->ai_gamepad_state[4] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_UP);
input_st->ai_gamepad_state[5] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_DOWN);
input_st->ai_gamepad_state[6] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_LEFT);
input_st->ai_gamepad_state[7] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_RIGHT);
input_st->ai_gamepad_state[8] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_A);
input_st->ai_gamepad_state[9] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_X);
input_st->ai_gamepad_state[10] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L);
input_st->ai_gamepad_state[11] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R);
input_st->ai_gamepad_state[12] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L2);
input_st->ai_gamepad_state[13] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R2);
input_st->ai_gamepad_state[14] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L3);
input_st->ai_gamepad_state[15] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R3);
}
#endif
#endif
if (!focused && !runloop_paused)
{
cbs->poll_cb();
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
/* Apply any pending fastmotion override parameters */
if (runloop_st->fastmotion_override.pending)
{
runloop_apply_fastmotion_override(runloop_st, settings);
runloop_st->fastmotion_override.pending = false;
}
/* Check fastmotion hotkeys */
/* To avoid continuous switching if we hold the button down, we require
* that the button must go from pressed to unpressed back to pressed
* to be able to toggle between them.
*/
if (!runloop_st->fastmotion_override.current.inhibit_toggle)
{
static bool old_button_state = false;
static bool old_hold_button_state = false;
bool new_button_state = BIT256_GET(
current_bits, RARCH_FAST_FORWARD_KEY);
bool new_hold_button_state = BIT256_GET(
current_bits, RARCH_FAST_FORWARD_HOLD_KEY);
bool check2 = new_button_state
&& !old_button_state;
if (!check2)
check2 = old_hold_button_state != new_hold_button_state;
/* Don't allow fastmotion while paused */
if (runloop_paused)
{
check2 = true;
new_button_state = false;
new_hold_button_state = false;
input_st->flags |= INP_FLAG_NONBLOCKING;
}
if (check2)
{
if (input_st->flags & INP_FLAG_NONBLOCKING)
{
input_st->flags &= ~INP_FLAG_NONBLOCKING;
runloop_st->flags &= ~RUNLOOP_FLAG_FASTMOTION;
runloop_st->fastforward_after_frames = 1;
}
else
{
input_st->flags |= INP_FLAG_NONBLOCKING;
runloop_st->flags |= RUNLOOP_FLAG_FASTMOTION;
command_event(CMD_EVENT_SET_FRAME_LIMIT, NULL);
}
driver_set_nonblock_state();
/* Reset frame time counter when toggling
* fast-forward off, if required */
if ( !(runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
&& settings->bools.frame_time_counter_reset_after_fastforwarding)
video_st->frame_time_count = 0;
}
old_button_state = new_button_state;
old_hold_button_state = new_hold_button_state;
}
/* Display fast-forward notification, unless
* disabled via override */
if (!runloop_st->fastmotion_override.current.fastforward ||
runloop_st->fastmotion_override.current.notification)
{
/* > Use widgets, if enabled */
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
if (settings->bools.notification_show_fast_forward)
{
if (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
video_st->flags |= VIDEO_FLAG_WIDGETS_FAST_FORWARD;
else
video_st->flags &= ~VIDEO_FLAG_WIDGETS_FAST_FORWARD;
}
else
video_st->flags &= ~VIDEO_FLAG_WIDGETS_FAST_FORWARD;
}
else
#endif
{
/* > If widgets are disabled, display fast-forward
* status via OSD text for 1 frame every frame */
if ( (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
&& settings->bools.notification_show_fast_forward)
runloop_msg_queue_push(
msg_hash_to_str(MSG_FAST_FORWARD), 1, 1, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
#if defined(HAVE_GFX_WIDGETS)
else
video_st->flags &= ~VIDEO_FLAG_WIDGETS_FAST_FORWARD;
#endif
#ifdef HAVE_CHEEVOS
if (!cheevos_hardcore_active)
#endif
{
{
/* Check slowmotion hotkeys */
static bool old_slowmotion_button_state = false;
static bool old_slowmotion_hold_button_state = false;
bool new_slowmotion_button_state = BIT256_GET(
current_bits, RARCH_SLOWMOTION_KEY);
bool new_slowmotion_hold_button_state = BIT256_GET(
current_bits, RARCH_SLOWMOTION_HOLD_KEY);
/* Don't allow slowmotion while paused */
if (runloop_paused)
{
new_slowmotion_button_state = false;
new_slowmotion_hold_button_state = false;
}
if (new_slowmotion_button_state && !old_slowmotion_button_state)
{
if (!(runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION))
runloop_st->flags |= RUNLOOP_FLAG_SLOWMOTION;
else
runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION;
}
else if (old_slowmotion_hold_button_state != new_slowmotion_hold_button_state)
{
if (new_slowmotion_hold_button_state)
runloop_st->flags |= RUNLOOP_FLAG_SLOWMOTION;
else
runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION;
}
if (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
{
if (settings->uints.video_black_frame_insertion)
if (!(runloop_st->flags & RUNLOOP_FLAG_IDLE))
video_driver_cached_frame();
#if defined(HAVE_GFX_WIDGETS)
if (!widgets_active)
#endif
{
#ifdef HAVE_REWIND
struct state_manager_rewind_state
*rewind_st = &runloop_st->rewind_st;
if (rewind_st->flags
& STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED)
runloop_msg_queue_push(
msg_hash_to_str(MSG_SLOW_MOTION_REWIND), 1, 1, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
else
#endif
runloop_msg_queue_push(
msg_hash_to_str(MSG_SLOW_MOTION), 1, 1, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
old_slowmotion_button_state = new_slowmotion_button_state;
old_slowmotion_hold_button_state = new_slowmotion_hold_button_state;
}
}
/* Check save state slot hotkeys */
{
static bool old_should_slot_increase = false;
static bool old_should_slot_decrease = false;
bool should_slot_increase = BIT256_GET(
current_bits, RARCH_STATE_SLOT_PLUS);
bool should_slot_decrease = BIT256_GET(
current_bits, RARCH_STATE_SLOT_MINUS);
bool check1 = true;
bool check2 = should_slot_increase && !old_should_slot_increase;
int addition = 1;
int state_slot = settings->ints.state_slot;
if (!check2)
{
check2 = should_slot_decrease && !old_should_slot_decrease;
check1 = state_slot > -1;
addition = -1;
/* Wrap-around to 999 */
if (check2 && !check1 && state_slot + addition < -1)
{
state_slot = 1000;
check1 = true;
}
}
/* Wrap-around to -1 (Auto) */
else if (state_slot + addition > 999)
state_slot = -2;
if (check2)
{
size_t _len;
char msg[128];
int cur_state_slot = state_slot + addition;
if (check1)
configuration_set_int(settings, settings->ints.state_slot,
cur_state_slot);
_len = strlcpy(msg, msg_hash_to_str(MSG_STATE_SLOT), sizeof(msg));
if (cur_state_slot < 0)
snprintf(msg + _len, sizeof(msg) - _len,
": %d (Auto)", settings->ints.state_slot);
else
snprintf(msg + _len, sizeof(msg) - _len,
": %d", settings->ints.state_slot);
#ifdef HAVE_GFX_WIDGETS
if (dispwidget_get_ptr()->active)
gfx_widget_set_generic_message(msg, 1000);
else
#endif
runloop_msg_queue_push(msg, 2, 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("[State]: %s\n", msg);
}
old_should_slot_increase = should_slot_increase;
old_should_slot_decrease = should_slot_decrease;
}
/* Check replay slot hotkeys */
{
static bool old_should_replay_slot_increase = false;
static bool old_should_replay_slot_decrease = false;
bool should_slot_increase = BIT256_GET(
current_bits, RARCH_REPLAY_SLOT_PLUS);
bool should_slot_decrease = BIT256_GET(
current_bits, RARCH_REPLAY_SLOT_MINUS);
bool check1 = true;
bool check2 = should_slot_increase && !old_should_replay_slot_increase;
int addition = 1;
int replay_slot = settings->ints.replay_slot;
if (!check2)
{
check2 = should_slot_decrease && !old_should_replay_slot_decrease;
check1 = replay_slot > -1;
addition = -1;
/* Wrap-around to 999 */
if (check2 && !check1 && replay_slot + addition < -1)
{
replay_slot = 1000;
check1 = true;
}
}
/* Wrap-around to -1 (Auto) */
else if (replay_slot + addition > 999)
replay_slot = -2;
if (check2)
{
size_t _len;
char msg[128];
int cur_replay_slot = replay_slot + addition;
if (check1)
configuration_set_int(settings, settings->ints.replay_slot,
cur_replay_slot);
_len = strlcpy(msg, msg_hash_to_str(MSG_REPLAY_SLOT), sizeof(msg));
if (cur_replay_slot < 0)
snprintf(msg + _len, sizeof(msg) - _len,
": %d (Auto)", settings->ints.replay_slot);
else
snprintf(msg + _len, sizeof(msg) - _len,
": %d", settings->ints.replay_slot);
#ifdef HAVE_GFX_WIDGETS
if (dispwidget_get_ptr()->active)
gfx_widget_set_generic_message(msg, 1000);
else
#endif
runloop_msg_queue_push(msg, 2, 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("[Replay]: %s\n", msg);
}
old_should_replay_slot_increase = should_slot_increase;
old_should_replay_slot_decrease = should_slot_decrease;
}
/* Check save state hotkeys */
HOTKEY_CHECK(RARCH_SAVE_STATE_KEY, CMD_EVENT_SAVE_STATE, true, NULL);
HOTKEY_CHECK(RARCH_LOAD_STATE_KEY, CMD_EVENT_LOAD_STATE, true, NULL);
/* Check reset hotkey */
HOTKEY_CHECK(RARCH_RESET, CMD_EVENT_RESET, true, NULL);
/* Check VRR runloop hotkey */
HOTKEY_CHECK(RARCH_VRR_RUNLOOP_TOGGLE, CMD_EVENT_VRR_RUNLOOP_TOGGLE, true, NULL);
/* Check bsv movie hotkeys */
HOTKEY_CHECK(RARCH_PLAY_REPLAY_KEY, CMD_EVENT_PLAY_REPLAY, true, NULL);
HOTKEY_CHECK(RARCH_RECORD_REPLAY_KEY, CMD_EVENT_RECORD_REPLAY, true, NULL);
HOTKEY_CHECK(RARCH_HALT_REPLAY_KEY, CMD_EVENT_HALT_REPLAY, true, NULL);
/* Check Disc Control hotkeys */
HOTKEY_CHECK3(
RARCH_DISK_EJECT_TOGGLE, CMD_EVENT_DISK_EJECT_TOGGLE,
RARCH_DISK_NEXT, CMD_EVENT_DISK_NEXT,
RARCH_DISK_PREV, CMD_EVENT_DISK_PREV);
/* Check cheat hotkeys */
HOTKEY_CHECK3(
RARCH_CHEAT_INDEX_PLUS, CMD_EVENT_CHEAT_INDEX_PLUS,
RARCH_CHEAT_INDEX_MINUS, CMD_EVENT_CHEAT_INDEX_MINUS,
RARCH_CHEAT_TOGGLE, CMD_EVENT_CHEAT_TOGGLE);
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
/* Check shader hotkeys */
HOTKEY_CHECK3(
RARCH_SHADER_NEXT, CMD_EVENT_SHADER_NEXT,
RARCH_SHADER_PREV, CMD_EVENT_SHADER_PREV,
RARCH_SHADER_TOGGLE, CMD_EVENT_SHADER_TOGGLE);
if (settings->bools.video_shader_watch_files)
{
static rarch_timer_t timer = {0};
static bool need_to_apply = false;
if (video_shader_check_for_changes())
{
need_to_apply = true;
if (!timer.timer_begin)
{
timer.timeout_us = SHADER_FILE_WATCH_DELAY_MSEC * 1000;
timer.current = cpu_features_get_time_usec();
timer.timeout_end = timer.current + timer.timeout_us;
timer.timer_begin = true;
timer.timer_end = false;
}
}
/* If a file is modified atomically (moved/renamed from a different file),
* we have no idea how long that might take.
* If we're trying to re-apply shaders immediately after changes are made
* to the original file(s), the filesystem might be in an in-between
* state where the new file hasn't been moved over yet and the original
* file was already deleted. This leaves us no choice but to wait an
* arbitrary amount of time and hope for the best.
*/
if (need_to_apply)
{
timer.current = current_time;
timer.timeout_us = timer.timeout_end - timer.current;
if ( !timer.timer_end
&& timer.timeout_us <= 0)
{
timer.timer_end = true;
timer.timer_begin = false;
timer.timeout_end = 0;
need_to_apply = false;
command_event(CMD_EVENT_SHADERS_APPLY_CHANGES, NULL);
}
}
}
if ( settings->uints.video_shader_delay
&& !runloop_st->shader_delay_timer.timer_end)
{
if (!runloop_st->shader_delay_timer.timer_begin)
{
runloop_st->shader_delay_timer.timeout_us = settings->uints.video_shader_delay * 1000;
runloop_st->shader_delay_timer.current = cpu_features_get_time_usec();
runloop_st->shader_delay_timer.timeout_end = runloop_st->shader_delay_timer.current
+ runloop_st->shader_delay_timer.timeout_us;
runloop_st->shader_delay_timer.timer_begin = true;
runloop_st->shader_delay_timer.timer_end = false;
}
else
{
runloop_st->shader_delay_timer.current = current_time;
runloop_st->shader_delay_timer.timeout_us = runloop_st->shader_delay_timer.timeout_end
- runloop_st->shader_delay_timer.current;
if (runloop_st->shader_delay_timer.timeout_us <= 0)
{
runloop_st->shader_delay_timer.timer_end = true;
runloop_st->shader_delay_timer.timer_begin = false;
runloop_st->shader_delay_timer.timeout_end = 0;
{
const char *preset = video_shader_get_current_shader_preset();
enum rarch_shader_type type = video_shader_parse_type(preset);
video_shader_apply_shader(settings, type, preset, false);
}
}
}
}
#endif
if (runloop_paused)
{
cbs->poll_cb();
return RUNLOOP_STATE_PAUSE;
}
return RUNLOOP_STATE_ITERATE;
}
/**
* runloop_iterate:
*
* Run Libretro core in RetroArch for one frame.
*
* Returns: 0 on success, 1 if we have to wait until
* button input in order to wake up the loop,
* -1 if we forcibly quit out of the RetroArch iteration loop.
**/
int runloop_iterate(void)
{
int i;
enum analog_dpad_mode dpad_mode[MAX_USERS];
input_driver_state_t *input_st = input_state_get_ptr();
audio_driver_state_t *audio_st = audio_state_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
recording_state_t *recording_st = recording_state_get_ptr();
camera_driver_state_t *camera_st = camera_state_get_ptr();
#if defined(HAVE_COCOATOUCH)
uico_driver_state_t *uico_st = uico_state_get_ptr();
#endif
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = &runloop_state;
unsigned video_frame_delay = settings->uints.video_frame_delay;
unsigned video_frame_delay_effective = video_st->frame_delay_effective;
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
unsigned max_users = settings->uints.input_max_users;
retro_time_t current_time = cpu_features_get_time_usec();
#ifdef HAVE_MENU
#ifdef HAVE_NETWORKING
bool menu_pause_libretro = settings->bools.menu_pause_libretro &&
netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
#else
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
#endif
bool core_paused =
(runloop_st->flags & RUNLOOP_FLAG_PAUSED) ||
(menu_pause_libretro && (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE));
#else
bool core_paused = (runloop_st->flags & RUNLOOP_FLAG_PAUSED);
#endif
float slowmotion_ratio = settings->floats.slowmotion_ratio;
#ifdef HAVE_CHEEVOS
bool cheevos_enable = settings->bools.cheevos_enable;
#endif
bool audio_sync = settings->bools.audio_sync;
#ifdef HAVE_DISCORD
discord_state_t *discord_st = discord_state_get_ptr();
if (discord_st->inited)
{
Discord_RunCallbacks();
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
}
#endif
if (runloop_st->frame_time.callback)
{
/* Updates frame timing if frame timing callback is in use by the core.
* Limits frame time if fast forward ratio throttle is enabled. */
retro_usec_t runloop_last_frame_time = runloop_st->frame_time_last;
retro_time_t current = current_time;
bool is_locked_fps = (
(runloop_st->flags & RUNLOOP_FLAG_PAUSED)
|| (input_st->flags & INP_FLAG_NONBLOCKING))
| !!recording_st->data;
retro_time_t delta = (!runloop_last_frame_time || is_locked_fps)
? runloop_st->frame_time.reference
: (current - runloop_last_frame_time);
if (is_locked_fps)
runloop_st->frame_time_last = 0;
else
{
runloop_st->frame_time_last = current;
if (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
delta /= slowmotion_ratio;
}
if (!core_paused)
runloop_st->frame_time.callback(delta);
}
/* Update audio buffer occupancy if buffer status
* callback is in use by the core */
if (runloop_st->audio_buffer_status.callback)
{
bool audio_buf_active = false;
unsigned audio_buf_occupancy = 0;
bool audio_buf_underrun = false;
if (!( (runloop_st->flags & RUNLOOP_FLAG_PAUSED)
|| !(audio_st->flags & AUDIO_FLAG_ACTIVE)
|| !(audio_st->output_samples_buf))
&& audio_st->current_audio->write_avail
&& audio_st->context_audio_data
&& audio_st->buffer_size)
{
size_t audio_buf_avail;
if ((audio_buf_avail = audio_st->current_audio->write_avail(
audio_st->context_audio_data)) > audio_st->buffer_size)
audio_buf_avail = audio_st->buffer_size;
audio_buf_occupancy = (unsigned)(100 - (audio_buf_avail * 100) /
audio_st->buffer_size);
/* Elsewhere, we standardise on a 'low water mark'
* of 25% of the total audio buffer size - use
* the same metric here (can be made more sophisticated
* if required - i.e. determine buffer occupancy in
* terms of usec, and weigh this against the expected
* frame time) */
audio_buf_underrun = audio_buf_occupancy < 25;
audio_buf_active = true;
}
if (!core_paused)
runloop_st->audio_buffer_status.callback(
audio_buf_active, audio_buf_occupancy, audio_buf_underrun);
}
switch ((enum runloop_state_enum)runloop_check_state(
global_get_ptr()->error_on_init,
settings, current_time))
{
case RUNLOOP_STATE_QUIT:
runloop_st->frame_limit_last_time = 0.0;
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_RUNNING;
command_event(CMD_EVENT_QUIT, NULL);
return -1;
case RUNLOOP_STATE_POLLED_AND_SLEEP:
#ifdef HAVE_NETWORKING
/* FIXME: This is an ugly way to tell Netplay this... */
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
#if defined(HAVE_COCOATOUCH)
if (!(uico_st->flags & UICO_ST_FLAG_IS_ON_FOREGROUND))
#endif
retro_sleep(10);
return 1;
case RUNLOOP_STATE_PAUSE:
#ifdef HAVE_NETWORKING
/* FIXME: This is an ugly way to tell Netplay this... */
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
video_driver_cached_frame();
return 1;
case RUNLOOP_STATE_END:
#ifdef HAVE_NETWORKING
#ifdef HAVE_MENU
/* FIXME: This is an ugly way to tell Netplay this... */
if (menu_pause_libretro &&
netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)
)
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
#endif
goto end;
case RUNLOOP_STATE_MENU_ITERATE:
#ifdef HAVE_NETWORKING
/* FIXME: This is an ugly way to tell Netplay this... */
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
return 0;
case RUNLOOP_STATE_ITERATE:
runloop_st->flags |= RUNLOOP_FLAG_CORE_RUNNING;
break;
}
#ifdef HAVE_THREADS
if (runloop_st->flags & RUNLOOP_FLAG_AUTOSAVE)
autosave_lock();
#endif
#ifdef HAVE_BSV_MOVIE
bsv_movie_next_frame(input_st);
#endif
if ( camera_st->cb.caps
&& camera_st->driver
&& camera_st->driver->poll
&& camera_st->data)
camera_st->driver->poll(camera_st->data,
camera_st->cb.frame_raw_framebuffer,
camera_st->cb.frame_opengl_texture);
/* Update binds for analog dpad modes. */
for (i = 0; i < (int)max_users; i++)
{
dpad_mode[i] = (enum analog_dpad_mode)
settings->uints.input_analog_dpad_mode[i];
switch (dpad_mode[i])
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
{
unsigned mapped_port = settings->uints.input_remap_ports[i];
if (input_st->analog_requested[mapped_port])
dpad_mode[i] = ANALOG_DPAD_NONE;
}
break;
case ANALOG_DPAD_LSTICK_FORCED:
dpad_mode[i] = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
dpad_mode[i] = ANALOG_DPAD_RSTICK;
break;
default:
break;
}
/* Push analog to D-Pad mappings to binds. */
if (dpad_mode[i] != ANALOG_DPAD_NONE)
{
unsigned k;
unsigned joy_idx = settings->uints.input_joypad_index[i];
struct retro_keybind *general_binds = input_config_binds[joy_idx];
struct retro_keybind *auto_binds = input_autoconf_binds[joy_idx];
unsigned x_plus = RARCH_ANALOG_RIGHT_X_PLUS;
unsigned y_plus = RARCH_ANALOG_RIGHT_Y_PLUS;
unsigned x_minus = RARCH_ANALOG_RIGHT_X_MINUS;
unsigned y_minus = RARCH_ANALOG_RIGHT_Y_MINUS;
if (dpad_mode[i] == ANALOG_DPAD_LSTICK)
{
x_plus = RARCH_ANALOG_LEFT_X_PLUS;
y_plus = RARCH_ANALOG_LEFT_Y_PLUS;
x_minus = RARCH_ANALOG_LEFT_X_MINUS;
y_minus = RARCH_ANALOG_LEFT_Y_MINUS;
}
for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
{
(auto_binds)[k].orig_joyaxis = (auto_binds)[k].joyaxis;
(general_binds)[k].orig_joyaxis = (general_binds)[k].joyaxis;
}
if (!INHERIT_JOYAXIS(auto_binds))
{
unsigned j = x_plus + 3;
/* Inherit joyaxis from analogs. */
for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
(auto_binds)[k].joyaxis = (auto_binds)[j--].joyaxis;
}
if (!INHERIT_JOYAXIS(general_binds))
{
unsigned j = x_plus + 3;
/* Inherit joyaxis from analogs. */
for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
(general_binds)[k].joyaxis = (general_binds)[j--].joyaxis;
}
}
}
/* Frame delay */
if ( !(input_st->flags & INP_FLAG_NONBLOCKING)
|| (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
{
bool skip_delay = core_paused
|| (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
|| (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION);
if (settings->bools.video_frame_delay_auto)
{
float refresh_rate = settings->floats.video_refresh_rate;
uint8_t video_swap_interval = runloop_get_video_swap_interval(
settings->uints.video_swap_interval);
uint8_t video_bfi = settings->uints.video_black_frame_insertion;
uint8_t frame_time_interval = 8;
static uint8_t skip_update = 0;
static bool skip_delay_prev = false;
bool frame_time_update =
/* Skip some initial frames for stabilization */
video_st->frame_count > frame_time_interval &&
/* Only update when there are enough frames for averaging */
video_st->frame_count % frame_time_interval == 0;
/* A few frames must be ignored after slow+fastmotion/pause
* is disabled or geometry change is triggered */
if ( (!skip_delay && skip_delay_prev)
|| video_st->frame_delay_pause)
{
skip_update = frame_time_interval * 4;
video_st->frame_delay_pause = false;
}
if (skip_update)
skip_update--;
skip_delay_prev = skip_delay;
/* Always skip when slow+fastmotion/pause is active */
if (skip_delay_prev)
skip_update = 1;
if (skip_update)
frame_time_update = false;
/* Black frame insertion + swap interval multiplier */
refresh_rate = (refresh_rate / (video_bfi + 1.0f) / video_swap_interval);
/* Set target moderately as half frame time with 0 (Auto) delay */
if (video_frame_delay == 0)
video_frame_delay = 1 / refresh_rate * 1000 / 2;
/* Reset new desired delay target */
if (video_st->frame_delay_target != video_frame_delay)
{
frame_time_update = false;
video_st->frame_delay_target = video_frame_delay_effective = video_frame_delay;
RARCH_LOG("[Video]: Frame delay reset to %d ms.\n", video_frame_delay);
}
/* Decide what should happen to effective delay */
if (video_frame_delay_effective > 0 && frame_time_update)
{
video_frame_delay_auto_t vfda = {0};
vfda.frame_time_interval = frame_time_interval;
vfda.refresh_rate = refresh_rate;
video_frame_delay_auto(video_st, &vfda);
if (vfda.delay_decrease > 0)
{
video_frame_delay_effective -= vfda.delay_decrease;
RARCH_LOG("[Video]: Frame delay decrease by %d ms to %d ms due to frame time average: %d > %d.\n",
vfda.delay_decrease, video_frame_delay_effective, vfda.frame_time_avg, vfda.frame_time_target);
}
}
}
else
video_st->frame_delay_target = video_frame_delay_effective = video_frame_delay;
video_st->frame_delay_effective = video_frame_delay_effective;
/* Never apply frame delay when slow+fastmotion/pause is active */
if (video_frame_delay_effective > 0 && !skip_delay)
retro_sleep(video_frame_delay_effective);
}
{
#ifdef HAVE_RUNAHEAD
bool run_ahead_enabled = settings->bools.run_ahead_enabled;
unsigned run_ahead_num_frames = settings->uints.run_ahead_frames;
bool run_ahead_hide_warnings = settings->bools.run_ahead_hide_warnings;
bool run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance;
/* Run Ahead Feature replaces the call to core_run in this loop */
bool want_runahead = run_ahead_enabled
&& (run_ahead_num_frames > 0)
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE);
#ifdef HAVE_NETWORKING
want_runahead = want_runahead
&& !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
#endif
if (want_runahead)
runahead_run(
runloop_st,
run_ahead_num_frames,
run_ahead_hide_warnings,
run_ahead_secondary_instance);
else if (runloop_st->preempt_data)
preempt_run(runloop_st->preempt_data, runloop_st);
else
#endif
core_run();
}
/* Increment runtime tick counter after each call to
* core_run() or run_ahead() */
runloop_st->core_runtime_usec += runloop_core_runtime_tick(
runloop_st,
slowmotion_ratio,
current_time);
#ifdef HAVE_CHEEVOS
if (cheevos_enable)
rcheevos_test();
#endif
#ifdef HAVE_CHEATS
cheat_manager_apply_retro_cheats();
#endif
#ifdef HAVE_PRESENCE
presence_update(PRESENCE_GAME);
#endif
/* Restores analog D-pad binds temporarily overridden. */
for (i = 0; i < (int)max_users; i++)
{
if (dpad_mode[i] != ANALOG_DPAD_NONE)
{
int j;
unsigned joy_idx = settings->uints.input_joypad_index[i];
struct retro_keybind *general_binds = input_config_binds[joy_idx];
struct retro_keybind *auto_binds = input_autoconf_binds[joy_idx];
for (j = RETRO_DEVICE_ID_JOYPAD_UP; j <= RETRO_DEVICE_ID_JOYPAD_RIGHT; j++)
{
(auto_binds)[j].joyaxis = (auto_binds)[j].orig_joyaxis;
(general_binds)[j].joyaxis = (general_binds)[j].orig_joyaxis;
}
}
}
#ifdef HAVE_BSV_MOVIE
bsv_movie_finish_rewind(input_st);
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)
{
movie_stop_playback(input_st);
command_event(CMD_EVENT_PAUSE, NULL);
}
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)
{
movie_stop_playback(input_st);
command_event(CMD_EVENT_PAUSE, NULL);
}
#endif
#ifdef HAVE_THREADS
if (runloop_st->flags & RUNLOOP_FLAG_AUTOSAVE)
autosave_unlock();
#endif
end:
if (vrr_runloop_enable)
{
/* Sync on video only, block audio later. */
if (runloop_st->fastforward_after_frames && audio_sync)
{
if (runloop_st->fastforward_after_frames == 1)
{
/* Nonblocking audio */
if ( (audio_st->flags & AUDIO_FLAG_ACTIVE)
&& (audio_st->context_audio_data))
audio_st->current_audio->set_nonblock_state(
audio_st->context_audio_data, true);
audio_st->chunk_size =
audio_st->chunk_nonblock_size;
}
runloop_st->fastforward_after_frames++;
if (runloop_st->fastforward_after_frames == 6)
{
/* Blocking audio */
if ( (audio_st->flags & AUDIO_FLAG_ACTIVE)
&& (audio_st->context_audio_data))
audio_st->current_audio->set_nonblock_state(
audio_st->context_audio_data,
audio_sync ? false : true);
audio_st->chunk_size = audio_st->chunk_block_size;
runloop_st->fastforward_after_frames = 0;
}
}
if (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION)
runloop_set_frame_limit(&video_st->av_info,
runloop_get_fastforward_ratio(settings,
&runloop_st->fastmotion_override.current));
else
runloop_set_frame_limit(&video_st->av_info,
1.0f);
}
/* if there's a fast forward limit, inject sleeps to keep from going too fast. */
if (runloop_st->frame_limit_minimum_time)
{
const retro_time_t end_frame_time = cpu_features_get_time_usec();
const retro_time_t to_sleep_ms = (
( runloop_st->frame_limit_last_time
+ runloop_st->frame_limit_minimum_time)
- end_frame_time) / 1000;
if (to_sleep_ms > 0)
{
unsigned sleep_ms = (unsigned)to_sleep_ms;
/* Combat jitter a bit. */
runloop_st->frame_limit_last_time +=
runloop_st->frame_limit_minimum_time;
if (sleep_ms > 0)
{
#if defined(HAVE_COCOATOUCH)
if (!(uico_state_get_ptr()->flags & UICO_ST_FLAG_IS_ON_FOREGROUND))
#endif
retro_sleep(sleep_ms);
}
return 1;
}
runloop_st->frame_limit_last_time = end_frame_time;
}
/* Set paused state after x frames */
if (runloop_st->run_frames_and_pause > 0)
{
runloop_st->run_frames_and_pause--;
if (!runloop_st->run_frames_and_pause)
runloop_st->flags |= RUNLOOP_FLAG_PAUSED;
}
return 0;
}
void runloop_msg_queue_deinit(void)
{
runloop_state_t *runloop_st = &runloop_state;
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
msg_queue_deinitialize(&runloop_st->msg_queue);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
#ifdef HAVE_THREADS
slock_free(runloop_st->msg_queue_lock);
runloop_st->msg_queue_lock = NULL;
#endif
runloop_st->msg_queue_size = 0;
}
void runloop_msg_queue_init(void)
{
runloop_state_t *runloop_st = &runloop_state;
runloop_msg_queue_deinit();
msg_queue_initialize(&runloop_st->msg_queue, 8);
#ifdef HAVE_THREADS
runloop_st->msg_queue_lock = slock_new();
#endif
}
void runloop_task_msg_queue_push(
retro_task_t *task, const char *msg,
unsigned prio, unsigned duration,
bool flush)
{
#if defined(HAVE_GFX_WIDGETS)
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
#ifdef HAVE_ACCESSIBILITY
access_state_t *access_st = access_state_get_ptr();
settings_t *settings = config_get_ptr();
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
runloop_state_t *runloop_st = &runloop_state;
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
bool widgets_active = p_dispwidget->active;
if (widgets_active && task->title && !task->mute)
{
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
ui_companion_driver_msg_queue_push(msg,
prio, task ? duration : duration * 60 / 1000, flush);
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
access_st->enabled))
accessibility_speak_priority(
accessibility_enable,
accessibility_narrator_speech_speed,
(char*)msg, 0);
#endif
gfx_widgets_msg_queue_push(
task,
msg,
duration,
NULL,
(enum message_queue_icon)MESSAGE_QUEUE_CATEGORY_INFO,
(enum message_queue_category)MESSAGE_QUEUE_ICON_DEFAULT,
prio,
flush,
#ifdef HAVE_MENU
menu_st->flags & MENU_ST_FLAG_ALIVE
#else
false
#endif
);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
}
else
#endif
runloop_msg_queue_push(msg, prio, duration, flush, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
bool runloop_get_current_savestate_path(char *path, size_t len)
{
settings_t *settings = config_get_ptr();
int state_slot = settings ? settings->ints.state_slot : 0;
return runloop_get_savestate_path(path, len, state_slot);
}
bool runloop_get_savestate_path(char *path, size_t len, int state_slot)
{
runloop_state_t *runloop_st = &runloop_state;
const char *name_savestate = NULL;
if (!path)
return false;
name_savestate = runloop_st->name.savestate;
if (string_is_empty(name_savestate))
return false;
if (state_slot < 0)
fill_pathname_join_delim(path, name_savestate, "auto", '.', len);
else
{
size_t _len = strlcpy(path, name_savestate, len);
if (state_slot > 0)
snprintf(path + _len, len - _len, "%d", state_slot);
}
return true;
}
bool runloop_get_current_replay_path(char *path, size_t len)
{
settings_t *settings = config_get_ptr();
int slot = settings ? settings->ints.replay_slot : 0;
return runloop_get_replay_path(path, len, slot);
}
bool runloop_get_replay_path(char *path, size_t len, unsigned slot)
{
size_t _len;
runloop_state_t *runloop_st = &runloop_state;
const char *name_replay = NULL;
if (!path)
return false;
name_replay = runloop_st->name.replay;
if (string_is_empty(name_replay))
return false;
_len = strlcpy(path, name_replay, len);
if (slot >= 0)
snprintf(path + _len, len - _len, "%d", slot);
return true;
}
bool runloop_get_entry_state_path(char *path, size_t len, unsigned slot)
{
size_t _len;
runloop_state_t *runloop_st = &runloop_state;
const char *name_savestate = NULL;
if (!path || !slot)
return false;
name_savestate = runloop_st->name.savestate;
if (string_is_empty(name_savestate))
return false;
_len = strlcpy(path, name_savestate, len);
snprintf(path + _len, len - _len, "%d.entry", slot);
return true;
}
void runloop_set_current_core_type(
enum rarch_core_type type, bool explicitly_set)
{
runloop_state_t *runloop_st = &runloop_state;
if (runloop_st->flags & RUNLOOP_FLAG_HAS_SET_CORE)
return;
if (explicitly_set)
{
runloop_st->flags |= RUNLOOP_FLAG_HAS_SET_CORE;
runloop_st->explicit_current_core_type = type;
}
runloop_st->current_core_type = type;
}
bool core_set_default_callbacks(void *data)
{
struct retro_callbacks *cbs = (struct retro_callbacks*)data;
retro_input_state_t state_cb = core_input_state_poll_return_cb();
cbs->frame_cb = video_driver_frame;
cbs->sample_cb = audio_driver_sample;
cbs->sample_batch_cb = audio_driver_sample_batch;
cbs->state_cb = state_cb;
cbs->poll_cb = input_driver_poll;
return true;
}
#ifdef HAVE_NETWORKING
/**
* core_set_netplay_callbacks:
*
* Set the I/O callbacks to use netplay's interceding callback system. Should
* only be called while initializing netplay.
**/
bool core_set_netplay_callbacks(void)
{
runloop_state_t *runloop_st = &runloop_state;
/* Force normal poll type for netplay. */
runloop_st->current_core.poll_type = POLL_TYPE_NORMAL;
/* And use netplay's interceding callbacks */
runloop_st->current_core.retro_set_video_refresh(video_frame_net);
runloop_st->current_core.retro_set_audio_sample(audio_sample_net);
runloop_st->current_core.retro_set_audio_sample_batch(audio_sample_batch_net);
runloop_st->current_core.retro_set_input_state(input_state_net);
return true;
}
/**
* core_unset_netplay_callbacks
*
* Unset the I/O callbacks from having used netplay's interceding callback
* system. Should only be called while uninitializing netplay.
*/
bool core_unset_netplay_callbacks(void)
{
struct retro_callbacks cbs;
runloop_state_t *runloop_st = &runloop_state;
if (!core_set_default_callbacks(&cbs))
return false;
runloop_st->current_core.retro_set_video_refresh(cbs.frame_cb);
runloop_st->current_core.retro_set_audio_sample(cbs.sample_cb);
runloop_st->current_core.retro_set_audio_sample_batch(cbs.sample_batch_cb);
runloop_st->current_core.retro_set_input_state(cbs.state_cb);
return true;
}
#endif
bool core_set_cheat(retro_ctx_cheat_info_t *info)
{
runloop_state_t *runloop_st = &runloop_state;
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
settings_t *settings = config_get_ptr();
bool run_ahead_enabled = false;
unsigned run_ahead_frames = 0;
bool run_ahead_secondary_instance = false;
bool want_runahead = false;
if (settings)
{
run_ahead_enabled = settings->bools.run_ahead_enabled;
run_ahead_frames = settings->uints.run_ahead_frames;
run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance;
want_runahead = run_ahead_enabled
&& (run_ahead_frames > 0)
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE);
#ifdef HAVE_NETWORKING
if (want_runahead)
want_runahead = !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
#endif
}
#endif
runloop_st->current_core.retro_cheat_set(info->index, info->enabled, info->code);
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
if ( (want_runahead)
&& (run_ahead_secondary_instance)
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE)
&& (secondary_core_ensure_exists(runloop_st, settings))
&& (runloop_st->secondary_core.retro_cheat_set))
runloop_st->secondary_core.retro_cheat_set(
info->index, info->enabled, info->code);
#endif
return true;
}
bool core_reset_cheat(void)
{
runloop_state_t *runloop_st = &runloop_state;
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
settings_t *settings = config_get_ptr();
bool run_ahead_enabled = false;
unsigned run_ahead_frames = 0;
bool run_ahead_secondary_instance = false;
bool want_runahead = false;
if (settings)
{
run_ahead_enabled = settings->bools.run_ahead_enabled;
run_ahead_frames = settings->uints.run_ahead_frames;
run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance;
want_runahead = run_ahead_enabled
&& (run_ahead_frames > 0)
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE);
#ifdef HAVE_NETWORKING
if (want_runahead)
want_runahead = !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
#endif
}
#endif
runloop_st->current_core.retro_cheat_reset();
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
if ( (want_runahead)
&& (run_ahead_secondary_instance)
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE)
&& (secondary_core_ensure_exists(runloop_st, settings))
&& (runloop_st->secondary_core.retro_cheat_reset))
runloop_st->secondary_core.retro_cheat_reset();
#endif
return true;
}
bool core_set_poll_type(unsigned type)
{
runloop_state_t *runloop_st = &runloop_state;
runloop_st->current_core.poll_type = type;
return true;
}
bool core_set_controller_port_device(retro_ctx_controller_info_t *pad)
{
runloop_state_t *runloop_st = &runloop_state;
input_driver_state_t *input_st = input_state_get_ptr();
if (!pad)
return false;
/* We are potentially 'connecting' a entirely different
* type of virtual input device, which may or may not
* support analog inputs. We therefore have to reset
* the 'analog input requested' flag for this port - but
* since port mapping is arbitrary/mutable, it is easiest
* to simply reset the flags for all ports.
* Correct values will be registered at the next call
* of 'input_state()' */
memset(&input_st->analog_requested, 0,
sizeof(input_st->analog_requested));
#if defined(HAVE_RUNAHEAD)
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
runahead_remember_controller_port_device(runloop_st, pad->port, pad->device);
#endif
#endif
runloop_st->current_core.retro_set_controller_port_device(pad->port, pad->device);
return true;
}
bool core_get_memory(retro_ctx_memory_info_t *info)
{
runloop_state_t *runloop_st = &runloop_state;
if (!info)
return false;
info->size = runloop_st->current_core.retro_get_memory_size(info->id);
info->data = runloop_st->current_core.retro_get_memory_data(info->id);
return true;
}
bool core_load_game(retro_ctx_load_content_info_t *load_info)
{
bool game_loaded = false;
video_driver_state_t *video_st = video_state_get_ptr();
runloop_state_t *runloop_st = &runloop_state;
video_st->frame_cache_data = NULL;
#ifdef HAVE_RUNAHEAD
runahead_set_load_content_info(runloop_st, load_info);
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
runahead_clear_controller_port_map(runloop_st);
#endif
#endif
set_save_state_in_background(false);
if (load_info && load_info->special)
game_loaded = runloop_st->current_core.retro_load_game_special(
load_info->special->id, load_info->info, load_info->content->size);
else if (load_info && !string_is_empty(load_info->content->elems[0].data))
game_loaded = runloop_st->current_core.retro_load_game(load_info->info);
else if (content_get_flags() & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)
game_loaded = runloop_st->current_core.retro_load_game(NULL);
if (game_loaded)
{
/* If 'game_loaded' is true at this point, then
* core is actually running; register that any
* changes to global remap-related parameters
* should be reset once core is deinitialised */
input_state_get_ptr()->flags |= INP_FLAG_REMAPPING_CACHE_ACTIVE;
runloop_st->current_core.flags |= RETRO_CORE_FLAG_GAME_LOADED;
return true;
}
runloop_st->current_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
return false;
}
bool core_get_system_info(struct retro_system_info *system)
{
runloop_state_t *runloop_st = &runloop_state;
if (!system)
return false;
runloop_st->current_core.retro_get_system_info(system);
return true;
}
bool core_unserialize(retro_ctx_serialize_info_t *info)
{
runloop_state_t *runloop_st = &runloop_state;
if (!info || !runloop_st->current_core.retro_unserialize(info->data_const, info->size))
return false;
#ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, info);
#endif
#if HAVE_RUNAHEAD
command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL);
#endif
return true;
}
bool core_unserialize_special(retro_ctx_serialize_info_t *info)
{
bool ret;
runloop_state_t *runloop_st = &runloop_state;
if (!info)
return false;
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
ret = runloop_st->current_core.retro_unserialize(info->data_const, info->size);
runloop_st->flags &= ~RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
#ifdef HAVE_NETWORKING
if (ret)
netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, info);
#endif
return ret;
}
bool core_serialize(retro_ctx_serialize_info_t *info)
{
runloop_state_t *runloop_st = &runloop_state;
if (!info || !runloop_st->current_core.retro_serialize(info->data, info->size))
return false;
return true;
}
bool core_serialize_special(retro_ctx_serialize_info_t *info)
{
bool ret;
runloop_state_t *runloop_st = &runloop_state;
if (!info)
return false;
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
ret = runloop_st->current_core.retro_serialize(
info->data, info->size);
runloop_st->flags &= ~RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
return ret;
}
size_t core_serialize_size(void)
{
runloop_state_t *runloop_st = &runloop_state;
return runloop_st->current_core.retro_serialize_size();
}
size_t core_serialize_size_special(void)
{
size_t val;
runloop_state_t *runloop_st = &runloop_state;
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
val = runloop_st->current_core.retro_serialize_size();
runloop_st->flags &= ~RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
return val;
}
uint64_t core_serialization_quirks(void)
{
runloop_state_t *runloop_st = &runloop_state;
return runloop_st->current_core.serialization_quirks_v;
}
void core_reset(void)
{
runloop_state_t *runloop_st = &runloop_state;
video_driver_state_t *video_st = video_state_get_ptr();
video_st->frame_cache_data = NULL;
runloop_st->current_core.retro_reset();
}
void core_run(void)
{
runloop_state_t *runloop_st = &runloop_state;
struct retro_core_t *
current_core = &runloop_st->current_core;
const enum poll_type_override_t
core_poll_type_override = runloop_st->core_poll_type_override;
unsigned new_poll_type = (core_poll_type_override != POLL_TYPE_OVERRIDE_DONTCARE)
? (core_poll_type_override - 1)
: current_core->poll_type;
bool early_polling = new_poll_type == POLL_TYPE_EARLY;
bool late_polling = new_poll_type == POLL_TYPE_LATE;
#ifdef HAVE_NETWORKING
bool netplay_preframe = netplay_driver_ctl(
RARCH_NETPLAY_CTL_PRE_FRAME, NULL);
if (!netplay_preframe)
{
/* Paused due to netplay. We must poll and display something so that a
* netplay peer pausing doesn't just hang. */
input_driver_poll();
video_driver_cached_frame();
return;
}
#endif
if (early_polling)
input_driver_poll();
else if (late_polling)
current_core->flags &= ~RETRO_CORE_FLAG_INPUT_POLLED;
current_core->retro_run();
if ( late_polling
&& (!(current_core->flags & RETRO_CORE_FLAG_INPUT_POLLED)))
input_driver_poll();
#ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_POST_FRAME, NULL);
#endif
}
bool core_has_set_input_descriptor(void)
{
runloop_state_t *runloop_st = &runloop_state;
return ((runloop_st->current_core.flags &
RETRO_CORE_FLAG_HAS_SET_INPUT_DESCRIPTORS) > 0);
}
void runloop_path_set_basename(const char *path)
{
runloop_state_t *runloop_st = &runloop_state;
char *dst = NULL;
path_set(RARCH_PATH_CONTENT, path);
path_set(RARCH_PATH_BASENAME, path);
#ifdef HAVE_COMPRESSION
/* Removing extension is a bit tricky for compressed files.
* Basename means:
* /file/to/path/game.extension should be:
* /file/to/path/game
*
* Two things to consider here are: /file/to/path/ is expected
* to be a directory and "game" is a single file. This is used for
* states and srm default paths.
*
* For compressed files we have:
*
* /file/to/path/comp.7z#game.extension and
* /file/to/path/comp.7z#folder/game.extension
*
* The choice I take here is:
* /file/to/path/game as basename. We might end up in a writable
* directory then and the name of srm and states are meaningful.
*
*/
path_basedir_wrapper(runloop_st->runtime_content_path_basename);
if (!string_is_empty(runloop_st->runtime_content_path_basename))
fill_pathname_dir(runloop_st->runtime_content_path_basename, path, "", sizeof(runloop_st->runtime_content_path_basename));
#endif
if ((dst = strrchr(runloop_st->runtime_content_path_basename, '.')))
*dst = '\0';
}
void runloop_path_set_names(void)
{
runloop_state_t *runloop_st = &runloop_state;
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL))
{
size_t len = strlcpy(runloop_st->name.savefile,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.savefile));
strlcpy(runloop_st->name.savefile + len,
".srm",
sizeof(runloop_st->name.savefile) - len);
}
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL))
{
size_t len = strlcpy(
runloop_st->name.savestate,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.savestate));
strlcpy(runloop_st->name.savestate + len,
".state",
sizeof(runloop_st->name.savestate) - len);
}
#ifdef HAVE_BSV_MOVIE
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL))
{
size_t len = strlcpy(
runloop_st->name.replay,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.replay));
strlcpy(runloop_st->name.replay + len,
".replay",
sizeof(runloop_st->name.replay) - len);
}
#endif
#ifdef HAVE_CHEATS
if (!string_is_empty(runloop_st->runtime_content_path_basename))
{
size_t len = strlcpy(
runloop_st->name.cheatfile,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.cheatfile));
strlcpy(runloop_st->name.cheatfile + len,
".cht",
sizeof(runloop_st->name.cheatfile) - len);
}
#endif
}
void runloop_path_set_redirect(settings_t *settings,
const char *old_savefile_dir,
const char *old_savestate_dir)
{
char content_dir_name[PATH_MAX_LENGTH];
char new_savefile_dir[PATH_MAX_LENGTH];
char new_savestate_dir[PATH_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
struct retro_system_info *system = &runloop_st->system.info;
bool sort_savefiles_enable = settings->bools.sort_savefiles_enable;
bool sort_savefiles_by_content_enable = settings->bools.sort_savefiles_by_content_enable;
bool sort_savestates_enable = settings->bools.sort_savestates_enable;
bool sort_savestates_by_content_enable = settings->bools.sort_savestates_by_content_enable;
bool savefiles_in_content_dir = settings->bools.savefiles_in_content_dir;
bool savestates_in_content_dir = settings->bools.savestates_in_content_dir;
content_dir_name[0] = '\0';
/* Initialize current save directories
* with the values from the config. */
strlcpy(new_savefile_dir, old_savefile_dir, sizeof(new_savefile_dir));
strlcpy(new_savestate_dir, old_savestate_dir, sizeof(new_savestate_dir));
/* Get content directory name, if per-content-directory
* saves/states are enabled */
if ( (sort_savefiles_by_content_enable
|| sort_savestates_by_content_enable)
&& !string_is_empty(runloop_st->runtime_content_path_basename))
fill_pathname_parent_dir_name(content_dir_name,
runloop_st->runtime_content_path_basename,
sizeof(content_dir_name));
if (system && !string_is_empty(system->library_name))
{
#ifdef HAVE_MENU
if (!string_is_equal(system->library_name,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE)))
#endif
{
/* Per-core and/or per-content-directory saves */
if (( sort_savefiles_enable
|| sort_savefiles_by_content_enable)
&& !string_is_empty(old_savefile_dir))
{
/* Append content directory name to save location */
if (sort_savefiles_by_content_enable)
fill_pathname_join_special(
new_savefile_dir,
old_savefile_dir,
content_dir_name,
sizeof(new_savefile_dir));
/* Append library_name to the save location */
if (sort_savefiles_enable)
fill_pathname_join(
new_savefile_dir,
new_savefile_dir,
system->library_name,
sizeof(new_savefile_dir));
/* If path doesn't exist, try to create it,
* if everything fails revert to the original path. */
if (!path_is_directory(new_savefile_dir))
if (!path_mkdir(new_savefile_dir))
{
RARCH_LOG("%s %s\n",
msg_hash_to_str(MSG_REVERTING_SAVEFILE_DIRECTORY_TO),
old_savefile_dir);
strlcpy(new_savefile_dir, old_savefile_dir, sizeof(new_savefile_dir));
}
}
/* Per-core and/or per-content-directory savestates */
if ((sort_savestates_enable || sort_savestates_by_content_enable)
&& !string_is_empty(old_savestate_dir))
{
/* Append content directory name to savestate location */
if (sort_savestates_by_content_enable)
fill_pathname_join_special(
new_savestate_dir,
old_savestate_dir,
content_dir_name,
sizeof(new_savestate_dir));
/* Append library_name to the savestate location */
if (sort_savestates_enable)
fill_pathname_join(
new_savestate_dir,
new_savestate_dir,
system->library_name,
sizeof(new_savestate_dir));
/* If path doesn't exist, try to create it.
* If everything fails, revert to the original path. */
if (!path_is_directory(new_savestate_dir))
if (!path_mkdir(new_savestate_dir))
{
RARCH_LOG("%s %s\n",
msg_hash_to_str(MSG_REVERTING_SAVESTATE_DIRECTORY_TO),
old_savestate_dir);
strlcpy(new_savestate_dir,
old_savestate_dir,
sizeof(new_savestate_dir));
}
}
}
}
/* Set savefile directory if empty to content directory */
if (string_is_empty(new_savefile_dir) || savefiles_in_content_dir)
{
strlcpy(new_savefile_dir,
runloop_st->runtime_content_path_basename,
sizeof(new_savefile_dir));
path_basedir(new_savefile_dir);
if (string_is_empty(new_savefile_dir))
RARCH_LOG("Cannot resolve save file path.\n");
else if (sort_savefiles_enable || sort_savefiles_by_content_enable)
RARCH_LOG("Saving files in content directory is set. This overrides other save file directory settings.\n");
}
/* Set savestate directory if empty based on content directory */
if (string_is_empty(new_savestate_dir) || savestates_in_content_dir)
{
strlcpy(new_savestate_dir,
runloop_st->runtime_content_path_basename,
sizeof(new_savestate_dir));
path_basedir(new_savestate_dir);
if (string_is_empty(new_savestate_dir))
RARCH_LOG("Cannot resolve save state file path.\n");
else if (sort_savestates_enable || sort_savestates_by_content_enable)
RARCH_LOG("Saving save states in content directory is set. This overrides other save state file directory settings.\n");
}
#ifdef HAVE_NETWORKING
/* Special save directory for netplay clients. */
if ( netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)
&& !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL))
{
fill_pathname_join(new_savefile_dir, new_savefile_dir, ".netplay",
sizeof(new_savefile_dir));
if (!path_is_directory(new_savefile_dir) &&
!path_mkdir(new_savefile_dir))
path_basedir(new_savefile_dir);
}
#endif
if (system && !string_is_empty(system->library_name))
{
bool savefile_is_dir = path_is_directory(new_savefile_dir);
bool savestate_is_dir = path_is_directory(new_savestate_dir);
if (savefile_is_dir)
strlcpy(runloop_st->name.savefile, new_savefile_dir,
sizeof(runloop_st->name.savefile));
else
savefile_is_dir = path_is_directory(runloop_st->name.savefile);
if (savestate_is_dir)
{
strlcpy(runloop_st->name.savestate, new_savestate_dir,
sizeof(runloop_st->name.savestate));
strlcpy(runloop_st->name.replay, new_savestate_dir,
sizeof(runloop_st->name.replay));
}
else
savestate_is_dir = path_is_directory(runloop_st->name.savestate);
if (savefile_is_dir)
{
fill_pathname_dir(runloop_st->name.savefile,
!string_is_empty(runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: system->library_name,
FILE_PATH_SRM_EXTENSION,
sizeof(runloop_st->name.savefile));
RARCH_LOG("[Overrides]: %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
runloop_st->name.savefile);
}
if (savestate_is_dir)
{
fill_pathname_dir(runloop_st->name.savestate,
!string_is_empty(runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: system->library_name,
FILE_PATH_STATE_EXTENSION,
sizeof(runloop_st->name.savestate));
fill_pathname_dir(runloop_st->name.replay,
!string_is_empty(runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: system->library_name,
FILE_PATH_BSV_EXTENSION,
sizeof(runloop_st->name.replay));
RARCH_LOG("[Overrides]: %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVESTATE_TO),
runloop_st->name.savestate);
}
#ifdef HAVE_CHEATS
if (path_is_directory(runloop_st->name.cheatfile))
{
fill_pathname_dir(runloop_st->name.cheatfile,
!string_is_empty(runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: system->library_name,
FILE_PATH_CHT_EXTENSION,
sizeof(runloop_st->name.cheatfile));
RARCH_LOG("[Overrides]: %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_CHEATFILE_TO),
runloop_st->name.cheatfile);
}
#endif
}
dir_set(RARCH_DIR_CURRENT_SAVEFILE, new_savefile_dir);
dir_set(RARCH_DIR_CURRENT_SAVESTATE, new_savestate_dir);
}
void runloop_path_deinit_subsystem(void)
{
runloop_state_t *runloop_st = &runloop_state;
if (runloop_st->subsystem_fullpaths)
string_list_free(runloop_st->subsystem_fullpaths);
runloop_st->subsystem_fullpaths = NULL;
}
void runloop_path_set_special(char **argv, unsigned num_content)
{
unsigned i;
char str[PATH_MAX_LENGTH];
union string_list_elem_attr attr;
bool is_dir = false;
struct string_list subsystem_paths = {0};
runloop_state_t *runloop_st = &runloop_state;
const char *savestate_dir = runloop_st->savestate_dir;
/* First content file is the significant one. */
runloop_path_set_basename(argv[0]);
string_list_initialize(&subsystem_paths);
runloop_st->subsystem_fullpaths = string_list_new();
attr.i = 0;
for (i = 0; i < num_content; i++)
{
string_list_append(runloop_st->subsystem_fullpaths, argv[i], attr);
strlcpy(str, argv[i], sizeof(str));
path_remove_extension(str);
string_list_append(&subsystem_paths, path_basename(str), attr);
}
str[0] = '\0';
string_list_join_concat(str, sizeof(str), &subsystem_paths, " + ");
string_list_deinitialize(&subsystem_paths);
/* We defer SRAM path updates until we can resolve it.
* It is more complicated for special content types. */
is_dir = path_is_directory(savestate_dir);
if (is_dir)
{
strlcpy(runloop_st->name.savestate, savestate_dir,
sizeof(runloop_st->name.savestate)); /* TODO/FIXME - why are we setting this string here but then later overwriting it later with fill_pathname_dir? */
strlcpy(runloop_st->name.replay, savestate_dir,
sizeof(runloop_st->name.replay)); /* TODO/FIXME - as above */
}
else
is_dir = path_is_directory(runloop_st->name.savestate);
if (is_dir)
{
fill_pathname_dir(runloop_st->name.savestate,
str,
".state",
sizeof(runloop_st->name.savestate));
fill_pathname_dir(runloop_st->name.replay,
str,
".replay",
sizeof(runloop_st->name.replay));
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVESTATE_TO),
runloop_st->name.savestate);
}
}