Files
RetroArch/runloop.c
T
LibretroAdmin 27ed906296 midi: a core's MIDI request lasts until the core is unloaded
A staged content load runs the new core's init and load on the
previous session's drivers, then rebuilds them. The core's
GET_MIDI_INTERFACE request now ends in runloop_event_deinit_core after
retro_deinit rather than in driver_uninit, so drivers_init reopens the
device on the rebuilt drivers for a core that asked during its load.

The content_load harness gains a lane for it: a core asking at load
has the output open after the load, the next core that does not ask
has it shut.
2026-10-26 13:45:44 +01:00

11579 lines
407 KiB
C

/* 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� Santoni
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2016-2019 - Andr�s Su�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/>.
*/
#include <memory/mem_stats.h>
#include "input/input_driver.h"
#ifdef HAVE_OPENXR
#include "input/common/input_openxr.h"
#endif
#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) && !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_RMP4
#include <formats/rh264.h>
#endif
#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 <retro_assert.h>
#include <queues/message_queue.h>
#include <lists/dir_list.h>
#ifdef __MACH__
#include <TargetConditionals.h>
#endif
#ifdef __EMSCRIPTEN__
#include "frontend/drivers/platform_emscripten.h"
#endif
#ifdef HAVE_LIBNX
#include <switch.h>
#include "switch_performance_profiles.h"
#endif
#if defined(ANDROID)
#include "play_feature_delivery/play_feature_delivery.h"
#include "frontend/drivers/platform_unix.h"
#include "input/drivers/android_input.h"
#endif
/* Android publishes the device's GLES level as a system property;
* SET_HW_RENDER grants GLES versions against it. */
#if defined(ANDROID) && !defined(RUNLOOP_GLES_VERSION_PROPERTY)
#define RUNLOOP_GLES_VERSION_PROPERTY
#endif
#ifdef RUNLOOP_GLES_VERSION_PROPERTY
#include <sys/system_properties.h>
#endif
#if defined(ANDROID) && defined(HAVE_SAF)
bool android_get_vfs_authorized_locations(
struct retro_vfs_authorized_locations *locations);
#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"
#include "record/record_driver.h"
#include "msg_hash_lbl_str.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_NGX
#include "gfx/video_upscaler.h"
#endif
#ifdef HAVE_MODELINE
#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 TARGET_OS_IPHONE
#include "JITSupport.h"
#define EXEC_MEM_MAX_ALLOCS 64
static struct
{
void *rx;
void *rw;
size_t size;
unsigned mode;
} exec_mem_ledger[EXEC_MEM_MAX_ALLOCS];
static unsigned exec_mem_ledger_count = 0;
static void exec_mem_ledger_add(void *rx, void *rw, size_t size, unsigned mode)
{
if (exec_mem_ledger_count < EXEC_MEM_MAX_ALLOCS)
{
exec_mem_ledger[exec_mem_ledger_count].rx = rx;
exec_mem_ledger[exec_mem_ledger_count].rw = rw;
exec_mem_ledger[exec_mem_ledger_count].size = size;
exec_mem_ledger[exec_mem_ledger_count].mode = mode;
exec_mem_ledger_count++;
}
}
/* Either mapping identifies the block: a core juggling both does not always
* know which one it is holding, and we are the side with the table. */
static bool exec_mem_ledger_remove(void *ptr)
{
for (unsigned i = 0; i < exec_mem_ledger_count; i++)
{
if (exec_mem_ledger[i].rx == ptr || exec_mem_ledger[i].rw == ptr)
{
exec_mem_free(exec_mem_ledger[i].rx, exec_mem_ledger[i].rw,
exec_mem_ledger[i].size,
exec_mem_ledger[i].mode == RETRO_EXEC_MEM_MODE_DUAL_MAP);
exec_mem_ledger[i] = exec_mem_ledger[--exec_mem_ledger_count];
return true;
}
}
return false;
}
static void exec_mem_ledger_free_all(void)
{
for (unsigned i = 0; i < exec_mem_ledger_count; i++)
exec_mem_free(exec_mem_ledger[i].rx, exec_mem_ledger[i].rw,
exec_mem_ledger[i].size,
exec_mem_ledger[i].mode == RETRO_EXEC_MEM_MODE_DUAL_MAP);
exec_mem_ledger_count = 0;
exec_mem_pool_reset();
}
#endif
#if HAVE_GAME_AI
#include "ai/game_ai.h"
/* Context for the cached_frame_read callback that dispatches into
* game_ai_think. Lets the per-frame GameAI processing pull from
* the cached frame through the lifetime-safe API instead of
* touching frame_cache_data directly. */
struct game_ai_think_ctx
{
bool override_p1;
bool override_p2;
bool show_debug;
enum retro_pixel_format pix_fmt;
};
static void runloop_game_ai_think_cb(void *userdata,
const void *data,
unsigned dims, size_t pitch)
{
struct game_ai_think_ctx *ctx = (struct game_ai_think_ctx*)userdata;
if (!ctx)
return;
game_ai_think(
ctx->override_p1,
ctx->override_p2,
ctx->show_debug,
data,
VIDEO_SCALE_W(dims), VIDEO_SCALE_H(dims), pitch,
ctx->pix_fmt);
}
#endif
#define SHADER_FILE_WATCH_DELAY_MSEC 500
#define QUIT_DELAY_USEC 3 * 1000000 /* 3 seconds */
#ifdef HAVE_COMPRESSION
#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_WEBMPLAYER
#define SYMBOL_MEDIA(x) current_core->x = libretro_media_##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;
/* Defined here, before its first user: the SET_MESSAGE_EXT STATUS
* path in the environment callback defers through this machinery,
* and it sits far above the message-queue code where the rest of
* the deferral lives. */
struct runloop_deferred_msg
{
mpsc_stack_node_t link; /* first: the stack's, from push to drain */
char *msg;
char *title;
size_t len;
unsigned prio;
unsigned duration;
enum message_queue_icon icon;
enum message_queue_category category;
bool flush;
/* A deferred RETRO_MESSAGE_TYPE_STATUS: drained into
* core_status_msg under its priority-overwrite rule rather than
* pushed onto the message queue. Raised only by a core calling
* SET_MESSAGE_EXT off the main thread, which the libretro API
* does not allow but rogue cores do. */
bool core_status;
};
#ifdef HAVE_THREADS
/* True when the caller is not the thread the message queue belongs
* to; such a caller hands its message to the deferral stack and the
* main thread replays it at the top of the next iterate. */
static bool runloop_msg_queue_off_main(runloop_state_t *runloop_st)
{
return runloop_st->msg_queue_main_id
&& sthread_get_current_thread_id()
!= runloop_st->msg_queue_main_id;
}
#endif
/* GLOBAL POINTER GETTERS */
runloop_state_t *runloop_state_get_ptr(void)
{
return &runloop_state;
}
void runloop_frame_work_set(unsigned bit, bool on)
{
if (on)
runloop_state.frame_work |= bit;
else
runloop_state.frame_work &= ~bit;
}
bool runloop_is_content_closing(void)
{
return runloop_state.content_closing;
}
bool runloop_is_content_switching(void)
{
return runloop_state.content_switching;
}
bool state_manager_frame_is_reversed(void)
{
#ifdef HAVE_REWIND
/* Acquire on the atomic mirror, not the flags word: callers
* include the video thread's FrameDirection reads and the task
* worker, while check_rewind writes on main. */
return retro_atomic_load_acquire_int(
&runloop_state.rewind_st.frame_reversed_atomic) != 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)
{
unsigned ptr = runloop_state.perf_ptr_libretro;
if (perf->registered || ptr >= MAX_COUNTERS)
return;
runloop_state.perf_counters_libretro[ptr] = perf;
runloop_state.perf_ptr_libretro = ptr + 1;
perf->registered = true;
}
void runloop_log_counters(
struct retro_perf_counter **counters, unsigned num)
{
unsigned i;
for (i = 0; i < num; i++)
{
const struct retro_perf_counter *c = counters[i];
if (c->call_cnt)
RARCH_LOG(PERF_LOG_FMT,
c->ident,
(uint64_t)c->total / (uint64_t)c->call_cnt,
(uint64_t)c->call_cnt);
}
}
static void runloop_perf_log(void)
{
if (!runloop_state.perfcnt_enable)
return;
RARCH_LOG("[PERF] Performance counters (libretro):\n");
runloop_log_counters(runloop_state.perf_counters_libretro,
runloop_state.perf_ptr_libretro);
}
/* ---- what a core reports of itself before it is loaded ----
*
* libretro_get_system_info() answers for a core that is not running
* - its name, extensions, whether it takes a path, whether it runs
* without content, which extensions it overrides - by opening the
* library, asking, and closing it again: the system loader's whole
* work, for a handful of values. The answers are the binary's own,
* so they are kept, keyed by the core file's size and modification
* time, and a core is opened only when its file is new or has
* changed. The record persists next to the core info cache.
*
* A core that declares subsystems is always opened: its declaration
* is a table of its own, which the probe copies out in full. */
/* Extension lists sized like the system info strings */
#define CORE_PROBE_EXTS_SIZE 256
enum core_probe_flags
{
CORE_PROBE_FLAG_NEED_FULLPATH = (1 << 0),
CORE_PROBE_FLAG_BLOCK_EXTRACT = (1 << 1),
CORE_PROBE_FLAG_NO_GAME = (1 << 2),
/* Declared subsystems: never answered from the record */
CORE_PROBE_FLAG_SUBSYSTEMS = (1 << 3),
/* The override lists overflowed, so they are not known */
CORE_PROBE_FLAG_OVERRIDES_CUT = (1 << 4),
/* size and mtime were read: the record may be served and kept */
CORE_PROBE_FLAG_STAMPED = (1 << 5)
};
typedef struct core_probe
{
int64_t size;
int64_t mtime;
char *path;
char library_name[NAME_MAX_LENGTH];
char library_version[64];
char valid_extensions[CORE_PROBE_EXTS_SIZE];
/* Extensions the core's content info overrides declare to take a
* path, and those declared to be read into memory, '|' separated;
* an extension in neither follows need_fullpath */
char fullpath_exts[CORE_PROBE_EXTS_SIZE];
char memory_exts[CORE_PROBE_EXTS_SIZE];
uint8_t flags;
} core_probe_t;
/* Filled by runloop_environ_cb_get_system_info() while a probe runs */
static core_probe_t *core_probe_capture = NULL;
static bool core_probe_ext_listed(const char *list, const char *ext,
size_t ext_len)
{
const char *p = list;
while (*p)
{
const char *end = p;
while (*end && *end != '|')
end++;
if ((size_t)(end - p) == ext_len)
{
size_t i;
for (i = 0; i < ext_len; i++)
if (tolower((unsigned char)p[i]) != tolower((unsigned char)ext[i]))
break;
if (i == ext_len)
return true;
}
p = *end ? end + 1 : end;
}
return false;
}
/* Records a content info override declaration the way
* content_file_override_set() reads it: an extension's first
* declaration is the one that counts. */
static void core_probe_capture_overrides(core_probe_t *probe,
const struct retro_system_content_info_override *overrides)
{
size_t i;
for (i = 0; overrides[i].extensions; i++)
{
const char *ptr = overrides[i].extensions;
char *dst = overrides[i].need_fullpath
? probe->fullpath_exts : probe->memory_exts;
while (*ptr)
{
const char *delim = ptr;
size_t _len, dst_len;
while (*delim && *delim != '|')
delim++;
_len = (size_t)(delim - ptr);
if ( _len
&& !core_probe_ext_listed(probe->fullpath_exts, ptr, _len)
&& !core_probe_ext_listed(probe->memory_exts, ptr, _len))
{
dst_len = strlen(dst);
if (dst_len + (dst_len ? 1 : 0) + _len >= CORE_PROBE_EXTS_SIZE)
probe->flags |= CORE_PROBE_FLAG_OVERRIDES_CUT;
else
{
if (dst_len)
dst[dst_len++] = '|';
memcpy(dst + dst_len, ptr, _len);
dst[dst_len + _len] = '\0';
}
}
ptr = *delim ? delim + 1 : delim;
}
}
}
static bool runloop_environ_cb_get_system_info(unsigned cmd, void *data)
{
runloop_state_t *runloop_st = &runloop_state;
rarch_system_info_t *sys_info = &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;
unsigned log_level = config_get_ptr()->uints.libretro_log_level;
bool do_debug_log = (log_level == RETRO_LOG_DEBUG);
runloop_st->subsystem_current_count = 0;
if (core_probe_capture)
core_probe_capture->flags |= CORE_PROBE_FLAG_SUBSYSTEMS;
RARCH_LOG("[Environ] SET_SUBSYSTEM_INFO.\n");
for (i = 0; info[i].ident; i++)
{
if (!do_debug_log)
continue;
RARCH_DBG("Subsystem #%u \"%s\", Ident: \"%s\".\n",
info[i].id,
info[i].desc,
info[i].ident);
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 (do_debug_log)
{
RARCH_DBG("Subsystems: %u.\n", size);
if (size > SUBSYSTEM_MAX_SUBSYSTEMS)
RARCH_WARN("Subsystems exceed subsystem max, clamping to %d.\n", SUBSYSTEM_MAX_SUBSYSTEMS);
}
if (sys_info)
{
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 ((subsys_info->desc && *subsys_info->desc))
free((char*)subsys_info->desc);
if ((subsys_info->ident && *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 (do_debug_log)
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 ((subsys_rom_info[j].desc && *subsys_rom_info[j].desc))
free((char*)
subsys_rom_info[j].desc);
if ((
subsys_rom_info[j].valid_extensions && *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;
}
case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE:
if (core_probe_capture && data)
core_probe_capture_overrides(core_probe_capture,
(const struct retro_system_content_info_override*)data);
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;
}
/* Whether the core path is resolved through symlinks before it is
* opened (and saved to history) */
static bool load_dynamic_core_resolves_symlinks(void)
{
#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) */
return !play_feature_delivery_enabled();
#else
return true;
#endif
}
/* ---- the core's library, opened ahead of the load ----
*
* Mapping a core and running its constructors is the system loader's
* work, sized by the core (MAME and friends are hundreds of
* megabytes), so a staged load opens it on the task worker while the
* frame loop goes on, and the core stage takes the handle. Only the
* open moves: retro_init, the content read and retro_load_game stay on
* the thread that will call retro_run.
*
* The open starts only once the previous core's handle is closed (the
* load's close stage): dlopen of a path already loaded returns the
* instance already there, so opening the new core before the old one
* is released would hand back the old core's statics on a same-core
* reload. The slot is touched on the main thread (begin, take, the
* callback); the worker touches only its own task state. A generation
* counter discards a result whose load was superseded by teardown. */
#if defined(HAVE_DYNAMIC) && defined(HAVE_THREADS)
typedef struct core_preload_handle
{
char *path;
dylib_t lib;
unsigned generation;
} core_preload_handle_t;
static char *core_preload_slot_path = NULL; /* what the slot holds */
static dylib_t core_preload_slot_lib = NULL; /* NULL if it would not open */
static bool core_preload_slot_done = false;
static bool core_preload_pending = false;
static unsigned core_preload_generation = 0;
static void core_preload_task_handler(retro_task_t *task)
{
core_preload_handle_t *h = task ? (core_preload_handle_t*)task->state : NULL;
/* The open: the one thing off the main thread */
if (h && h->path)
h->lib = dylib_load(h->path);
if (task)
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
}
static void core_preload_task_cb(retro_task_t *task,
void *task_data, void *user_data, const char *err)
{
core_preload_handle_t *h = task ? (core_preload_handle_t*)task->state : NULL;
(void)task_data; (void)user_data; (void)err;
if (!h)
return;
core_preload_pending = false;
/* Superseded while it was opening: the handle is nobody's now */
if (h->generation != core_preload_generation)
{
if (h->lib)
dylib_close(h->lib);
h->lib = NULL;
return;
}
core_preload_slot_lib = h->lib;
h->lib = NULL;
core_preload_slot_done = true; /* lib NULL here means it would not open */
}
static void core_preload_task_free(retro_task_t *task)
{
core_preload_handle_t *h = task ? (core_preload_handle_t*)task->state : NULL;
if (!h)
return;
if (h->lib)
dylib_close(h->lib);
if (h->path)
free(h->path);
free(h);
task->state = NULL;
}
/* Main thread. Drops an unconsumed handle and tells an in-flight open
* to discard its result. */
static void core_preload_reset(void)
{
if (core_preload_slot_lib)
{
dylib_close(core_preload_slot_lib);
core_preload_slot_lib = NULL;
}
if (core_preload_slot_path)
{
free(core_preload_slot_path);
core_preload_slot_path = NULL;
}
core_preload_slot_done = false;
core_preload_generation++; /* invalidate any open still running */
}
/* Main thread. Hands over the opened handle when the slot holds one
* for @path, else NULL - the caller opens it itself. */
static dylib_t core_preload_take(const char *path)
{
dylib_t lib;
if ( !core_preload_slot_done
|| !core_preload_slot_path
|| !string_is_equal(core_preload_slot_path, path ? path : ""))
return NULL;
lib = core_preload_slot_lib;
core_preload_slot_lib = NULL;
core_preload_reset();
return lib;
}
bool runloop_core_preload_begin(void)
{
retro_task_t *task = NULL;
core_preload_handle_t *h = NULL;
const char *path = path_get(RARCH_PATH_CORE);
/* Only a plain core named by the push, and only with a worker to
* open it on: inline, the open would run on this thread at the next
* check, which is the synchronous path. */
if ( !path || !*path
|| !(runloop_state.flags & RUNLOOP_FLAG_HAS_SET_CORE)
|| runloop_state.explicit_current_core_type != CORE_TYPE_PLAIN
|| !task_queue_is_threaded()
|| core_preload_pending)
return false;
core_preload_reset();
/* The path load_dynamic_core() will open: resolved the same way,
* in place, so the core stage finds the handle under it */
path_resolve_realpath(path_get_ptr(RARCH_PATH_CORE),
path_get_realsize(RARCH_PATH_CORE),
load_dynamic_core_resolves_symlinks());
path = path_get(RARCH_PATH_CORE);
if ( !(task = task_init())
|| !(h = (core_preload_handle_t*)calloc(1, sizeof(*h))))
{
if (task)
free(task);
return false;
}
h->path = strdup(path);
h->generation = core_preload_generation;
core_preload_slot_path = strdup(path);
task->handler = core_preload_task_handler;
task->state = h;
task->callback = core_preload_task_cb;
task->cleanup = core_preload_task_free;
task->flags |= RETRO_TASK_FLG_MUTE;
core_preload_pending = true;
task_queue_push(task);
return true;
}
bool runloop_core_preload_ready(void)
{
return !core_preload_pending;
}
void runloop_core_preload_cancel(void)
{
core_preload_reset();
}
#endif
static dylib_t load_dynamic_core(const char *path, char *s,
size_t len)
{
bool resolve_symlinks = load_dynamic_core_resolves_symlinks();
/* 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(s, len, resolve_symlinks);
#if defined(HAVE_DYNAMIC) && defined(HAVE_THREADS)
{
dylib_t pre = core_preload_take(path);
if (pre)
return pre;
}
#endif
return dylib_load(path);
}
/* @live is set when @path opened the library already loaded as the
* current core: its retro_set_environment has been called and is not
* called again mid-session, so what it declared is taken from the
* running state instead. */
static dylib_t libretro_get_system_info_lib(const char *path,
struct retro_system_info *sysinfo, bool *load_no_content,
bool *live)
{
dylib_t lib = dylib_load(path);
void (*proc)(struct retro_system_info*);
*live = false;
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(sysinfo);
if (lib == runloop_state.lib_handle)
{
*live = true;
if (load_no_content)
*load_no_content = runloop_state.system.load_no_content;
}
else 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
#if !(defined(HAVE_DYNAMIC) && defined(HAVE_THREADS))
/* No worker to open the library on: the core stage loads it
* (or links it statically), so the preload stage has nothing to do. */
bool runloop_core_preload_begin(void) { return false; }
bool runloop_core_preload_ready(void) { return true; }
void runloop_core_preload_cancel(void) { }
#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);
/* Update play count */
runtime_log->play_count++;
/* Update state slot */
runtime_log->state_slot = config_get_ptr()->ints.state_slot;
/* 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()
&& runloop_st->core_runtime_usec > 0)
{
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),
"[Runtime] 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));
/* Reset entry state slot, to prevent any possibility
* of a stale slot leaking into subsequently loaded
* content */
runloop_st->entry_state_slot = -1;
}
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)
{
size_t len = strlen(video_ident);
switch (type)
{
case RETRO_HW_CONTEXT_VULKAN:
if (!(len == 6 && memcmp(video_ident, "vulkan", 6) == 0))
return false;
break;
#if defined(HAVE_OPENGL_CORE)
case RETRO_HW_CONTEXT_OPENGL_CORE:
if (!(len == 6 && memcmp(video_ident, "glcore", 6) == 0))
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 ( !(len == 2 && memcmp(video_ident, "gl", 2) == 0)
&& !(len == 6 && memcmp(video_ident, "glcore", 6) == 0))
return false;
break;
case RETRO_HW_CONTEXT_D3D10:
if (!(len == 5 && memcmp(video_ident, "d3d10", 5) == 0))
return false;
break;
case RETRO_HW_CONTEXT_D3D11:
if (!(len == 5 && memcmp(video_ident, "d3d11", 5) == 0))
return false;
break;
case RETRO_HW_CONTEXT_D3D12:
if (!(len == 5 && memcmp(video_ident, "d3d12", 5) == 0))
return false;
break;
case RETRO_HW_CONTEXT_D3D9:
if (!(len == 9 && memcmp(video_ident, "d3d9_hlsl", 9) == 0))
return false;
break;
default:
break;
}
}
return true;
}
#if defined(HAVE_OPENGLES) && (defined(HAVE_OPENGLES2) || defined(HAVE_OPENGLES3))
/* The highest GLES version a core may be granted, as major * 100 +
* minor. A device that publishes its GLES level is taken at its word,
* so a core walking down a version list stops at one the context can
* really give; otherwise the build decides. A GLES2 build already
* grants OPENGLES3, so 3.0 is its ceiling as well. */
static unsigned runloop_gles_version_ceiling(void)
{
#if defined(HAVE_OPENGLES3_2)
unsigned ceiling = 302;
#elif defined(HAVE_OPENGLES3_1)
unsigned ceiling = 301;
#else
unsigned ceiling = 300;
#endif
#ifdef RUNLOOP_GLES_VERSION_PROPERTY
char value[PROP_VALUE_MAX];
/* ro.opengles.version is major << 16 | minor */
if (__system_property_get("ro.opengles.version", value) > 0)
{
unsigned long v = strtoul(value, NULL, 10);
if (v >> 16)
ceiling = (unsigned)((v >> 16) * 100 + (v & 0xFFFF));
}
#endif
return ceiling;
}
#endif
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("[HW] Requesting no HW context.\n");
break;
case RETRO_HW_CONTEXT_VULKAN:
#ifdef HAVE_VULKAN
RARCH_LOG("[HW] Requesting Vulkan context.\n");
break;
#else
RARCH_WARN("[HW] 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:
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
{
unsigned ceiling = runloop_gles_version_ceiling();
if (type == RETRO_HW_CONTEXT_OPENGLES2)
{
major = 2;
minor = 0;
}
else if (type == RETRO_HW_CONTEXT_OPENGLES3)
{
major = 3;
minor = 0;
}
if (major * 100 + minor > ceiling)
{
RARCH_WARN("[HW] Requesting OpenGLES%u.%u context, but "
"only OpenGLES%u.%u is available. Cannot use HW "
"context.\n", major, minor,
ceiling / 100, ceiling % 100);
return false;
}
}
RARCH_LOG("[HW] Requesting OpenGLES%u.%u context.\n",
major, minor);
break;
#endif
case RETRO_HW_CONTEXT_OPENGL:
case RETRO_HW_CONTEXT_OPENGL_CORE:
RARCH_WARN("[HW] 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_WARN("[HW] 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_WARN("[HW] 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("[HW] 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("[HW] Requesting core OpenGL context (%u.%u).\n",
major, minor);
break;
#endif
#if defined(HAVE_D3D11)
case RETRO_HW_CONTEXT_D3D11:
RARCH_LOG("[HW] Requesting D3D11 context.\n");
break;
#endif
#ifdef HAVE_D3D10
case RETRO_HW_CONTEXT_D3D10:
RARCH_LOG("[HW] Requesting D3D10 context.\n");
break;
#endif
#ifdef HAVE_D3D12
case RETRO_HW_CONTEXT_D3D12:
RARCH_LOG("[HW] Requesting D3D12 context.\n");
break;
#endif
#if defined(HAVE_D3D9) && defined(HAVE_HLSL)
case RETRO_HW_CONTEXT_D3D9:
RARCH_LOG("[HW] Requesting D3D9 context.\n");
break;
#endif
default:
RARCH_WARN("[HW] Requesting context type %d, which this build "
"does not support. Cannot use HW context.\n", (int)type);
return false;
}
return true;
}
static void libretro_log_cb(
enum retro_log_level level,
const char *fmt, ...)
{
va_list vp;
unsigned libretro_log_level = config_get_ptr()->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;
/* Only compiled on 64-bit targets; avoids a runtime branch per call */
#if SIZE_MAX > 0xFFFFFFFFUL
n |= n >> 32;
#endif
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 (path_core_options && *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 */
/* config_file_new_from_path_to_string() returns NULL for a
* missing or unreadable file, and the empty-config fallback
* below already handles NULL, so a path_is_valid() stat first
* would only repeat the open's own lookup. */
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);
/* Log what happened, not what was attempted: this claimed
* a successful save before the write and then discarded
* its result, so a failed one told the user their options
* were saved. */
if (config_file_write(conf_tmp, path_core_options, true))
RARCH_LOG("[Core] Saved %s-specific core options to \"%s\".\n",
game_options_active ? "game" : "folder", path_core_options);
else
RARCH_ERR("[Core] Failed to save %s-specific core options to \"%s\".\n",
game_options_active ? "game" : "folder", path_core_options);
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);
if (config_file_write(core_options->conf, path, true))
RARCH_LOG("[Core] Saved core options file to \"%s\".\n", path);
else
RARCH_ERR("[Core] Failed to save core options file to \"%s\".\n",
path);
}
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[DIR_MAX_LENGTH];
config_directory[0] = '\0';
if ( !s
|| (len < 1)
|| (!core_name || !*core_name)
|| (!game_name || !*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_folder_options(char *s, size_t len, bool mkdir)
{
const char *game_path = path_get(RARCH_PATH_BASENAME);
if (game_path && *game_path)
{
char folder_name[DIR_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
const char *core_name = runloop_st->system.info.library_name;
fill_pathname_parent_dir_name(folder_name,
game_path, sizeof(folder_name));
return validate_per_core_options(s, len, mkdir,
core_name, folder_name);
}
return false;
}
/**
* 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(
bool game_specific_options,
bool per_core_options,
const char *path_core_options,
char *s, size_t len,
char *s2, size_t len2)
{
runloop_state_t *runloop_st = &runloop_state;
/* Check whether game-specific options exist */
if ( game_specific_options
&& validate_per_core_options(s, len, false,
runloop_st->system.info.library_name,
path_basename_nocompression(path_get(RARCH_PATH_BASENAME)))
&& path_is_valid(s))
{
RARCH_LOG("[Core] %s \"%s\".\n",
msg_hash_to_str(MSG_GAME_SPECIFIC_CORE_OPTIONS_FOUND_AT),
s);
/* Notify system that we have a valid core options
* override */
path_set(RARCH_PATH_CORE_OPTIONS, s);
runloop_st->flags &= ~RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
}
/* Check whether folder-specific options exist */
else if ( game_specific_options
&& validate_folder_options(s, len, false)
&& path_is_valid(s))
{
RARCH_LOG("[Core] %s \"%s\".\n",
msg_hash_to_str(MSG_FOLDER_SPECIFIC_CORE_OPTIONS_FOUND_AT),
s);
/* Notify system that we have a valid core options
* override */
path_set(RARCH_PATH_CORE_OPTIONS, s);
runloop_st->flags &= ~RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
}
else
{
char global_options_path[PATH_MAX_LENGTH];
char per_core_options_path[PATH_MAX_LENGTH];
bool per_core_options_exist = false;
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)
{
if (path_core_options && *path_core_options)
strlcpy(global_options_path,
path_core_options, 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(s, per_core_options_path, len);
if (!per_core_options_exist)
strlcpy(s2, global_options_path, len2);
}
else
strlcpy(s, 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(
bool categories_enabled,
bool game_specific_options,
bool global_core_options,
const char *path_core_options,
const struct retro_core_options_v2 *options_v2)
{
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(
game_specific_options,
global_core_options,
path_core_options,
options_path, sizeof(options_path),
src_options_path, sizeof(src_options_path));
if (*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(
bool game_specific_options,
bool global_core_options,
const char *path_core_options,
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(
game_specific_options,
global_core_options,
path_core_options,
options_path, sizeof(options_path),
src_options_path, sizeof(src_options_path));
if (*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)
{
enum message_queue_category category;
/* Get duration in frames */
double fps = (av_info && (av_info->timing.fps > 0)) ? av_info->timing.fps : 60.0;
unsigned duration_frames = (unsigned)((fps * (float)msg->duration / 1000.0f) + 0.5f);
/* 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;
}
/* Note: Do not flush the message queue here - a core
* may need to send multiple notifications simultaneously */
runloop_msg_queue_push(msg->msg, strlen(msg->msg),
msg->priority, duration_frames,
false, NULL, MESSAGE_QUEUE_ICON_DEFAULT,
category);
}
static void core_performance_counter_start(
struct retro_perf_counter *perf)
{
if (runloop_state.perfcnt_enable)
{
perf->call_cnt++;
perf->start = cpu_features_get_perf_counter();
}
}
static void core_performance_counter_stop(struct retro_perf_counter *perf)
{
if (runloop_state.perfcnt_enable)
perf->total += cpu_features_get_perf_counter() - perf->start;
}
#ifdef HAVE_OPENXR
/* The pose calls answer while the headset tracks. A core asking before
* a sampled frame is sampled now, and before each run from then on. */
static bool runloop_vr_poses(runloop_state_t *runloop_st)
{
if (!(video_driver_xr_headset() & VIDEO_XR_HEADSET_TRACKING))
return false;
if (!runloop_st->core_vr_content)
{
runloop_st->core_vr_content = true;
video_driver_vr_sample_tracking();
}
return true;
}
#endif
#ifdef HAVE_RMP4
static void *runloop_video_decode_open(void)
{
rh264_video *v = rh264_video_open();
rh264_video_set_hw_readback(v, 1);
return v;
}
static int runloop_video_decode_frame(rh264_video *v,
struct retro_video_decode_frame *frame)
{
int i, stride, w, h;
if (!frame || rh264_video_bit_depth(v) != 8)
return -1;
memset(frame, 0, sizeof(*frame));
for (i = 0; i < 3; i++)
{
frame->planes[i] = rh264_video_plane(v, i, &stride, &w, &h);
if (!frame->planes[i] || stride <= 0 || w <= 0 || h <= 0)
return -1;
frame->strides[i] = (unsigned)stride;
if (!i)
{
frame->width = (unsigned)w;
frame->height = (unsigned)h;
}
else if ((unsigned)w != (frame->width + 1) / 2
|| (unsigned)h != (frame->height + 1) / 2)
return -1;
}
frame->hardware = rh264_video_is_hw(v) != 0;
return 1;
}
static int runloop_video_decode(void *decoder, const uint8_t *data,
size_t size, struct retro_video_decode_frame *frame)
{
rh264_video *v = (rh264_video*)decoder;
int result;
rh264_video_set_deferred_readback(v, 0);
result = rh264_video_decode(v, data, size);
return result == 1 ? runloop_video_decode_frame(v, frame) : result;
}
static int runloop_video_decode_drain(void *decoder,
struct retro_video_decode_frame *frame)
{
rh264_video *v = (rh264_video*)decoder;
rh264_video_set_deferred_readback(v, 0);
return rh264_video_drain(v) == 0 ? runloop_video_decode_frame(v, frame) : 0;
}
static int runloop_video_decode_metadata(rh264_video *v,
struct retro_video_decode_frame *frame)
{
int width, height;
if (!frame || rh264_video_bit_depth(v) != 8
|| rh264_video_frame_size(v, &width, &height) != 0)
return -1;
memset(frame, 0, sizeof(*frame));
frame->width = (unsigned)width;
frame->height = (unsigned)height;
frame->hardware = rh264_video_is_hw(v) != 0;
return 1;
}
static int runloop_video_decode_ex(void *decoder, const uint8_t *data,
size_t size, struct retro_video_decode_frame *frame, unsigned flags)
{
rh264_video *v = (rh264_video*)decoder;
int result;
if (!v || !frame || (flags & ~RETRO_VIDEO_DECODE_DEFER_PLANES))
return -1;
rh264_video_set_deferred_readback(v, flags & RETRO_VIDEO_DECODE_DEFER_PLANES);
result = rh264_video_decode(v, data, size);
if (result != 1)
return result;
return flags ? runloop_video_decode_metadata(v, frame)
: runloop_video_decode_frame(v, frame);
}
static int runloop_video_decode_drain_ex(void *decoder,
struct retro_video_decode_frame *frame, unsigned flags)
{
rh264_video *v = (rh264_video*)decoder;
int result;
if (!v || !frame || (flags & ~RETRO_VIDEO_DECODE_DEFER_PLANES))
return -1;
rh264_video_set_deferred_readback(v, flags & RETRO_VIDEO_DECODE_DEFER_PLANES);
result = rh264_video_drain(v);
if (result != 0)
return result == 2 ? 2 : 0;
return flags ? runloop_video_decode_metadata(v, frame)
: runloop_video_decode_frame(v, frame);
}
static int runloop_video_decode_get_frame(void *decoder,
struct retro_video_decode_frame *frame)
{
rh264_video *v = (rh264_video*)decoder;
return rh264_video_readback(v) == 0
? runloop_video_decode_frame(v, frame) : -1;
}
static int runloop_video_decode_get_gpu_frame(void *decoder,
struct retro_video_decode_gpu_frame *frame)
{
video_hw_frame_t source;
int result;
if (!frame)
return -1;
result = rh264_video_export_frame((rh264_video*)decoder, &source);
if (result != 0)
return result == 2 ? 2 : -1;
memset(frame, 0, sizeof(*frame));
frame->image = source.image;
frame->plane_views[0] = source.plane_views[0];
frame->plane_views[1] = source.plane_views[1];
frame->wait = source.wait;
frame->wait_value = source.wait_value;
frame->done = source.done;
frame->done_value = source.done_value;
frame->abandon = source.abandon;
frame->owner = source.owner;
frame->width = source.width;
frame->height = source.height;
frame->layout = source.layout;
frame->yuv = source.yuv;
frame->pixfmt = source.pixfmt;
frame->array_slice = source.array_slice;
frame->crop_x = source.crop_x;
frame->crop_y = source.crop_y;
frame->api = source.api;
return 1;
}
static void runloop_video_decode_close(void *decoder)
{
rh264_video_close((rh264_video*)decoder);
}
#endif
bool runloop_environment_cb(unsigned cmd, void *data)
{
unsigned p;
runloop_state_t *runloop_st = &runloop_state;
settings_t *settings;
rarch_system_info_t *sys_info;
if (runloop_st->flags & RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB)
return false;
settings = config_get_ptr();
sys_info = &runloop_st->system;
/* 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;
}
/* Log initial environment gets here and handle
* runtime logging in 'core_option_manager.c' */
if (runloop_st->core_options->log)
RARCH_DBG("[Environ] GET_VARIABLE: %s = \"%s\"\n",
var->key, var->value);
}
break;
case RETRO_ENVIRONMENT_SET_VARIABLE:
{
size_t opt_idx, val_idx;
const struct retro_variable *var = (const struct retro_variable*)data;
/* If core passes NULL to the callback, return
* value indicates whether callback is supported */
if (!var)
return true;
if ( (!var->key || !*var->key)
|| (!var->value || !*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) ? true : false,
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->bools.game_specific_options,
!settings->bools.global_core_options,
settings->paths.path_core_options,
(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) ? true : false,
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->bools.core_option_category_enable,
settings->bools.game_specific_options,
!settings->bools.global_core_options,
settings->paths.path_core_options,
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] 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) ? true : false,
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->bools.core_option_category_enable,
settings->bools.game_specific_options,
!settings->bools.global_core_options,
settings->paths.path_core_options,
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] 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) ? true : false,
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->bools.core_option_category_enable,
settings->bools.game_specific_options,
!settings->bools.global_core_options,
settings->paths.path_core_options,
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] 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)
? true : false,
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(
categories_enabled,
settings->bools.game_specific_options,
!settings->bools.global_core_options,
settings->paths.path_core_options,
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:
{
const struct retro_core_option_display *core_options_display =
(const struct retro_core_option_display *)data;
if (runloop_st->core_options && core_options_display)
{
if (runloop_st->core_options->log)
RARCH_DBG("[Environ] SET_CORE_OPTIONS_DISPLAY: %s = %s\n",
core_options_display->key,
core_options_display->visible ? "visible" : "hidden");
core_option_manager_set_display(
runloop_st->core_options,
core_options_display->key,
core_options_display->visible);
}
}
break;
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK:
#ifdef DEBUG
RARCH_DBG("[Environ] SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK.\n");
#endif
{
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_DBG("[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)
{
/* msg->frames counts the core's frames, not 60 Hz ones */
double fps = video_state_get_ptr()->av_info.timing.fps;
if (fps <= 0.0)
fps = 60.0;
gfx_widget_set_libretro_message(
msg->msg,
(unsigned)((double)msg->frames * 1000.0 / fps + 0.5));
}
else
#endif
runloop_msg_queue_push(msg->msg, strlen(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:
/* core_status_msg belongs to the main thread, like
* the message queue: every writer and reader runs
* there. The libretro API has environment calls
* come from retro_run's thread, but a rogue core
* calling from its own worker is a thing that
* happens, so that case rides the same deferral as
* off-main message pushes and is applied by the
* drain at the top of the next iterate. */
#ifdef HAVE_THREADS
if (runloop_msg_queue_off_main(runloop_st))
{
struct runloop_deferred_msg *node =
(struct runloop_deferred_msg *)
malloc(sizeof(*node));
if (!node)
break;
node->core_status = true;
node->msg = msg->msg ? strdup(msg->msg) : NULL;
node->title = NULL;
node->len = 0;
node->prio = msg->priority;
node->duration = msg->duration;
node->icon = MESSAGE_QUEUE_ICON_DEFAULT;
node->category = MESSAGE_QUEUE_CATEGORY_INFO;
node->flush = false;
mpsc_stack_push(&runloop_st->msg_queue_deferred,
&node->link);
break;
}
#endif
/* 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 (msg->msg && *msg->msg)
{
strlcpy(runloop_st->core_status_msg.str, msg->msg,
sizeof(runloop_st->core_status_msg.str));
/* Stored so the guard above bites: a status
* holds its priority for its lifetime, and
* lower-priority updates - clears included -
* bounce until it expires or a same-or-higher
* write lands. Every clear path zeroes this
* again. */
runloop_st->core_status_msg.priority = msg->priority;
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;
}
}
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;
unsigned rotation_v[4] = {0, 90, 180, 270};
bool video_allow_rotate = settings->bools.video_allow_rotate;
RARCH_LOG("[Environ] SET_ROTATION: \"%u\" (%u deg).\n", rotation, rotation_v[rotation % 4]);
if (sys_info)
sys_info->core_requested_rotation = rotation;
if (!video_allow_rotate)
return false;
if (sys_info)
sys_info->rotation = rotation;
/* Compose with the user's configured rotation, as the menu
* path and retroarch_get_rotation() do - passing the core's
* raw value dropped the config offset until the user next
* touched the rotation setting, and left the driver's
* stored rotation out of step with retroarch_get_rotation. */
if (!video_driver_set_rotation(
(rotation + settings->uints.video_rotation) % 4))
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 (content_path && *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:
RARCH_DBG("[Environ] SET_PERFORMANCE_LEVEL: %u (Deprecated).\n",
*(const unsigned*)data);
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 ( (!dir_system || !*dir_system)
|| systemfiles_in_content_dir)
{
const char *fullpath = path_get(RARCH_PATH_CONTENT);
if (fullpath && *fullpath)
{
size_t _len;
char tmp_path[PATH_MAX_LENGTH];
if (!dir_system || !*dir_system)
RARCH_WARN("[Environ] SYSTEM DIR is empty, assume CONTENT DIR \"%s\".\n",
fullpath);
_len = fill_pathname_basedir(tmp_path, fullpath, sizeof(tmp_path));
/* Removes trailing slash (unless root dir) */
if (string_count_occurrences_single_character(tmp_path, PATH_DEFAULT_SLASH_C()) > 1
&& tmp_path[_len - 1] == PATH_DEFAULT_SLASH_C())
tmp_path[_len - 1] = '\0';
dir_set(RARCH_DIR_SYSTEM, tmp_path);
*(const char**)data = dir_get_ptr(RARCH_DIR_SYSTEM);
}
else /* If content path is empty, fall back to global system dir path */
*(const char**)data = dir_system;
RARCH_LOG("[Environ] GET_SYSTEM_DIRECTORY: \"%s\".\n",
*(const char**)data);
}
else
{
*(const char**)data = dir_system;
RARCH_LOG("[Environ] GET_SYSTEM_DIRECTORY: \"%s\".\n",
dir_system);
}
}
break;
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
*(const char**)data = runloop_st->savefile_dir;
RARCH_LOG("[Environ] GET_SAVE_DIRECTORY: \"%s\".\n",
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:
/* Builds without HAVE_LANGEXTRA have no language setting and
* hold English here, which is what their menus show. */
{
unsigned user_lang = *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE);
*(unsigned *)data = user_lang;
RARCH_LOG("[Environ] GET_LANGUAGE: \"%u\".\n", user_lang);
}
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;
case RETRO_PIXEL_FORMAT_SCRGB_RGBA16F:
if (!video_driver_supports_fp16_source())
return false;
RARCH_LOG("[Environ] SET_PIXEL_FORMAT: SCRGB_RGBA16F.\n");
break;
case RETRO_PIXEL_FORMAT_XRGB2101010:
/* Always accepted: if the active video driver cannot present a
* 10-bit source surface, the frame path transparently
* down-converts to XRGB8888 (see video_driver_frame). */
RARCH_LOG("[Environ] SET_PIXEL_FORMAT: XRGB2101010.\n");
break;
case RETRO_PIXEL_FORMAT_HDR10_2101010:
/* Unlike the SDR formats this one is conditional. The samples
* are PQ-encoded Rec.2020 absolute luminance, which is only
* meaningful on a 10-bit HDR presentation path: narrowed to 8
* bits, or shown through the SDR path, PQ code values read as
* ordinary gamma and the image comes out badly wrong rather
* than merely coarse. There is no safe silent fallback, so
* refuse and let the core pick an SDR format instead.
*
* What can be tested here is limited by when this runs.
* SET_PIXEL_FORMAT is issued from retro_load_game, i.e. during
* CMD_EVENT_CORE_INIT, which precedes drivers_init: the video
* driver has not yet created its swapchain for this session.
* VIDEO_FLAG_HDR_SUPPORT is cleared at driver init and only
* re-set once an HDR swapchain exists, so testing it here
* always fails and would refuse the format on every machine.
* The user setting is the reliable signal instead -- the D3D
* and Vulkan paths force video_hdr_mode to 0 when the display
* cannot do HDR, so a non-zero value means HDR output was both
* requested and possible. */
{
settings_t *settings = config_get_ptr();
if (settings->uints.video_hdr_mode == 0)
{
RARCH_LOG("[Environ] SET_PIXEL_FORMAT: HDR10_2101010 "
"refused (HDR output is off).\n");
return false;
}
if (!video_driver_supports_10bit_source())
{
RARCH_LOG("[Environ] SET_PIXEL_FORMAT: HDR10_2101010 "
"refused (video driver has no 10-bit source "
"path).\n");
return false;
}
RARCH_LOG("[Environ] SET_PIXEL_FORMAT: HDR10_2101010.\n");
}
break;
default:
return false;
}
/* A cached frame in the old format cannot be drawn again
* in the new one: its rows are the old format's size. */
if (video_st->pix_fmt != pix_fmt)
video_driver_cached_frame_invalidate();
video_st->pix_fmt = pix_fmt;
break;
}
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
{
if (sys_info)
{
unsigned retro_id;
const struct retro_input_descriptor *desc = NULL;
memset((void*)&sys_info->input_desc_btn, 0,
sizeof(sys_info->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:
sys_info->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:
sys_info->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_X_PLUS] = desc->description;
sys_info->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_X_MINUS] = desc->description;
break;
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
sys_info->input_desc_btn[retro_port]
[RARCH_ANALOG_RIGHT_X_PLUS] = desc->description;
sys_info->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:
sys_info->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_Y_PLUS] = desc->description;
sys_info->input_desc_btn[retro_port]
[RARCH_ANALOG_LEFT_Y_MINUS] = desc->description;
break;
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
sys_info->input_desc_btn[retro_port]
[RARCH_ANALOG_RIGHT_Y_PLUS] = desc->description;
sys_info->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:
sys_info->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:
sys_info->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];
RARCH_DBG("%s %u:\n", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PORT), p + 1);
if (mapped_port >= MAX_USERS)
continue;
for (retro_id = 0; retro_id < RARCH_FIRST_CUSTOM_BIND; retro_id++)
{
enum msg_hash_enums _enum;
unsigned bind_index = input_config_bind_order[retro_id];
const char *description = sys_info->input_desc_btn[mapped_port][bind_index];
if (!description)
continue;
_enum = (enum msg_hash_enums)(MENU_ENUM_LABEL_VALUE_INPUT_JOYPAD_B + bind_index);
RARCH_DBG(" \"%s\" => \"%s\"\n",
msg_hash_to_str(_enum), description);
}
for (retro_id = RARCH_FIRST_CUSTOM_BIND; retro_id < RARCH_ANALOG_BIND_LIST_END; retro_id++)
{
enum msg_hash_enums _enum;
unsigned bind_index = input_config_bind_order[retro_id];
const char *description = sys_info->input_desc_btn[mapped_port][bind_index];
if (!description)
continue;
_enum = (enum msg_hash_enums)(MENU_ENUM_LABEL_VALUE_INPUT_ANALOG_LEFT_X_PLUS
+ bind_index - RARCH_FIRST_CUSTOM_BIND);
RARCH_DBG(" \"%s\" => \"%s\"\n",
msg_hash_to_str(_enum), 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 (sys_info)
{
RARCH_LOG("[Environ] SET_DISK_CONTROL_INTERFACE.\n");
disk_control_set_callback(&sys_info->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 (sys_info)
{
RARCH_LOG("[Environ] SET_DISK_CONTROL_EXT_INTERFACE.\n");
disk_control_set_ext_callback(&sys_info->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: \"%s\"...\n", video_driver_name);
if (memcmp(video_driver_name, "glcore", sizeof("glcore")) == 0)
{
*cb = RETRO_HW_CONTEXT_OPENGL_CORE;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: RETRO_HW_CONTEXT_OPENGL_CORE.\n");
}
else if (memcmp(video_driver_name, "gl", sizeof("gl")) == 0)
{
*cb = RETRO_HW_CONTEXT_OPENGL;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: RETRO_HW_CONTEXT_OPENGL.\n");
}
else if (memcmp(video_driver_name, "vulkan", sizeof("vulkan")) == 0)
{
*cb = RETRO_HW_CONTEXT_VULKAN;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: RETRO_HW_CONTEXT_VULKAN.\n");
}
else if (memcmp(video_driver_name, "d3d11", sizeof("d3d11")) == 0)
{
*cb = RETRO_HW_CONTEXT_D3D11;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: RETRO_HW_CONTEXT_D3D11.\n");
}
else if (memcmp(video_driver_name, "d3d12", sizeof("d3d12")) == 0)
{
*cb = RETRO_HW_CONTEXT_D3D12;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: RETRO_HW_CONTEXT_D3D12.\n");
}
else if (memcmp(video_driver_name, "d3d9_hlsl", sizeof("d3d9_hlsl")) == 0)
{
*cb = RETRO_HW_CONTEXT_D3D9;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: RETRO_HW_CONTEXT_D3D9.\n");
}
else
{
*cb = RETRO_HW_CONTEXT_NONE;
RARCH_LOG("[Environ] GET_PREFERRED_HW_RENDER: 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;
}
/* A refusal is the answer to one probe: cores walk a list
* of context types until one is accepted, so it is a warning
* here and the core decides whether it was fatal. */
if (!dynamic_request_hw_context(
cb->context_type, cb->version_minor, cb->version_major))
{
RARCH_WARN("[Environ] SET_HW_RENDER: Context refused, the core may request another.\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));
/* Publish the type for cross-thread
* video_driver_is_hw_context() readers, after the copy has
* landed; see hw_context_type in video_driver.h. */
retro_atomic_store_release_int(
&video_st->hw_context_type, (int)cb->context_type);
#ifdef DEBUG
RARCH_DBG("[Environ] Reached end of SET_HW_RENDER.\n");
#endif
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");
runloop_st->system.load_no_content = state;
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 *rec_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 (rec_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;
#ifdef HAVE_MICROPHONE
unsigned microphone_latency_current = MAX(
runloop_st->audio_latency, settings->uints.microphone_latency);
#endif
RARCH_LOG("[Environ] 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;
}
/* A staged load applies the request when its drivers start. */
if (video_state_get_ptr()->data && !runloop_is_content_switching())
{
if (MAX(audio_latency_new, settings->uints.audio_latency_floor)
!= MAX(audio_latency_current, settings->uints.audio_latency_floor))
{
RARCH_LOG("[Environ] Setting audio latency to %u ms.\n", audio_latency_new);
command_event(CMD_EVENT_AUDIO_REINIT, NULL);
}
#ifdef HAVE_MICROPHONE
if ( settings->bools.microphone_enable
&& microphone_latency_current != MAX(
runloop_st->audio_latency, settings->uints.microphone_latency))
command_event(CMD_EVENT_MICROPHONE_REINIT, NULL);
#endif
}
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_core_set_sensor_state;
iface->get_sensor_input = input_core_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;
/* The planar callback sits past the end of the struct an
* older core allocated; its caps bit says the field exists */
memcpy(&camera_st->cb, cb,
offsetof(struct retro_camera_callback, frame_planar));
camera_st->cb.frame_planar =
(cb->caps & (UINT64_C(1) << RETRO_CAMERA_BUFFER_PLANAR))
? cb->frame_planar : NULL;
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 *loc_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 (sys_info)
sys_info->location_cb = *cb;
loc_st->active = true;
break;
}
case RETRO_ENVIRONMENT_GET_VIDEO_DECODE_INTERFACE:
#ifdef HAVE_RMP4
{
struct retro_video_decode_interface *iface =
(struct retro_video_decode_interface*)data;
if (!iface || (iface->interface_version < 1
|| iface->interface_version > RETRO_VIDEO_DECODE_INTERFACE_VERSION))
return false;
RARCH_LOG("[Environ] GET_VIDEO_DECODE_INTERFACE: H.264 with CPU planes.\n");
iface->open = runloop_video_decode_open;
iface->decode = runloop_video_decode;
iface->drain = runloop_video_decode_drain;
iface->close = runloop_video_decode_close;
if (iface->interface_version >= 2)
{
iface->decode_ex = runloop_video_decode_ex;
iface->drain_ex = runloop_video_decode_drain_ex;
iface->get_frame = runloop_video_decode_get_frame;
}
if (iface->interface_version >= 3)
iface->get_gpu_frame = runloop_video_decode_get_gpu_frame;
return true;
}
#else
return false;
#endif
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] GET_CORE_ASSETS_DIRECTORY: \"%s\".\n",
dir_core_assets);
break;
}
case RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY:
{
const char **dir = (const char**)data;
const char *dir_playlist = settings->paths.directory_playlist;
*dir = *dir_playlist ? dir_playlist : NULL;
RARCH_LOG("[Environ] GET_PLAYLIST_DIRECTORY: \"%s\".\n",
dir_playlist);
break;
}
case RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY:
{
const char **dir = (const char**)data;
const char *dir_content = settings->paths.directory_menu_content;
*dir = *dir_content ? dir_content : NULL;
RARCH_LOG("[Environ] GET_FILE_BROWSER_START_DIRECTORY: \"%s\".\n",
dir_content);
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();
recording_state_t *rec_st = recording_state_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_frame_delay_auto = settings->bools.video_frame_delay_auto;
bool video_switch_refresh_rate = false;
bool no_video_reinit = true;
bool fps_changed = false;
/* Refresh rate switch for regular displays */
if (video_display_server_has_resolution_list())
video_switch_refresh_rate_maybe(&refresh_rate, &video_switch_refresh_rate);
/* Recalibrate frame delay target when video reinits
* and pause frame delay when video does not reinit */
if (video_frame_delay_auto)
{
if (no_video_reinit && !video_switch_refresh_rate)
video_st->frame_delay_pause = true;
else
video_st->frame_delay_target = 0;
}
/* CRT switchres derives its mode from the base resolution
* and field rate; while those hold, the shortcut below
* keeps the switched mode and only the remaining drivers
* (audio among them) pick up the new av_info. */
no_video_reinit = (
(video_switch_refresh_rate == false)
&& data
&& ((*info)->geometry.max_width == av_info->geometry.max_width)
&& ((*info)->geometry.max_height == av_info->geometry.max_height)
&& ( (crt_switch_resolution == 0)
|| ( ((*info)->timing.fps == av_info->timing.fps)
&& ((*info)->geometry.base_width == av_info->geometry.base_width)
&& ((*info)->geometry.base_height == av_info->geometry.base_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);
/* no need to reinit camera or microphone here */
reinit_flags &= ~(DRIVER_CAMERA_MASK | DRIVER_MICROPHONE_MASK);
RARCH_LOG("[Environ] SET_SYSTEM_AV_INFO: %ux%u, Aspect: %.4f, FPS: %.4f, Sample rate: %.0f Hz.\n",
(*info)->geometry.base_width, (*info)->geometry.base_height,
(*info)->geometry.aspect_ratio,
(*info)->timing.fps,
(*info)->timing.sample_rate);
fps_changed = (*info)->timing.fps != av_info->timing.fps;
memcpy(av_info, *info, sizeof(*av_info));
/* With video kept, only audio has anything to follow - the
* rate, and the timing the frame rate sets - and the
* running driver takes both in place; nothing starts over. */
if (!no_video_reinit || !driver_set_audio_input_rate())
command_event(CMD_EVENT_REINIT, &reinit_flags);
/* The frame limit is the frame rate times the fast-forward
* ratio; nothing else recomputes it while the core runs. */
if (fps_changed)
command_event(CMD_EVENT_SET_FRAME_LIMIT, NULL);
if (no_video_reinit)
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
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 (rec_st->data)
{
const char *_msg = msg_hash_to_str(MSG_RESTARTING_RECORDING_DUE_TO_DRIVER_REINIT);
runloop_msg_queue_push(_msg, strlen(_msg), 2, 180, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (rec_st->streaming_enable)
{
command_event(CMD_EVENT_STREAMING_TOGGLE, NULL);
command_event(CMD_EVENT_STREAMING_TOGGLE, NULL);
}
else
{
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);
}
return true;
}
return false;
}
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
{
size_t i;
const struct retro_subsystem_info *info =
(const struct retro_subsystem_info*)data;
for (i = 0; info[i].ident; i++) {}
if (sys_info)
{
struct retro_subsystem_info *info_ptr = NULL;
free(sys_info->subsystem.data);
sys_info->subsystem.data = NULL;
sys_info->subsystem.size = 0;
info_ptr = (struct retro_subsystem_info*)
malloc(i * sizeof(*info_ptr));
if (!info_ptr)
return false;
sys_info->subsystem.data = info_ptr;
memcpy(sys_info->subsystem.data, info,
i * sizeof(*sys_info->subsystem.data));
sys_info->subsystem.size = (unsigned)i;
runloop_st->current_core.flags |=
RETRO_CORE_FLAG_HAS_SET_SUBSYSTEMS;
}
break;
}
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
{
size_t 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("%s %u:\n", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PORT), i + 1);
for (j = 0; j < info[i].num_types; j++)
if (info[i].types[j].desc)
RARCH_DBG(" \"%s\" (%u)\n",
info[i].types[j].desc,
info[i].types[j].id);
}
if (sys_info)
{
struct retro_controller_info *info_ptr = NULL;
free(sys_info->ports.data);
sys_info->ports.data = NULL;
sys_info->ports.size = 0;
if (!(info_ptr = (struct retro_controller_info*)
calloc(i, sizeof(*info_ptr))))
return false;
sys_info->ports.data = info_ptr;
memcpy(sys_info->ports.data, info,
i * sizeof(*sys_info->ports.data));
sys_info->ports.size = (unsigned)i;
}
break;
}
case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
{
if (sys_info)
{
size_t 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*)sys_info->mmaps.descriptors);
sys_info->mmaps.descriptors = 0;
sys_info->mmaps.num_descriptors = 0;
if (!(descriptors = (rarch_memory_descriptor_t*)calloc(mmaps->num_descriptors,
sizeof(*descriptors))))
return false;
sys_info->mmaps.descriptors = descriptors;
sys_info->mmaps.num_descriptors = mmaps->num_descriptors;
for (i = 0; i < mmaps->num_descriptors; i++)
sys_info->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 < sys_info->mmaps.num_descriptors; i++)
{
char flags[7];
const rarch_memory_descriptor_t *desc =
&sys_info->mmaps.descriptors[i];
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))
{
bool video_frame_delay_auto = settings->bools.video_frame_delay_auto;
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. */
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
/* Ignore frame delay target temporarily */
if (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))
{
/* what the core holds from here on */
video_driver_hw_render_interface_note(*iface);
return true;
}
return false;
}
case RETRO_ENVIRONMENT_GET_HW_RENDER_UPSCALER_INTERFACE:
{
#ifdef HAVE_NGX
const struct retro_hw_render_upscaler_interface **iface =
(const struct retro_hw_render_upscaler_interface **)data;
video_upscaler_note_wanted();
if (iface && (*iface = video_upscaler_get_interface()))
return true;
#endif
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:
case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS_2:
{
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 = 5;
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,
/* VFS API v4 */
retro_vfs_stat_64_impl,
/* VFS API v5 */
retro_vfs_set_readonly_impl,
retro_vfs_get_mtime_impl,
retro_vfs_set_mtime_impl,
retro_vfs_copy_begin_impl,
retro_vfs_copy_step_impl,
retro_vfs_copy_close_impl,
retro_vfs_dirent_stat_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;
sys_info->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_VFS_AUTHORIZED_LOCATIONS:
{
#if defined(ANDROID) && defined(HAVE_SAF)
return android_get_vfs_authorized_locations(
(struct retro_vfs_authorized_locations*)data);
#else
return false;
#endif
}
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_FLAGS_GET(audio_st) & AUDIO_FLAG_SUSPENDED)
&& (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_ACTIVE))
result |= RETRO_AV_ENABLE_AUDIO;
/* A frame the fast-forward frameskip drops is not worth
* rendering (video_driver_ff_frameskip_decide) */
if ( (video_st->main_flags & VIDEO_FLAG_ACTIVE)
&& !runloop_ff_skips_render(
(video_st->main_flags & VIDEO_FLAG_FF_SKIP_FRAME) != 0,
(video_st->main_flags & VIDEO_FLAG_FF_FRAME_OWED) != 0)
&& !(video_st->main_flags & VIDEO_FLAG_STUB_FRAME)
&& !(video_st->current_video->frame == video_null.frame))
result |= RETRO_AV_ENABLE_VIDEO;
#ifdef HAVE_RUNAHEAD
if (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_HARD_DISABLE)
result |= RETRO_AV_ENABLE_HARD_DISABLE_AUDIO;
#endif
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_REPLAYING, NULL))
result &= ~(RETRO_AV_ENABLE_VIDEO|RETRO_AV_ENABLE_AUDIO);
#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 |= RETRO_AV_ENABLE_FAST_SAVESTATES;
#endif
if (data)
{
enum retro_av_enable_flags* result_p = (enum retro_av_enable_flags*)data;
*result_p = (enum retro_av_enable_flags)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)
== RUNAHEAD_COPY_READY))
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_NETPLAY_CLIENT_INDEX:
{
#ifdef HAVE_NETWORKING
if (data)
{
uint32_t self_client_num = 0;
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_GET_SELF_CLIENT_NUM, &self_client_num))
{
*(unsigned*)data = self_client_num;
break;
}
}
#endif
return false;
}
case RETRO_ENVIRONMENT_GET_MIDI_INTERFACE:
{
struct retro_midi_interface *midi_interface =
(struct retro_midi_interface *)data;
RARCH_LOG("[Environ] GET_MIDI_INTERFACE.\n");
if (midi_interface)
{
midi_driver_request(true);
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_FLAGS_GET(audio_st) & AUDIO_FLAG_SUSPENDED)
|| !(AUDIO_FLAGS_GET(audio_st) & 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) ? true : false;
if (menu_opened)
{
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
#ifdef HAVE_NETWORKING
core_paused = menu_pause_libretro
&& netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
#else
core_paused = 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)
{
float slowmotion_ratio = settings->floats.slowmotion_ratio;
throttle_state->mode = RETRO_THROTTLE_SLOW_MOTION;
throttle_state->rate /= (slowmotion_ratio > 0.0f
? 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 || settings->bools.video_scanline_sync)
&& (!(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_SAMPLE_RATE:
{
if (!settings)
return false;
*(unsigned *)data = settings->uints.audio_output_sample_rate;
break;
}
case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE:
{
/* "The refresh rate the frontend is targeting." That is
* settings->floats.video_refresh_rate: every part of the sync
* stack paces to it - driver_adjust_system_rates(),
* audio_driver_monitor_adjust_system_rates() and
* video_driver_monitor_adjust_system_rates() all derive from
* that setting and never from the polled value.
*
* video_driver_get_refresh_rate() answers a different
* question: what the display reports it is capable of. On a
* fixed-mode desktop display the two coincide, so asking
* the display happens to give the right answer. They
* diverge on an
* adaptive panel: an iOS ProMotion device reports 120 Hz from
* [UIScreen maximumFramesPerSecond] while the CADisplayLink -
* and therefore the runloop - is deliberately being driven at
* the configured 60 Hz.
*
* Answering 120 there makes the dummy core advertise
* timing.fps 120 while the runloop iterates 60 times a second,
* so audio_driver_menu_sample() emits half the frames per
* second it should and menu BGM crackles from startup.
*
* Keep the polled rate only as the fallback for a config that
* has no usable value yet. */
float target_refresh_rate = 0.0f;
if (settings)
target_refresh_rate = settings->floats.video_refresh_rate;
/* If the config has nothing sane, fall back to asking the
* display [possibly 0 if unimplemented, which is a valid
* answer for this envcall]. */
if (target_refresh_rate <= 0.0f)
target_refresh_rate = video_driver_get_refresh_rate();
#ifdef HAVE_OPENXR
/* The headset's rate only while the runloop iterates at it:
* a headset that paces the core at its own rate, or a window
* that does, targets the configured one. */
{
video_driver_state_t *video_st = video_state_get_ptr();
if ( (video_driver_xr_headset() & VIDEO_XR_HEADSET_PRESENTING)
&& video_st->headset_runloop_hz > 0.0f)
target_refresh_rate = video_st->headset_runloop_hz;
}
#endif
*(float *)data = target_refresh_rate;
break;
}
case RETRO_ENVIRONMENT_GET_MEMORY_STATUS:
{
struct retro_memory_status *memstat = (struct retro_memory_status *)data;
memstat->free = mem_stats_free();
memstat->total = mem_stats_total();
/* A core sizing a pool against these will do total - free at
* some point; never hand it a pair that makes that negative. */
if (memstat->total && memstat->free > memstat->total)
memstat->free = memstat->total;
/* If the active frontend driver cannot report memory, tell the core
* the call is unsupported so it falls back to its own defaults. */
if (memstat->free == 0 && memstat->total == 0)
return false;
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_SAVE_STATE_DISABLE_UNDO:
{
bool state = *(const bool*)data;
RARCH_LOG("[Environ] RETRO_ENVIRONMENT_SET_SAVE_STATE_DISABLE_UNDO: %s.\n", state ? "yes" : "no");
set_save_state_disable_undo(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] 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] 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] 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 the null driver is active... */
if (driver == &microphone_null)
{
RARCH_DBG("[Environ] Cannot initialize microphone interface, active driver is null.\n");
return false;
}
/* If microphone support is off... */
if (!settings->bools.microphone_enable)
{
RARCH_DBG("[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 && memcmp(settings->arrays.microphone_driver,
"null", sizeof("null")) == 0)
{
RARCH_DBG("[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] 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
#ifdef HAVE_D3D12
/* Version 1 of the D3D12 negotiation: the core names the
* highest hardware render interface version it can use
* (libretro_d3d12.h). The literal, not the header's macro:
* that header is Windows' d3d12.h and this file is not. */
if (iface->interface_type == RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12)
iface->interface_version = 1;
else
#endif
#ifdef HAVE_D3D11
/* Version 1 of the D3D11 negotiation, as for D3D12 above
* (libretro_d3d11.h). */
if (iface->interface_type == RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11)
iface->interface_version = 1;
else
#endif
{
iface->interface_version = 0;
}
}
break;
case RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT:
{
struct retro_audio_sample_float_callback *cb =
(struct retro_audio_sample_float_callback*)data;
if (!cb)
return false;
/* The 'Resample to Fixed Integer' hint (audio_fastpath_s16) asks for
* a deterministic integer audio pipeline. Advertising float here
* would defeat it: a float-native core forces the float resampler
* path regardless of the hint. So when the hint is set we decline,
* and the core keeps using its int16 batch callback - leaving the
* whole chain in the integer domain. The hint takes precedence over
* float advertisement. This is queried once at core init, so
* toggling the hint takes effect on the next core load. */
if (config_get_ptr()->bools.audio_fastpath_s16)
return false;
/* Only advertise float when the audio driver actually runs a float
* output format. If it negotiated s16 (the device rejected float, or
* the format-negotiation hint asked for s16), a float core would just
* be converted straight back to s16 for the device - so decline and
* let the core keep its int16 batch callback. When the driver is not
* yet initialised (queried before drivers_init, e.g. a direct CLI
* load) we cannot read its real format, so fall back to the
* format-negotiation hint, which is what it will request. */
if (audio_state_get_ptr()->context_audio_data
&& (AUDIO_FLAGS_GET(audio_state_get_ptr()) & AUDIO_FLAG_ACTIVE))
{
if (!(AUDIO_FLAGS_GET(audio_state_get_ptr()) & AUDIO_FLAG_USE_FLOAT))
return false;
}
else if (config_get_ptr()->uints.audio_format_negotiation
!= AUDIO_FORMAT_NEGOTIATION_FLOAT)
return false;
/* RetroArch's resampler and DSP chain are float-native, so we
* advertise float audio output and hand the core our float
* batch entry point. The core then bypasses int16 entirely.
*
* Note: the float entry funnels into the same audio_driver_flush()
* as int16 and internally bridges recording and reverse-audio
* (rewind). The one subsystem it does not cover is netplay's
* core-packet audio interception (audio_sample_batch_net), which
* swaps the int16 batch callback the float core no longer uses;
* that path remains int16-only. */
cb->batch = audio_driver_sample_batch_float;
audio_driver_set_core_float(true);
break;
}
case RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_MULTI:
{
struct retro_audio_sample_multi_callback *cb =
(struct retro_audio_sample_multi_callback*)data;
if (!cb)
return false;
/* The int16 entry always; the float one under the same terms
* as RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT - a core
* wanting float queries that first, and gets the float entry
* here when it was granted. Both fold to the stereo pipeline;
* the device's layout is the upmix's affair, as for a stereo
* core. */
cb->batch_int16 = audio_driver_sample_batch_multi_int16;
cb->batch_float = audio_state_get_ptr()->core_float
? audio_driver_sample_batch_multi_float : NULL;
audio_driver_set_core_multi(true);
break;
}
case RETRO_ENVIRONMENT_SET_VIDEO_VIEWS:
if (!video_driver_set_views((struct retro_video_views*)data))
return false;
#ifdef HAVE_OPENXR
if ( video_driver_vr_content_active()
&& !runloop_st->core_vr_content)
{
runloop_st->core_vr_content = true;
/* A core reading poses later in this run needs a sample */
video_driver_vr_sample_tracking();
}
#endif
break;
case RETRO_ENVIRONMENT_GET_VIDEO_VIEWS_STATUS:
if (!data)
return false;
*(unsigned*)data = video_driver_views_status();
break;
case RETRO_ENVIRONMENT_GET_JIT_CAPABLE:
{
#if TARGET_OS_IPHONE
/* iOS 26 changed how w/x memory can be acquired, and this API isn't helpful anymore */
if (__builtin_available(iOS 26, tvOS 26, *))
*(bool*)data = false;
else
*(bool*)data = jit_available();
#else
*(bool*)data = true;
#endif
}
break;
case RETRO_ENVIRONMENT_EXEC_MEM_ALLOC:
{
struct retro_exec_mem_alloc *alloc =
(struct retro_exec_mem_alloc *)data;
if (!alloc || alloc->version < 1)
return false;
#if TARGET_OS_IPHONE
if (!exec_mem_alloc(&alloc->size, &alloc->mode,
&alloc->rx, &alloc->rw))
{
alloc->mode = RETRO_EXEC_MEM_MODE_UNAVAILABLE;
alloc->rx = NULL;
alloc->rw = NULL;
return true;
}
if (alloc->size > 0)
exec_mem_ledger_add(alloc->rx, alloc->rw,
alloc->size, alloc->mode);
#else
if (alloc->size > 0)
return false;
alloc->mode = RETRO_EXEC_MEM_MODE_UNRESTRICTED;
alloc->rx = NULL;
alloc->rw = NULL;
#endif
return true;
}
case RETRO_ENVIRONMENT_EXEC_MEM_FREE:
{
#if TARGET_OS_IPHONE
struct retro_exec_mem_free *f =
(struct retro_exec_mem_free *)data;
if (!f)
return false;
return exec_mem_ledger_remove(f->rx);
#else
return false;
#endif
}
case RETRO_ENVIRONMENT_GET_HDR_METADATA:
case RETRO_ENVIRONMENT_GET_DISPLAY_METADATA:
return video_driver_get_display_metadata(cmd, data);
case RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE:
#ifdef HAVE_NETWORKING
RARCH_LOG("[Environ] SET_NETPACKET_INTERFACE.\n");
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, data))
{
RARCH_ERR("[Environ] SET_NETPACKET_INTERFACE set too late.\n");
return false;
}
break;
#else
return false;
#endif
case RETRO_ENVIRONMENT_GET_DEVICE_POWER:
{
struct retro_device_power *status = (struct retro_device_power *)data;
frontend_ctx_driver_t *frontend = frontend_get_ptr();
int seconds = 0;
int percent = 0;
/* If the frontend driver is unavailable... */
if (!frontend)
return false;
/* If the core just wants to query support for this environment call... */
if (!status)
return frontend->get_powerstate != NULL;
/* If the frontend driver doesn't support reporting the powerstate... */
if (frontend->get_powerstate == NULL)
return false;
switch (frontend->get_powerstate(&seconds, &percent))
{
case FRONTEND_POWERSTATE_ON_POWER_SOURCE: /* on battery power */
status->state = RETRO_POWERSTATE_DISCHARGING;
status->percent = (int8_t)percent;
status->seconds = seconds == 0 ? RETRO_POWERSTATE_NO_ESTIMATE : seconds;
break;
case FRONTEND_POWERSTATE_CHARGING /* battery available, charging */:
status->state = RETRO_POWERSTATE_CHARGING;
status->percent = (int8_t)percent;
status->seconds = seconds == 0 ? RETRO_POWERSTATE_NO_ESTIMATE : seconds;
break;
case FRONTEND_POWERSTATE_CHARGED: /* on AC, battery is full */
status->state = RETRO_POWERSTATE_CHARGED;
status->percent = (int8_t)percent;
status->seconds = RETRO_POWERSTATE_NO_ESTIMATE;
break;
case FRONTEND_POWERSTATE_NO_SOURCE: /* on AC, no battery available */
status->state = RETRO_POWERSTATE_PLUGGED_IN;
status->percent = RETRO_POWERSTATE_NO_ESTIMATE;
status->seconds = RETRO_POWERSTATE_NO_ESTIMATE;
break;
default:
/* The frontend driver supports power status queries,
* but it still gave us bad information for whatever reason. */
return false;
}
}
break;
#ifdef HAVE_OPENXR
case RETRO_ENVIRONMENT_GET_VR_HEAD_POSE:
if (!data || !runloop_vr_poses(runloop_st))
return false;
return video_driver_get_vr_head_pose((struct retro_vr_head_pose*)data);
case RETRO_ENVIRONMENT_GET_VR_FRAME_STATE:
if (!data || !runloop_vr_poses(runloop_st))
return false;
return video_driver_get_vr_frame_state(
(struct retro_vr_frame_state*)data);
case RETRO_ENVIRONMENT_GET_VR_CONTROLLER_POSES:
{
struct retro_vr_controller_poses *poses =
(struct retro_vr_controller_poses*)data;
if ( !poses
|| !runloop_vr_poses(runloop_st)
|| !video_driver_get_vr_controller_poses(poses))
return false;
input_openxr_controller_poses(poses);
}
break;
#endif
default:
RARCH_LOG("[Environ] UNSUPPORTED (#%u).\n", cmd);
return false;
}
return true;
}
#ifdef HAVE_DYNAMIC
/* The records, one per core file probed; touched only on the main
* thread (libretro_get_system_info and the load's own lookups). */
static core_probe_t *core_probes = NULL;
static size_t core_probes_count = 0;
static size_t core_probes_cap = 0;
static bool core_probes_read = false;
#define CORE_PROBE_FILE_HEADER "core_probe 1\n"
static bool core_probe_file_path(char *s, size_t len)
{
settings_t *settings = config_get_ptr();
const char *dir = settings ? settings->paths.path_libretro_info : NULL;
if (!dir || !*dir)
return false;
fill_pathname_join_special(s, dir, FILE_PATH_CORE_PROBE_CACHE, len);
return true;
}
static bool core_probe_stamp(const char *path, int64_t *size, int64_t *mtime)
{
*size = path_get_size(path);
return *size > 0 && path_get_mtime(path, mtime);
}
static core_probe_t *core_probe_find(const char *path)
{
size_t i;
for (i = 0; i < core_probes_count; i++)
if (string_is_equal(core_probes[i].path, path))
return &core_probes[i];
return NULL;
}
/* Copies @src in, replacing the record of the same path */
static core_probe_t *core_probe_put(const core_probe_t *src)
{
core_probe_t *dst = core_probe_find(src->path);
char *path;
if (!dst)
{
if (core_probes_count == core_probes_cap)
{
size_t cap = core_probes_cap ? core_probes_cap * 2 : 8;
core_probe_t *tmp = (core_probe_t*)realloc(core_probes,
cap * sizeof(*tmp));
if (!tmp)
return NULL;
core_probes = tmp;
core_probes_cap = cap;
}
if (!(path = strdup(src->path)))
return NULL;
dst = &core_probes[core_probes_count++];
}
else
path = dst->path;
*dst = *src;
dst->path = path;
return dst;
}
/* Splits the next tab-separated field off *line, in place */
static char *core_probe_field(char **line)
{
char *start = *line;
char *end;
if (!start)
return NULL;
for (end = start; *end && *end != '\t'; end++) { }
if (*end)
{
*end = '\0';
*line = end + 1;
}
else
*line = NULL;
return start;
}
static bool core_probe_parse_int64(const char *s, int64_t *out)
{
int64_t v = 0;
bool neg = (*s == '-');
if (neg)
s++;
if (!*s)
return false;
for (; *s; s++)
{
if (*s < '0' || *s > '9')
return false;
v = v * 10 + (*s - '0');
}
*out = neg ? -v : v;
return true;
}
/* Reads the persisted records once, the first time one is asked for.
* A line that does not parse is skipped: the core it names is opened
* the next time, as if never seen. */
static void core_probe_read_file(void)
{
/* Off the stack: libretro_get_system_info() holds a record there */
char *file_path = (char*)malloc(PATH_MAX_LENGTH);
void *buf = NULL;
int64_t len = 0;
char *line, *next;
core_probes_read = true;
if ( file_path
&& core_probe_file_path(file_path, PATH_MAX_LENGTH)
&& path_is_valid(file_path))
filestream_read_file(file_path, &buf, &len);
free(file_path);
if (!buf)
return;
line = (char*)buf;
if (strncmp(line, CORE_PROBE_FILE_HEADER,
STRLEN_CONST(CORE_PROBE_FILE_HEADER)))
{
free(buf);
return;
}
line += STRLEN_CONST(CORE_PROBE_FILE_HEADER);
for (; line && *line; line = next)
{
core_probe_t rec;
char *fields[9];
char *cur = line;
unsigned n;
int64_t flags;
if ((next = strchr(line, '\n')))
*next++ = '\0';
for (n = 0; n < 9 && cur; n++)
fields[n] = core_probe_field(&cur);
if ( n != 9 || cur
|| !core_probe_parse_int64(fields[0], &rec.size)
|| !core_probe_parse_int64(fields[1], &rec.mtime)
|| !core_probe_parse_int64(fields[2], &flags)
|| !*fields[3])
continue;
rec.flags = (uint8_t)flags;
rec.path = fields[3];
strlcpy(rec.library_name, fields[4], sizeof(rec.library_name));
strlcpy(rec.library_version, fields[5], sizeof(rec.library_version));
strlcpy(rec.valid_extensions, fields[6], sizeof(rec.valid_extensions));
strlcpy(rec.fullpath_exts, fields[7], sizeof(rec.fullpath_exts));
strlcpy(rec.memory_exts, fields[8], sizeof(rec.memory_exts));
if ( (rec.flags & CORE_PROBE_FLAG_STAMPED)
&& !(rec.flags & CORE_PROBE_FLAG_SUBSYSTEMS)
&& !core_probe_find(rec.path))
core_probe_put(&rec);
}
free(buf);
}
static bool core_probe_field_ok(const char *s)
{
return !strchr(s, '\t') && !strchr(s, '\n') && !strchr(s, '\r');
}
/* Writes every record that can be served, atomically. Called when a
* record changes - a core file seen for the first time or changed -
* which has just cost a full open of the core. */
static void core_probe_write_file(void)
{
char *file_path;
char *buf;
size_t i, cap, len;
cap = STRLEN_CONST(CORE_PROBE_FILE_HEADER) + 1;
for (i = 0; i < core_probes_count; i++)
cap += 3 * 24 + 6 + strlen(core_probes[i].path)
+ sizeof(core_probes[i].library_name)
+ sizeof(core_probes[i].library_version)
+ 3 * CORE_PROBE_EXTS_SIZE;
/* The path rides behind the records, off the stack */
if (!(buf = (char*)malloc(cap + PATH_MAX_LENGTH)))
return;
file_path = buf + cap;
if (!core_probe_file_path(file_path, PATH_MAX_LENGTH))
{
free(buf);
return;
}
len = strlcpy(buf, CORE_PROBE_FILE_HEADER, cap);
for (i = 0; i < core_probes_count; i++)
{
const core_probe_t *rec = &core_probes[i];
int n;
if ( !(rec->flags & CORE_PROBE_FLAG_STAMPED)
|| (rec->flags & CORE_PROBE_FLAG_SUBSYSTEMS)
|| !core_probe_field_ok(rec->path)
|| !core_probe_field_ok(rec->library_name)
|| !core_probe_field_ok(rec->library_version)
|| !core_probe_field_ok(rec->valid_extensions)
|| !core_probe_field_ok(rec->fullpath_exts)
|| !core_probe_field_ok(rec->memory_exts))
continue;
n = snprintf(buf + len, cap - len,
"%" PRId64 "\t%" PRId64 "\t%u\t%s\t%s\t%s\t%s\t%s\t%s\n",
rec->size, rec->mtime, (unsigned)rec->flags, rec->path,
rec->library_name, rec->library_version,
rec->valid_extensions, rec->fullpath_exts, rec->memory_exts);
if (n < 0 || (size_t)n >= cap - len)
break;
len += (size_t)n;
}
if (!filestream_write_file_atomic(file_path, buf, (int64_t)len))
RARCH_WARN("[Core] Could not write \"%s\".\n", file_path);
free(buf);
}
/* The record for @path if it still describes the file there */
static const core_probe_t *core_probe_lookup(const char *path)
{
int64_t size, mtime;
const core_probe_t *rec;
if (!core_probes_read)
core_probe_read_file();
if ( !(rec = core_probe_find(path))
|| !(rec->flags & CORE_PROBE_FLAG_STAMPED)
|| (rec->flags & CORE_PROBE_FLAG_SUBSYSTEMS)
|| !core_probe_stamp(path, &size, &mtime)
|| size != rec->size
|| mtime != rec->mtime)
return NULL;
return rec;
}
void runloop_core_probe_cache_free(void)
{
size_t i;
for (i = 0; i < core_probes_count; i++)
free(core_probes[i].path);
free(core_probes);
core_probes = NULL;
core_probes_count = 0;
core_probes_cap = 0;
core_probes_read = false;
}
bool runloop_core_probe_need_fullpath(const char *core_path,
const char *ext, bool *need_fullpath)
{
const core_probe_t *rec;
size_t ext_len;
if ( !core_path || !*core_path
|| !(rec = core_probe_find(core_path))
|| (rec->flags & CORE_PROBE_FLAG_OVERRIDES_CUT))
return false;
*need_fullpath = (rec->flags & CORE_PROBE_FLAG_NEED_FULLPATH) != 0;
if (ext && (ext_len = strlen(ext)))
{
if (core_probe_ext_listed(rec->fullpath_exts, ext, ext_len))
*need_fullpath = true;
else if (core_probe_ext_listed(rec->memory_exts, ext, ext_len))
*need_fullpath = false;
}
return true;
}
#endif
bool libretro_get_system_info(
const char *path,
struct retro_system_info *sysinfo,
bool *load_no_content)
{
struct retro_system_info dummy_info;
#ifdef HAVE_DYNAMIC
core_probe_t probe;
const core_probe_t *cached = NULL;
dylib_t lib;
bool no_game = false;
bool live = false;
#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 ((cached = core_probe_lookup(path)))
{
dummy_info.library_name = cached->library_name;
dummy_info.library_version = cached->library_version;
dummy_info.valid_extensions = cached->valid_extensions;
dummy_info.need_fullpath =
(cached->flags & CORE_PROBE_FLAG_NEED_FULLPATH) != 0;
dummy_info.block_extract =
(cached->flags & CORE_PROBE_FLAG_BLOCK_EXTRACT) != 0;
if (load_no_content)
*load_no_content = (cached->flags & CORE_PROBE_FLAG_NO_GAME) != 0;
lib = NULL;
}
else
{
memset(&probe, 0, sizeof(probe));
core_probe_capture = &probe;
lib = libretro_get_system_info_lib(path, &dummy_info, &no_game,
&live);
core_probe_capture = NULL;
if (!lib)
{
RARCH_ERR("[Core] %s: \"%s\"\n",
msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE),
path);
RARCH_ERR("[Core] Error(s): %s\n", dylib_error());
return false;
}
if (load_no_content)
*load_no_content = no_game;
}
if (lib && !live)
{
/* Kept for the session, and on disk once its file is stamped */
probe.path = (char*)path;
if (dummy_info.library_name)
strlcpy(probe.library_name, dummy_info.library_name,
sizeof(probe.library_name));
if (dummy_info.library_version)
strlcpy(probe.library_version, dummy_info.library_version,
sizeof(probe.library_version));
if (dummy_info.valid_extensions)
strlcpy(probe.valid_extensions, dummy_info.valid_extensions,
sizeof(probe.valid_extensions));
if (dummy_info.need_fullpath)
probe.flags |= CORE_PROBE_FLAG_NEED_FULLPATH;
if (dummy_info.block_extract)
probe.flags |= CORE_PROBE_FLAG_BLOCK_EXTRACT;
if (no_game)
probe.flags |= CORE_PROBE_FLAG_NO_GAME;
if (core_probe_stamp(path, &probe.size, &probe.mtime))
probe.flags |= CORE_PROBE_FLAG_STAMPED;
if (!core_probes_read)
core_probe_read_file();
if (core_probe_put(&probe) && (probe.flags & CORE_PROBE_FLAG_STAMPED))
core_probe_write_file();
}
#else
/* the linked core is the running one once initialised: what it
* declared is taken from the running state */
if (load_no_content
&& (runloop_st->current_core.flags & RETRO_CORE_FLAG_INITED))
*load_no_content = runloop_st->system.load_no_content;
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(sysinfo, &dummy_info, sizeof(*sysinfo));
runloop_st->current_library_name[0] = '\0';
runloop_st->current_library_version[0] = '\0';
runloop_st->current_valid_extensions[0] = '\0';
if ((dummy_info.library_name && *dummy_info.library_name))
strlcpy(runloop_st->current_library_name,
dummy_info.library_name,
sizeof(runloop_st->current_library_name));
if ((dummy_info.library_version && *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));
sysinfo->library_name = runloop_st->current_library_name;
sysinfo->library_version = runloop_st->current_library_version;
sysinfo->valid_extensions = runloop_st->current_valid_extensions;
#ifdef HAVE_DYNAMIC
if (lib)
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 (!path || !*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 *_msg = msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE);
RARCH_ERR("[Core] %s: \"%s\"\nError(s): %s\n", _msg, path, dylib_error());
runloop_msg_queue_push(_msg, strlen(_msg), 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 -
* unless the caller has opened it already */
if (!(lib_handle_local = *lib_handle_p))
{
if (!(lib_handle_local = dylib_load(lib_path)))
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_WEBM:
#ifdef HAVE_WEBMPLAYER
CORE_SYMBOLS(SYMBOL_MEDIA);
#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)
{
return runloop_state.flags;
}
/* The platform has taken the application away, or given it back:
* paused and idle while it is away. */
void runloop_set_platform_paused(bool paused)
{
if (paused)
runloop_state.flags |= (RUNLOOP_FLAG_PAUSED | RUNLOOP_FLAG_IDLE);
else
runloop_state.flags &= ~(RUNLOOP_FLAG_PAUSED | RUNLOOP_FLAG_IDLE);
}
/* Whether the keyboard callback in place is the frontend's own, which
* is the one a replay feeds its recorded keys to. */
bool runloop_key_event_is_frontend(void)
{
return runloop_state.key_event
&& runloop_state.key_event == runloop_state.frontend_key_event;
}
/* Whether a core has content loaded. */
bool runloop_content_loaded(void)
{
return (runloop_state.current_core.flags & RETRO_CORE_FLAG_GAME_LOADED) != 0;
}
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;
runloop_st->secondary_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,
float fastforward_ratio)
{
video_driver_state_t
*video_st = video_state_get_ptr();
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, config_get_ptr()->floats.fastforward_ratio);
/* 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 *loc_st = location_state_get_ptr();
#ifdef HAVE_DYNAMIC
dylib_t lib_handle_local = runloop_st->lib_handle;
runloop_st->lib_handle = NULL;
#endif
/* Clear the callback pointers BEFORE the core library is unmapped.
* With the old order (dylib_close first, memset second) there was a
* window in which current_core still held function pointers into an
* already-unmapped .so. Anything observing runloop_state concurrently
* - e.g. a second rarch_main instance spawned by an overlapping
* Android activity lifecycle, whose APP_CMD_PAUSE handler flushes
* save files via core_get_system_info() - would pass the non-NULL
* pointer check and then jump into unmapped memory. Zeroing first
* degrades that race to a benign NULL check instead of a SIGSEGV. */
memset(current_core, 0, sizeof(struct retro_core_t));
#ifdef HAVE_DYNAMIC
if (lib_handle_local)
dylib_close(lib_handle_local);
#endif
#ifdef HAVE_OPENXR
runloop_st->core_vr_content = false;
video_driver_vr_content_clear();
#endif
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) ? true : false,
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;
loc_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));
memset(&input_st->touchpad_requested, 0,
sizeof(input_st->touchpad_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();
uint32_t flags = runloop_st->flags;
double fps = video_st->av_info.timing.fps;
retro_time_t frame_time = 0;
/* A core that reports no frame rate - the dummy core, or one that
* has not set its timing yet - has no frame time to count, and
* neither has a rate so small that one frame outlasts a day.
* Any other rate is counted exactly as it always was. */
if (fps > 0.0)
{
double frame_usec = (1.0 / fps) * 1000000.0;
if (frame_usec < 86400000000.0)
frame_time = (retro_time_t)frame_usec;
}
/* Account for slow motion */
if (flags & RUNLOOP_FLAG_SLOWMOTION)
return (retro_time_t)(frame_time * slowmotion_ratio);
/* Account for fast forward */
if (flags & RUNLOOP_FLAG_FASTMOTION)
{
retro_time_t potential_frame_time = current_time
- runloop_st->core_runtime_last;
runloop_st->core_runtime_last = current_time;
if (potential_frame_time < frame_time)
return potential_frame_time;
return frame_time;
}
return frame_time;
}
static bool core_unload_game(void)
{
runloop_state_t *runloop_st = &runloop_state;
video_driver_free_hw_context();
#ifdef HAVE_NGX
video_upscaler_clear_wanted();
#endif
/* The core owns the buffer the cache points at and is about to
* close: wait out any reader before it goes. */
video_driver_cached_frame_retire();
if ((runloop_st->current_core.flags & RETRO_CORE_FLAG_GAME_LOADED))
{
RARCH_LOG("[Core] Unloading game...\n");
#ifdef HAVE_THREADS
video_thread_core_context_take();
#endif
runloop_st->current_core.retro_unload_game();
#ifdef HAVE_THREADS
video_thread_core_context_release();
#endif
#ifdef HAVE_WEBMPLAYER
if (runloop_st->current_core_type == CORE_TYPE_WEBM)
{
libretro_media_set_video_hw_refresh(NULL, NULL);
video_driver_movie_clear();
}
#endif
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_reset_auto_state_load(runloop_state_t *runloop_st)
{
runloop_st->auto_state_load_pending = false;
runloop_st->auto_state_load_attempted = false;
runloop_st->auto_state_load_ready = false;
runloop_st->auto_state_load_core_ran = false;
}
static void runloop_apply_fastmotion_override(runloop_state_t *runloop_st,
bool frame_time_counter_auto_reset,
float fastforward_ratio_default,
bool audio_fastforward_mute)
{
float fastforward_ratio_current;
video_driver_state_t *video_st = video_state_get_ptr();
audio_driver_state_t *audio_st = audio_state_get_ptr();
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 (audio_fastforward_mute && (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_MUTED);
else
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_MUTED);
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_auto_reset)
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->main_flags &= ~VIDEO_FLAG_WIDGETS_FASTMOTION;
#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);
}
static void runloop_apply_fastmotion(bool enabled)
{
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();
video_driver_state_t *video_st = video_state_get_ptr();
settings_t *settings = config_get_ptr();
if (enabled)
{
input_st->flags |= INP_FLAG_NONBLOCKING;
runloop_st->flags |= RUNLOOP_FLAG_FASTMOTION;
command_event(CMD_EVENT_SET_FRAME_LIMIT, NULL);
}
else
{
input_st->flags &= ~INP_FLAG_NONBLOCKING;
runloop_st->flags &= ~RUNLOOP_FLAG_FASTMOTION;
runloop_st->fastforward_after_frames = 1;
}
if (settings->bools.audio_fastforward_mute && enabled)
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_MUTED);
else
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_MUTED);
driver_set_nonblock_state();
if (!enabled && settings->bools.frame_time_counter_auto_reset)
video_st->frame_time_count = 0;
}
static bool runloop_can_set_speed(void)
{
if (!(runloop_state.flags & RUNLOOP_FLAG_CORE_RUNNING)
|| (runloop_state.flags & RUNLOOP_FLAG_PAUSED))
return false;
#ifdef HAVE_NETWORKING
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL))
return false;
#endif
return true;
}
bool runloop_set_fastmotion(bool enabled)
{
if (((runloop_state.flags & RUNLOOP_FLAG_FASTMOTION) != 0) == enabled)
return true;
if (!runloop_can_set_speed()
|| runloop_state.fastmotion_override.pending
|| runloop_state.fastmotion_override.current.inhibit_toggle)
return false;
runloop_apply_fastmotion(enabled);
return true;
}
bool runloop_set_slowmotion(bool enabled)
{
if (((runloop_state.flags & RUNLOOP_FLAG_SLOWMOTION) != 0) == enabled)
return true;
if (!runloop_can_set_speed())
return false;
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
return false;
#endif
if (enabled)
runloop_state.flags |= RUNLOOP_FLAG_SLOWMOTION;
else
runloop_state.flags &= ~RUNLOOP_FLAG_SLOWMOTION;
return true;
}
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();
#ifdef HAVE_COMMAND
if (runloop_st->frame_work & RUNLOOP_WORK_RUN_FRAMES)
command_run_frames_cancel("the content was closed");
runloop_frame_work_set(RUNLOOP_WORK_PAUSE_LOADED, false);
/* the next content's addresses mean something else, and its first
* frame is not to find buttons held for this one */
command_watch_clear();
command_checkpoint_clear();
command_run_frames_forget();
input_driver_command_release(MAX_USERS);
#endif
audio_driver_set_core_float(false);
audio_driver_set_core_multi(false);
#ifdef HAVE_THREADS
/* Defensive: ensure the autosave worker thread is joined
* before we touch core-owned memory. autosave_t->retro_buffer
* borrows a pointer from core_get_memory(); if a worker is
* mid-read when retro_unload_game / retro_deinit frees that
* region, the worker reads freed memory.
*
* The standard MAIN_DEINIT path already calls autosave_deinit
* before reaching here, so this is normally a no-op. The error
* paths in retroarch_main_init (the dummy-core fallback at
* retroarch.c:8314 and the error: label at retroarch.c:8414)
* reach CMD_EVENT_CORE_DEINIT without the explicit teardown,
* which can leave a worker alive if event_init_content failed
* after starting one. autosave_deinit is idempotent so the
* extra call is safe regardless of which path got us here. */
if (runloop_st->flags & RUNLOOP_FLAG_USE_SRAM)
autosave_deinit();
#endif
runloop_reset_auto_state_load(runloop_st);
/* Remap save and cleanup logic should be placed before
* core_unload_game(), to ensure that input description data
* does not become invalid before remaps are saved. */
if ((runloop_st->flags & (
RUNLOOP_FLAG_REMAPS_CORE_ACTIVE
| RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE
| RUNLOOP_FLAG_REMAPS_GAME_ACTIVE))
|| (runloop_st->name.remapfile && *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, false);
core_unload_game();
/* Reset core sensor tracking — the core is going away */
{
input_driver_state_t *input_st = input_state_get_ptr();
input_st->core_accel_rate = 0;
input_st->core_gyro_rate = 0;
}
video_driver_cached_frame_retire();
video_driver_clear_views();
/* The next content gets its own headset notice. */
video_st->headset_notice_hz = 0;
if (runloop_st->current_core.flags & RETRO_CORE_FLAG_INITED)
{
RARCH_LOG("[Core] Unloading core...\n");
runloop_st->current_core.retro_deinit();
#if TARGET_OS_IPHONE
exec_mem_ledger_free_all();
#endif
}
/* A core may turn its LEDs or motors off as it unloads; no frame
* follows to write that. */
input_driver_flush_rumble();
led_driver_flush();
/* After retro_deinit, which may still send its notes-off */
midi_driver_request(false);
/* 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->bools.frame_time_counter_auto_reset,
settings->floats.fastforward_ratio,
settings->bools.audio_fastforward_mute
);
runloop_st->fastmotion_override.pending = false;
}
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)
video_display_server_restore_refresh_rate();
/* Recalibrate frame delay target */
if (settings->bools.video_frame_delay_auto)
video_st->frame_delay_target = 0;
/* A staged content load keeps the drivers up through the close
* and the next core's init, so the window keeps presenting; it
* frees them itself, right before the new session's drivers_init. */
if (!runloop_st->content_switching)
driver_uninit(DRIVERS_CMD_ALL, (enum driver_lifetime_flags)0);
#ifdef HAVE_CONFIGFILE
/* Reload the original config */
if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE)
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
#ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_SET_CORE_PACKET_INTERFACE, NULL);
#endif
}
static bool runloop_path_init_subsystem(runloop_state_t *runloop_st)
{
size_t i, j;
const struct retro_subsystem_info *info = NULL;
rarch_system_info_t *sys_info = &runloop_st->system;
bool subsystem_path_empty = path_is_empty(RARCH_PATH_SUBSYSTEM);
const char *savefile_dir = runloop_st->savefile_dir;
if (!sys_info || 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(
sys_info->subsystem.data,
sys_info->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[NAME_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);
fill_pathname(savename,
runloop_st->subsystem_fullpaths->elems[i].data, "",
sizeof(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("[Subsystem] %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))
fill_pathname(runloop_st->name.savefile,
runloop_st->runtime_content_path_basename,
".srm",
sizeof(runloop_st->name.savefile));
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("[Subsystem] %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();
bool entry_state_load = runloop_st->entry_state_slot > -1;
runloop_reset_auto_state_load(runloop_st);
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 (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)
runloop_path_init_savefile_internal(runloop_st);
runloop_path_fill_names();
if (!content_init())
{
#ifdef HAVE_MENU
/* Single-click playlist return */
if (settings->bools.input_menu_singleclick_playlists)
menu_state_get_ptr()->flags |= MENU_ST_FLAG_PENDING_CLOSE_CONTENT;
/* Return from empty Quick Menu if core is manually loaded and needs reloading */
if (!path_is_empty(RARCH_PATH_CORE_LAST))
menu_state_get_ptr()->flags |= MENU_ST_FLAG_PENDING_CLOSE_CONTENT;
#endif
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));
/* Set entry slot from playlist entry if available */
{
#ifdef HAVE_MENU
playlist_t *playlist = playlist_get_cached();
if (playlist)
{
struct menu_state *menu_st = menu_state_get_ptr();
const struct playlist_entry *entry = NULL;
if (menu_st && menu_st->driver_data)
playlist_get_index(playlist, menu_st->driver_data->rpl_entry_selection_ptr, &entry);
if (entry && PLAYLIST_ENTRY_SLOT(entry) > 0)
runloop_st->entry_state_slot = PLAYLIST_ENTRY_SLOT(entry);
entry_state_load = runloop_st->entry_state_slot > -1;
/* Override entry slot in savestate run list */
{
menu_entry_t menu_entry;
MENU_ENTRY_INITIALIZE(menu_entry);
menu_entry.flags |= MENU_ENTRY_FLAG_LABEL_ENABLED;
menu_entry_get(&menu_entry, 0, 0, NULL, true);
if (string_is_equal(menu_entry.label, MENU_ENUM_LABEL_STATE_SLOT_RUN_STR))
{
runloop_st->entry_state_slot = menu_st->driver_data->state_slot_run;
entry_state_load = true;
}
}
}
#endif
/* Set current active state slot */
if (runloop_st->entry_state_slot > -1)
configuration_set_int(settings, settings->ints.state_slot, runloop_st->entry_state_slot);
}
/*
* 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 ( entry_state_load
&& !command_event_load_entry_state(settings))
{
/* Loading the state failed, reset entry slot */
runloop_st->entry_state_slot = -1;
}
}
#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 < 0
&& 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);
/* The record task defers itself until any state load has been
* applied (task_moviectl_record_handler). That guard must not
* ask a queue finder: the unthreaded gather lifts every
* running task off the queue before invoking any handler, so a
* sibling load task is invisible to a finder and recording
* starts first. The
* guard now reads a main-thread flag instead, so no
* whole-queue wait is needed here to force the ordering. */
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)
{
settings_t *settings = config_get_ptr();
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 (content_path && *content_path)
strlcpy(runloop_st->runtime_content_path,
content_path,
sizeof(runloop_st->runtime_content_path));
if (core_path && *core_path)
strlcpy(runloop_st->runtime_core_path,
core_path,
sizeof(runloop_st->runtime_core_path));
if ( !settings->bools.content_runtime_log
&& !settings->bools.content_runtime_log_aggregate)
return;
if ( (content_path && *content_path)
&& (core_path && *core_path))
{
runtime_log_t *runtime_log = runtime_log_init(
runloop_st->runtime_content_path,
runloop_st->runtime_core_path,
settings->paths.directory_runtime_log,
settings->paths.directory_playlist,
true);
if (runtime_log)
{
if ( runloop_st->entry_state_slot < 0
&& path_is_valid(runtime_log->path)
&& runtime_log->state_slot < 1000)
configuration_set_int(settings, settings->ints.state_slot, runtime_log->state_slot);
free(runtime_log);
}
}
}
/* The facts the pace decision reads, from this iteration's state, as
* one word. Read here, in one place, so every path sees the same
* iteration. */
static unsigned runloop_menu_rate(settings_t *settings,
runloop_state_t *runloop_st, bool menu_pause_libretro);
/* The low-latency release, asked of the video driver for this iteration
* from what the pace decision already knows: the mode the setting
* wants where a content frame will follow at normal speed, nothing
* otherwise. What the driver answers, and why nothing was asked, is
* kept on video_st for the statistics line. Returns the facts with
* PACE_FACT_DRIVER_PACING added when the mode is in effect. */
static runloop_pace_facts_t runloop_driver_pacing_gather(settings_t *settings,
runloop_state_t *runloop_st, video_driver_state_t *video_st,
runloop_pace_facts_t f)
{
unsigned want = settings->uints.video_driver_pacing;
unsigned reason = VIDEO_DRIVER_PACING_REASON_NONE;
const video_poke_interface_t *poke = video_st->poke;
if (want >= VIDEO_DRIVER_PACING_LAST)
want = VIDEO_DRIVER_PACING_OFF;
if (!want)
reason = VIDEO_DRIVER_PACING_REASON_OFF;
else if (!poke || !poke->driver_pacing_set || !video_st->data)
reason = VIDEO_DRIVER_PACING_REASON_DRIVER;
/* A present paced to a scanline is its own release */
else if (settings->bools.video_scanline_sync)
reason = VIDEO_DRIVER_PACING_REASON_MODE;
else if (!runloop_pace_driver_owns(f | PACE_FACT_DRIVER_PACING,
runloop_pace_sources(f)))
reason = VIDEO_DRIVER_PACING_REASON_SUSPENDED;
if (reason != VIDEO_DRIVER_PACING_REASON_NONE)
want = VIDEO_DRIVER_PACING_OFF;
/* The driver keeps the mode on its swapchain; it is told OFF once,
* and asked again every frame while wanted, since a replaced
* swapchain comes up without it. */
if (want || video_st->driver_pacing_mode)
{
unsigned answer = (poke && poke->driver_pacing_set && video_st->data)
? poke->driver_pacing_set(video_st->data, want)
: (unsigned)VIDEO_DRIVER_PACING_REASON_DRIVER;
if (want)
reason = answer;
video_st->driver_pacing_mode = (uint8_t)
((answer == VIDEO_DRIVER_PACING_REASON_NONE) ? want : VIDEO_DRIVER_PACING_OFF);
}
video_st->driver_pacing_reason = (uint8_t)reason;
if (reason == VIDEO_DRIVER_PACING_REASON_NONE)
f |= PACE_FACT_DRIVER_PACING;
else
{
runloop_st->driver_pacing_frame_id = 0;
video_st->driver_pacing_id = 0;
video_st->driver_pacing_interval_us = 0;
}
return f;
}
static runloop_pace_facts_t runloop_pace_gather(settings_t *settings,
bool menu_pause_libretro, bool core_ran)
{
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();
video_driver_state_t *video_st = video_state_get_ptr();
runloop_pace_facts_t f = 0;
if (settings->bools.video_vsync) f |= PACE_FACT_VSYNC;
if (input_st->flags & INP_FLAG_NONBLOCKING) f |= PACE_FACT_NONBLOCKING;
if (runloop_st->flags & RUNLOOP_FLAG_FORCE_NONBLOCK) f |= PACE_FACT_FORCE_NONBLOCK;
if (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION) f |= PACE_FACT_FASTMOTION;
if (runloop_st->flags & RUNLOOP_FLAG_PAUSED) f |= PACE_FACT_PAUSED;
if (runloop_st->flags & RUNLOOP_FLAG_FOCUSED) f |= PACE_FACT_FOCUSED;
#ifdef HAVE_MENU
if (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE) f |= PACE_FACT_MENU_ALIVE;
#endif
f |= runloop_menu_rate(settings, runloop_st, menu_pause_libretro);
if ( settings->bools.vrr_runloop_enable
&& !video_st->headset_interval) f |= PACE_FACT_VRR;
#ifdef HAVE_THREADS
if (video_st->thread_wrapper_active) f |= PACE_FACT_WRAPPER;
if (settings->bools.video_threaded_display_pacing) f |= PACE_FACT_DISPLAY_PACING;
#endif
if ( (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_ACTIVE)
&& !(AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_NONBLOCK)
&& (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_WROTE)) f |= PACE_FACT_AUDIO_HOLDING;
if (settings->bools.video_scanline_sync) f |= PACE_FACT_SCANLINE_SYNC;
if (video_st->scanline[SCANLINE_TARGET]) f |= PACE_FACT_SCANLINE_LOCKED;
if (settings->bools.audio_rate_control) f |= PACE_FACT_RATE_CONTROL;
if (video_context_driver_presentable()) f |= PACE_FACT_PRESENTABLE;
if (runloop_st->frame_limit_minimum_time) f |= PACE_FACT_FRAME_LIMIT;
if (core_ran) f |= PACE_FACT_CORE_RAN;
return runloop_driver_pacing_gather(settings, runloop_st, video_st, f);
}
size_t runloop_pace_string(char *s, size_t len)
{
runloop_state_t *runloop_st = &runloop_state;
unsigned pace = runloop_st->pace;
size_t _len = 0;
s[0] = '\0';
if (pace & RUNLOOP_PACE_VSYNC)
_len += strlcpy(s + _len, "VSync", len - _len);
if (pace & RUNLOOP_PACE_AUDIO)
_len += strlcpy(s + _len, _len ? "+Audio" : "Audio", len - _len);
#ifdef HAVE_MENU
if (pace & RUNLOOP_PACE_SCANLINE && menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
_len += strlcpy(s + _len, _len ? "+Scanline" : "Scanline", len - _len);
else
#endif
if (pace & RUNLOOP_PACE_SCANLINE)
_len += strlcpy(s + _len, _len ? "+SL" : "SL", len - _len);
if (pace & RUNLOOP_PACE_DISPLAY)
_len += strlcpy(s + _len, _len ? "+Display" : "Display", len - _len);
if (pace & RUNLOOP_PACE_TIMER)
_len += strlcpy(s + _len, _len ? "+Timer" : "Timer", len - _len);
if (pace & RUNLOOP_PACE_DRIVER)
_len += strlcpy(s + _len, _len ? "+Driver" : "Driver", len - _len);
if (pace & RUNLOOP_PACE_NOWINDOW)
_len += strlcpy(s + _len, _len ? "+NoWindow" : "NoWindow", len - _len);
if (!_len)
_len = strlcpy(s, "None", len);
return _len;
}
size_t runloop_driver_pacing_string(char *s, size_t len)
{
static const char *modes[VIDEO_DRIVER_PACING_LAST] = { "Off", "On", "On+Boost" };
/* Indexed by enum video_driver_pacing_reason; the first is "in effect" */
static const char *reasons[VIDEO_DRIVER_PACING_REASON_LAST] = {
NULL,
NULL,
"no backend in this video driver",
"waiting for the video thread",
"not offered by the GPU driver",
"no native swapchain",
"Scanline Sync",
"driver error",
"menu, pause, fast-forward or unfocused",
"the core made the device without it"
};
video_driver_state_t *video_st = video_state_get_ptr();
unsigned want = config_get_ptr()->uints.video_driver_pacing;
unsigned reason = video_st->driver_pacing_reason;
size_t _len;
if (want >= VIDEO_DRIVER_PACING_LAST)
want = VIDEO_DRIVER_PACING_OFF;
if (reason >= VIDEO_DRIVER_PACING_REASON_LAST)
reason = VIDEO_DRIVER_PACING_REASON_NONE;
_len = strlcpy(s, modes[want], len);
if (want && reason != VIDEO_DRIVER_PACING_REASON_NONE && reasons[reason])
{
_len += strlcpy(s + _len, " (off: ", len - _len);
_len += strlcpy(s + _len, reasons[reason], len - _len);
_len += strlcpy(s + _len, ")", len - _len);
}
return _len;
}
void runloop_set_frame_limit(
const struct retro_system_av_info *av_info,
float fastforward_ratio)
{
if (fastforward_ratio < 0.1f)
{
runloop_state.frame_limit_minimum_time = 0;
runloop_state.frame_limit_minimum_time_ns = 0;
}
else
{
float fps = av_info->timing.fps;
runloop_state.frame_limit_minimum_time = (fps > 0.0f)
? (retro_time_t)roundf(1000000.0f / (fps * fastforward_ratio))
: 0;
runloop_state.frame_limit_minimum_time_ns = (fps > 0.0f)
? (int64_t)(1000000000.0 / ((double)fps * fastforward_ratio))
: 0;
}
}
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(
settings_t *settings)
{
runloop_state_t *runloop_st = &runloop_state;
video_driver_state_t *video_st = video_state_get_ptr();
float video_refresh_rate = settings->floats.video_refresh_rate;
float audio_max_timing_skew = settings->floats.audio_max_timing_skew;
float input_fps = video_st->av_info.timing.fps;
float timing_fps = retro_atomic_load_acquire_int(&video_st->crt_switching_active)
? input_fps : video_refresh_rate;
unsigned swap_interval_config = settings->uints.video_swap_interval;
unsigned black_frame_insertion = settings->uints.video_black_frame_insertion;
unsigned shader_subframes = settings->uints.video_shader_subframes;
unsigned ceiling;
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
/* 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 BFI is active set swap interval to 1
* > If Shader Subframes active, set swap interval to 1 */
if ( (vrr_runloop_enable)
|| (black_frame_insertion)
|| (shader_subframes > 1)
)
{
runloop_st->video_swap_interval_auto = 1;
return;
}
/* A driver that cannot hold a frame for the full multiple takes the
* ceiling down to what it can present, so the interval derived and
* the interval presented are the same number. */
ceiling = video_display_server_get_swap_interval_cap();
if ((ceiling == 0) || (ceiling > MAXIMUM_SWAP_INTERVAL))
ceiling = MAXIMUM_SWAP_INTERVAL;
runloop_st->video_swap_interval_auto =
runloop_video_swap_interval_for(timing_fps, input_fps,
audio_max_timing_skew, ceiling);
}
unsigned runloop_get_video_swap_interval(
unsigned swap_interval_config)
{
runloop_state_t *runloop_st = &runloop_state;
return (swap_interval_config != 0)
? swap_interval_config
: runloop_st->video_swap_interval_auto;
}
/*
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)
{
return config_get_ptr()->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;
}
/* The poll mode in force: the core's override if it has set one, the
* user's setting otherwise. */
static INLINE enum poll_type core_poll_type_current(void)
{
const enum poll_type_override_t
core_poll_type_override = runloop_state.core_poll_type_override;
return (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
? (enum poll_type)(core_poll_type_override - 1)
: (enum poll_type)(runloop_state.current_core.poll_type);
}
/* The content frame's input sample, wherever the poll type puts it:
* the marker the low-latency backend wants at exactly that point. */
static INLINE void runloop_driver_pacing_input_sampled(void)
{
runloop_state_t *runloop_st = &runloop_state;
if (runloop_st->driver_pacing_frame_id)
{
video_driver_state_t *video_st = video_state_get_ptr();
if (video_st->poke && video_st->poke->driver_pacing_mark)
video_st->poke->driver_pacing_mark(video_st->data,
runloop_st->driver_pacing_frame_id, VIDEO_DRIVER_PACING_MARK_INPUT_SAMPLE);
}
}
/* The core's input_state callback, in every poll mode.
*
* Under late polling the frame's first query polls. Whether late
* polling is in force is read here, at the call, and not when the
* callback is given to the core. The callback used to be chosen once,
* at load, between this one and a plain one with no poll in it; the
* mode, though, can change afterwards - Poll Type Behavior in the menu,
* or a core's override. A core loaded with normal or early polling and
* switched to late kept the plain callback: nothing polled during the
* frame, the only poll left was the fallback after retro_run(), and
* the core read input sampled before the previous frame's pacing wait,
* up to a frame older than late polling is for.
*
* RETRO_CORE_FLAG_INPUT_POLLED is set by whichever poll the mode calls
* for - before retro_run() (early), in input_poll (normal), or here
* (late) - so past the frame's poll a query costs one test in every
* mode. */
static int16_t core_input_state_cb(unsigned port,
unsigned device, unsigned idx, unsigned id)
{
if ( !(runloop_state.current_core.flags & RETRO_CORE_FLAG_INPUT_POLLED)
&& core_poll_type_current() == POLL_TYPE_LATE)
{
runloop_driver_pacing_input_sampled();
input_driver_poll();
runloop_state.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)
{
if (core_poll_type_current() == POLL_TYPE_NORMAL)
{
runloop_driver_pacing_input_sampled();
input_driver_poll();
runloop_state.current_core.flags |= RETRO_CORE_FLAG_INPUT_POLLED;
}
}
/**
* 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_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 = (enum 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);
RARCH_LOG("[Core] Geometry: %ux%u, Aspect: %.4f, FPS: %.4f, Sample rate: %.0f Hz.\n",
video_st->av_info.geometry.base_width, video_st->av_info.geometry.base_height,
video_st->av_info.geometry.aspect_ratio,
video_st->av_info.timing.fps,
video_st->av_info.timing.sample_rate);
return true;
}
#ifdef HAVE_COMPRESSION
/* The content is a file inside an archive ("game.zip#track03.bin", which
* is how a scanned playlist lists it) and the core takes archives
* itself: the name everything is filed under is the archive's, not the
* member's. See runloop_event_init_core(). */
static void runloop_path_set_basename_to_archive(runloop_state_t *runloop_st)
{
const char *content = path_get(RARCH_PATH_CONTENT);
const char *delim = (content && *content) ? path_get_archive_delim(content) : NULL;
size_t _len;
char *dot;
if (!delim)
return;
_len = (size_t)(delim - content);
if (_len == 0 || _len >= sizeof(runloop_st->runtime_content_path_basename))
return;
memcpy(runloop_st->runtime_content_path_basename, content, _len);
runloop_st->runtime_content_path_basename[_len] = '\0';
/* (without the archive's extension, as runloop_path_set_basename()
* leaves a path) */
if ( (dot = strrchr(runloop_st->runtime_content_path_basename, '.'))
&& (dot - runloop_st->runtime_content_path_basename > 0)
&& !strchr(dot, '/')
#ifdef _WIN32
&& !strchr(dot, '\\')
#endif
)
*dot = '\0';
runloop_path_set_names();
}
#endif
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 initial_disk_change_enable = true;
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;
/* Init core info files */
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
{
/* What CMD_EVENT_LOAD_CORE_PERSIST provides, without its open
* and close of the core: the system info, library strings and
* no-game support come from the handle opened below. */
const char *core_path = path_get(RARCH_PATH_CORE);
#ifdef HAVE_DYNAMIC
if ( core_path && *core_path
&& !string_ends_with_size(core_path, "builtin",
strlen(core_path), STRLEN_CONST("builtin")))
#endif
core_info_load(core_path);
}
/* 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);
runloop_st->current_library_name[0] = '\0';
runloop_st->current_library_version[0] = '\0';
runloop_st->current_valid_extensions[0] = '\0';
if (sys_info->info.library_name)
strlcpy(runloop_st->current_library_name,
sys_info->info.library_name,
sizeof(runloop_st->current_library_name));
if (sys_info->info.library_version)
strlcpy(runloop_st->current_library_version,
sys_info->info.library_version,
sizeof(runloop_st->current_library_version));
if (sys_info->info.valid_extensions)
strlcpy(runloop_st->current_valid_extensions,
sys_info->info.valid_extensions,
sizeof(runloop_st->current_valid_extensions));
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));
if (sys_info->info.library_name && *sys_info->info.library_name)
{
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);
}
if (sys_info->info.library_version && *sys_info->info.library_version)
{
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);
}
if (!sys_info->info.valid_extensions)
strlcpy(sys_info->valid_extensions, DEFAULT_EXT,
sizeof(sys_info->valid_extensions));
#ifdef HAVE_COMPRESSION
/* A core that takes archives itself (block_extract) is handed the
* archive and decides for itself what in it is the content: the
* content is the archive. A path into the archive named everything
* after the member instead - states, saves, cheats, and the
* overrides and remaps loaded next - and for a disc image packed in
* an archive that is "track03" for every game there is. Now that
* the core has said which kind it is, they are named after the
* archive, as they are when it is loaded by its own path. (The save
* directories are set from the name further down, as always.) */
if ( sys_info->info.block_extract
&& path_is_empty(RARCH_PATH_SUBSYSTEM))
runloop_path_set_basename_to_archive(runloop_st);
#endif
#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
initial_disk_change_enable = settings->bools.initial_disk_change_enable;
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_driver_modify_disp_flags(VIDEO_FLAG_SHADER_PRESETS_NEED_RELOAD, 0);
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;
/* Set save redirection paths */
runloop_path_set_redirect(settings, old_savefile_dir, old_savestate_dir);
/* Set core environment. A core declares no-game support from
* retro_set_environment, so the flag starts clear for this one. */
sys_info->load_no_content = false;
runloop_st->current_core.retro_set_environment(runloop_environment_cb);
/* The dummy stands in for the selected core, which the menu
* describes - on static builds, the linked core. */
if (type == CORE_TYPE_DUMMY)
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
/* 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)
{
/* Reset the in-memory remap state before searching for
* tier files. The unload paths in runloop_event_deinit_core
* and CMD_EVENT_UNLOAD_CORE only call set_defaults when a
* REMAPS_*_ACTIVE flag is set or a remapfile name is cached;
* unsaved per-port edits made via the Quick Menu set neither,
* so the per-port input_remap_ids arrays survive content
* close. Without this reset, those stale edits leak into the
* next session even when no remap file is present.
*
* Placed at the content-load call site rather than inside
* config_load_remap itself, so the menu remap-file deletion
* path (menu_cbs_ok.c) is unaffected: deleting a non-active
* tier file while a higher-priority tier is active continues
* to leave the active tier in place, and deleting the active
* tier still falls back to the next-priority tier as before.
*
* If a tier file is found below, input_remapping_load_file()
* calls set_defaults() itself before applying the file's
* bindings, so this reset is harmless on the found-file
* path. */
input_remapping_set_defaults(false);
config_load_remap(dir_input_remapping, &runloop_st->system);
}
#endif
video_driver_cached_frame_invalidate();
runloop_st->current_core.retro_init();
runloop_st->current_core.flags |= RETRO_CORE_FLAG_INITED;
/* Attempt to set initial disk index */
if (initial_disk_change_enable)
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, initial_disk_change_enable);
if (!runloop_event_load_core(runloop_st, poll_type_behavior))
{
runloop_reset_auto_state_load(runloop_st);
return false;
}
runloop_set_frame_limit(&video_st->av_info, fastforward_ratio);
runloop_st->frame_limit_anchor_ns = (int64_t)cpu_features_get_time_usec()
* 1000;
/* Init runtime log and read current state slot */
runloop_runtime_log_init(runloop_st);
if (runloop_st->entry_state_slot > -1)
configuration_set_int(settings, settings->ints.state_slot, runloop_st->entry_state_slot);
return true;
}
void runloop_pause_checks(void)
{
#ifdef HAVE_PRESENCE
presence_userdata_t userdata;
#endif
video_driver_state_t *video_st = video_state_get_ptr();
settings_t *settings = config_get_ptr();
float video_refresh_rate = settings->floats.video_refresh_rate;
float fastforward_ratio = settings->floats.fastforward_ratio;
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)
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
bool widgets_active = p_dispwidget->active;
if (widgets_active)
{
if (is_paused)
video_st->main_flags |= VIDEO_FLAG_WIDGETS_PAUSED;
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_PAUSED;
}
#endif
if (is_paused)
{
#if defined(HAVE_GFX_WIDGETS)
if (!widgets_active)
#endif
{
const char *_msg = msg_hash_to_str(MSG_PAUSED);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 1, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
if (!is_idle)
video_driver_cached_frame();
midi_driver_set_all_sounds_off();
/* Same idea as the MIDI silence above, for the audio stream: end it on
* a ramp rather than wherever the waveform happened to be. */
audio_driver_pause_fade(true);
#ifdef HAVE_PRESENCE
userdata.status = PRESENCE_GAME_PAUSED;
command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
#endif
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
#endif
/* Limit paused frames to video refresh. */
runloop_st->frame_limit_minimum_time = runloop_content_frame_time_us(
((video_st->video_refresh_rate_original)
? video_st->video_refresh_rate_original
: video_refresh_rate));
runloop_st->frame_limit_minimum_time_ns = runloop_content_frame_time_ns(
(video_st->video_refresh_rate_original)
? video_st->video_refresh_rate_original
: video_refresh_rate);
}
else
{
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
#endif
/* Restore frame limit. */
runloop_set_frame_limit(&video_st->av_info, fastforward_ratio);
/* Ramp back up rather than restarting mid-waveform. Not while the
* menu still holds the core: nothing resumes until it closes, and
* that close ramps it. */
#ifdef HAVE_MENU
if (!( (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
&& settings->bools.menu_pause_libretro
#ifdef HAVE_NETWORKING
&& netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL)
#endif
))
#endif
audio_driver_pause_fade(false);
}
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
if (gfx_widgets_ai_service_overlay_get_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;
/* Ignore frame delay target temporarily */
video_st->frame_delay_pause = true;
}
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 (!*runloop_st->runtime_content_path_basename)
return;
if (!*runloop_st->name.ups)
{
size_t _len = strlcpy(runloop_st->name.ups,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.ups));
strlcpy_lit(runloop_st->name.ups + _len,
".ups",
sizeof(runloop_st->name.ups) - _len);
}
if (!*runloop_st->name.bps)
{
size_t _len = strlcpy(runloop_st->name.bps,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.bps));
strlcpy_lit(runloop_st->name.bps + _len,
".bps",
sizeof(runloop_st->name.bps) - _len);
}
if (!*runloop_st->name.ips)
{
size_t _len = strlcpy(runloop_st->name.ips,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.ips));
strlcpy_lit(runloop_st->name.ips + _len,
".ips",
sizeof(runloop_st->name.ips) - _len);
}
if (!*runloop_st->name.xdelta)
{
size_t _len = strlcpy(runloop_st->name.xdelta,
runloop_st->runtime_content_path_basename,
sizeof(runloop_st->name.xdelta));
strlcpy_lit(runloop_st->name.xdelta + _len,
".xdelta",
sizeof(runloop_st->name.xdelta) - _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;
const char *_msg = NULL;
config_file_t *conf = NULL;
options_path[0] = '\0';
if (game_specific)
{
/* Get options file path (game-specific) */
if (!validate_per_core_options(options_path,
sizeof(options_path),
true,
runloop_st->system.info.library_name,
path_basename_nocompression(path_get(RARCH_PATH_BASENAME))))
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;
RARCH_LOG("[Core] Core options file created: \"%s\".\n", options_path);
_msg = msg_hash_to_str(MSG_CORE_OPTIONS_FILE_CREATED_SUCCESSFULLY);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 100, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
path_set(RARCH_PATH_CORE_OPTIONS, options_path);
if (game_specific)
{
runloop_st->flags |= RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
runloop_st->flags &= ~RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
}
else
{
runloop_st->flags &= ~RUNLOOP_FLAG_GAME_OPTIONS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_FOLDER_OPTIONS_ACTIVE;
}
config_file_free(conf);
return true;
error:
_msg = msg_hash_to_str(MSG_ERROR_SAVING_CORE_OPTIONS_FILE);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 100, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
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 (!current_options_path || !*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 (path_core_options && *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 (!*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 (!entry->value || !*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;
}
}
{
const char *_msg = msg_hash_to_str(MSG_CORE_OPTIONS_FILE_REMOVED_SUCCESSFULLY);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 100, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
}
if (conf)
config_file_free(conf);
return true;
error:
{
const char *_msg = msg_hash_to_str(MSG_ERROR_REMOVING_CORE_OPTIONS_FILE);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 100, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
}
if (conf)
config_file_free(conf);
return false;
}
void core_options_reset(const char* label)
{
size_t i;
runloop_state_t *runloop_st = &runloop_state;
core_option_manager_t *coreopts = runloop_st->core_options;
/* If there are no loaded core options, or the menu entry
* was indicating a reset for a specific core instead,
* do a "cold reset" (deletion of options file) */
if (!coreopts || (label && *label))
{
settings_t *settings = config_get_ptr();
const char *core_name = label;
char per_core_options_path[PATH_MAX_LENGTH];
RARCH_DBG("[Core] Core options cold reset, label from menu entry \"%s\", loaded core \"%s\".\n",
label, runloop_st->system.info.library_name);
if (!label || !*label)
core_name = runloop_st->system.info.library_name;
if (settings->bools.global_core_options)
{
RARCH_WARN("[Core] Core options cold reset is not supported when global core options are used, deletion skipped.\n");
return;
}
/* Get current options file path */
per_core_options_path[0] = '\0';
validate_per_core_options(
per_core_options_path, sizeof(per_core_options_path), true,
core_name, core_name);
if (!*per_core_options_path)
{
RARCH_ERR("[Core] Core options file could not be located, deletion skipped.\n");
return;
}
/* Remove current options file, if possible */
if (path_is_valid(per_core_options_path))
{
RARCH_WARN("[Core] Deleting core options file: \"%s\".\n", per_core_options_path);
filestream_delete(per_core_options_path);
}
else
{
RARCH_ERR("[Core] Core options file path is not valid, deletion skipped: \"%s\".\n", per_core_options_path);
return;
}
}
else if (coreopts->size < 1)
{
RARCH_WARN("[Core] Core options reset invoked but there are no options.\n");
return;
}
else
{
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
}
{
const char *_msg = msg_hash_to_str(MSG_CORE_OPTIONS_RESET);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 100, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
/* Writes the current core option values to whichever
* options file is active (game-specific, folder-specific,
* or the per-core/global 'default' file), without freeing
* anything and without user-facing notifications.
* Returns false only if a write was attempted and failed.
* If @out_path is non-NULL, receives the file path used
* (or NULL when there are no core options). */
static bool runloop_core_options_write(const char **out_path)
{
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);
bool ret = false;
if (out_path)
*out_path = NULL;
/* If there are no core options, there
* is nothing to do */
if (!coreopts || (coreopts->size < 1))
return true;
/* Check whether game/folder-specific options file
* is being used */
if (path_core_options && *path_core_options)
{
config_file_t *conf_tmp = NULL;
/* Attempt to load existing file */
if (path_is_valid(path_core_options))
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(coreopts, conf_tmp);
ret = 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 = coreopts->conf_path;
if (path_core_options && *path_core_options)
{
core_option_manager_flush(coreopts, coreopts->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))
coreopts->conf->flags |= CONF_FILE_FLG_MODIFIED;
ret = config_file_write(coreopts->conf,
path_core_options, true);
}
}
if (out_path)
*out_path = path_core_options;
return ret;
}
/* Quietly persists core options to disk.
*
* The automatic save otherwise only happens in
* runloop_deinit_core_options(), at the very end of core
* teardown - after the auto save-state, retro_unload_game(),
* retro_deinit() and dylib_close(). A crash or process kill
* anywhere in that sequence, or before it (Android can reclaim
* the process at any point after onPause()), loses every option
* change made during the session. Calling this beforehand makes
* those changes durable; the late save is kept, and is a no-op
* unless the core changed an option during teardown, since
* config_file_write() only writes a modified config and clears
* the flag once it has. */
void runloop_core_options_save(void)
{
const char *path = NULL;
if (!runloop_core_options_write(&path))
RARCH_ERR("[Core] Failed to save core options to \"%s\".\n",
path ? path : "UNKNOWN");
}
void core_options_flush(void)
{
size_t _len;
char msg[128];
enum message_queue_category category
= MESSAGE_QUEUE_CATEGORY_INFO;
runloop_state_t *runloop_st = &runloop_state;
core_option_manager_t *coreopts = runloop_st->core_options;
const char *path_core_options = NULL;
const char *core_options_file = NULL;
bool ret = false;
msg[0] = '\0';
/* If there are no core options, there
* is nothing to do */
if (!coreopts || (coreopts->size < 1))
return;
ret = runloop_core_options_write(&path_core_options);
/* Get options file name for display purposes */
if (path_core_options && *path_core_options)
core_options_file = path_basename_nocompression(path_core_options);
if (!core_options_file || !*core_options_file)
core_options_file = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN);
if (ret)
{
_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
{
_len = strlcpy(msg, msg_hash_to_str(MSG_CORE_OPTIONS_FLUSH_FAILED),
sizeof(msg));
RARCH_ERR(
"[Core] Failed to save core options to \"%s\".\n",
path_core_options ? path_core_options : "UNKNOWN");
category = MESSAGE_QUEUE_CATEGORY_ERROR;
}
_len += snprintf(msg + _len, sizeof(msg) - _len, " \"%s\"",
core_options_file);
runloop_msg_queue_push(msg, _len, 1, 100, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, category);
}
void runloop_msg_queue_push(
const char *msg,
size_t len,
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;
bool widgets_active;
#endif
#ifdef HAVE_ACCESSIBILITY
settings_t *settings;
bool accessibility_enable;
unsigned accessibility_narrator_speech_speed;
access_state_t *access_st;
#endif
runloop_state_t *runloop_st = &runloop_state;
#ifdef HAVE_THREADS
/* A worker's message crosses to the main thread here, whole,
* before anything below reads the settings or touches a widget:
* that work belongs to the main thread, and the drain at the top
* of the iterate replays the message there, a frame late at most
* - the cadence the message queue shows things at anyway. */
if (runloop_msg_queue_off_main(runloop_st))
{
struct runloop_deferred_msg *node = (struct runloop_deferred_msg *)
malloc(sizeof(*node));
if (!node)
return;
node->core_status = false;
node->msg = strdup(msg);
node->title = title ? strdup(title) : NULL;
node->len = len;
node->prio = prio;
node->duration = duration;
node->icon = icon;
node->category = category;
node->flush = flush;
mpsc_stack_push(&runloop_st->msg_queue_deferred, &node->link);
return;
}
#endif
#if defined(HAVE_GFX_WIDGETS)
p_dispwidget = dispwidget_get_ptr();
widgets_active = p_dispwidget->active;
#endif
#ifdef HAVE_ACCESSIBILITY
settings = config_get_ptr();
accessibility_enable = settings->bools.accessibility_enable;
accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
access_st = access_state_get_ptr();
#endif
#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,
len,
roundf((float)duration / 60.0f * 1000.0f),
title,
icon,
category,
prio,
flush,
#ifdef HAVE_MENU
(menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE) ? true : false
#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);
}
static void runloop_load_deferred_auto_state(void);
#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;
video_driver_ff_frameskip_decide(current_time);
core_run();
runloop_load_deferred_auto_state();
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 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_st->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 INLINE bool runloop_is_libretro_running(runloop_state_t* runloop_st, bool menu_pause_libretro)
{
return ((runloop_is_inited()))
&& !(runloop_st->flags & RUNLOOP_FLAG_PAUSED)
&& (!menu_pause_libretro
&& runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING);
}
/* Which Menu Frame Rate holds this iteration, as the pace fact that
* names it, or 0 when neither does: the menu is down; netplay is on,
* whose sessions keep the core's own pacing; or, at 'Content Rate', the
* core is running behind the menu, which then runs at the content's rate
* already, or there is no content rate to hold to. */
static unsigned runloop_menu_rate(settings_t *settings,
runloop_state_t *runloop_st, bool menu_pause_libretro)
{
#ifdef HAVE_MENU
if (!(menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE))
return 0;
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
return 0;
#endif
if (settings->uints.menu_frame_rate != MENU_FRAME_RATE_CONTENT)
return PACE_FACT_MENU_DISPLAY_RATE;
if ( runloop_is_libretro_running(runloop_st, menu_pause_libretro)
|| !(video_state_get_ptr()->av_info.timing.fps > 0.0))
return 0;
return PACE_FACT_MENU_CONTENT_RATE;
#else
return 0;
#endif
}
bool runloop_menu_display_rate(void)
{
settings_t *settings = config_get_ptr();
return (runloop_menu_rate(settings, &runloop_state,
settings->bools.menu_pause_libretro)
& PACE_FACT_MENU_DISPLAY_RATE) != 0;
}
bool runloop_menu_content_rate(void)
{
settings_t *settings = config_get_ptr();
return (runloop_menu_rate(settings, &runloop_state,
settings->bools.menu_pause_libretro)
& PACE_FACT_MENU_CONTENT_RATE) != 0;
}
#ifdef HAVE_MENU
/* The display's rate as the frame limiter paces to it: the configured
* one (the original one, under a refresh rate switch) or, with @window,
* that of the monitor the window is on, where one is known - which on a
* desktop of several is the one that matters. */
static float runloop_display_hz(video_driver_state_t *video_st,
settings_t *settings, bool window)
{
float hz = 0.0f;
if (window)
hz = video_driver_get_window_refresh_rate();
if (!(hz > 0.0f))
hz = (video_st->video_refresh_rate_original)
? video_st->video_refresh_rate_original
: settings->floats.video_refresh_rate;
/* A rate of nothing paces nothing; the frame time helpers take
* 60 Hz for it too */
return (hz > 0.0f) ? hz : 60.0f;
}
/* Whether the core running behind the menu at 'Display Rate' is due a
* frame. It runs on the content's own clock - a period a frame, from
* when it was last due - while the menu runs at the display's, so a
* 60 fps core keeps its speed under a 240 Hz menu. A frame due within
* half a display period runs now: the next iteration would be half a
* period late, and holding to the nearest vblank keeps the cadence even
* where the display is a whole multiple of the content. */
static bool runloop_menu_core_due(runloop_state_t *runloop_st,
video_driver_state_t *video_st, settings_t *settings,
retro_time_t now_us)
{
float refresh = runloop_display_hz(video_st, settings, true);
int64_t period = runloop_content_frame_time_ns(
(float)video_st->av_info.timing.fps);
int64_t now = (int64_t)now_us * 1000;
int64_t half = (refresh > 0.0f)
? (int64_t)(500000000.0 / (double)refresh) : 0;
if (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
period = (int64_t)((double)period * settings->floats.slowmotion_ratio);
/* The first frame under the menu, or after a stall of more than a
* few frames: due now, and the schedule restarts from here rather
* than catching up. */
if ( !runloop_st->menu_core_due_ns
|| now - runloop_st->menu_core_due_ns > 4 * period)
runloop_st->menu_core_due_ns = now;
if (now + half < runloop_st->menu_core_due_ns)
return false;
runloop_st->menu_core_due_ns += period;
return true;
}
/* The menu's audio writes block only where the menu is held to the
* content's rate. At 'Display Rate' they never hold the loop: the
* silence it feeds is measured out by the clock and the core behind
* it, if running, writes what it makes. Brought back into line every
* menu iteration, so a setting changed in the menu, or a core started
* or stopped behind it, takes effect at once; leaving the menu restores
* the usual state through driver_set_nonblock_state(). */
static void runloop_menu_audio_nonblock(settings_t *settings,
input_driver_state_t *input_st, audio_driver_state_t *audio_st,
bool display_rate)
{
bool want = display_rate
|| !settings->bools.audio_sync
|| (input_st->flags & INP_FLAG_NONBLOCKING);
bool have = (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_NONBLOCK) != 0;
if ( want != have
&& (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_ACTIVE)
&& audio_st->context_audio_data)
audio_driver_set_nonblock_state(want);
}
#endif
static retro_atomic_int_t runloop_inited
= RETRO_ATOMIC_INT_INITIALIZER(0);
void runloop_is_inited_set(void)
{
retro_atomic_store_release_int(&runloop_inited, 1);
}
void runloop_is_inited_clear(void)
{
retro_atomic_store_release_int(&runloop_inited, 0);
}
bool runloop_is_inited(void)
{
return retro_atomic_load_acquire_int(&runloop_inited) != 0;
}
/* The pause / unfocused wait: what it waits for is user input, so
* where the windowing system offers a waitable event transport the
* display server blocks on it, with ten milliseconds as the bound
* rather than the schedule; where it offers none the bound is the
* sleep it always was. Readiness only - nothing is dispatched here,
* the next input poll consumes as before. */
#if !(defined(__EMSCRIPTEN__) && !defined(EMSCRIPTEN_ASYNCIFY) && !defined(PROXY_TO_PTHREAD))
static void runloop_idle_wait(void)
{
if (!video_display_server_idle_wait(10))
retro_sleep(10);
}
#endif
#ifdef HAVE_REWIND
/* The rewind buffer a load asked for: one full serialize of the core,
* taken on the frame after the load, before the core runs. */
static VIDEO_NOINLINE void runloop_rewind_init_pending(
runloop_state_t *runloop_st)
{
runloop_st->rewind_st.flags &= ~STATE_MGR_REWIND_ST_FLAG_INIT_PENDING;
command_event(CMD_EVENT_REWIND_INIT, NULL);
}
#endif
static enum runloop_state_enum runloop_check_state(
input_driver_state_t *input_st,
audio_driver_state_t *audio_st,
video_driver_state_t *video_st,
uico_driver_state_t *uico_st,
bool err_on_init,
settings_t *settings,
retro_time_t current_time,
bool netplay_allow_pause,
bool netplay_allow_timeskip)
{
input_bits_t current_bits;
#ifdef HAVE_MENU
#endif
gfx_display_t *p_disp = disp_get_ptr();
runloop_state_t *runloop_st = &runloop_state;
static bool old_focus = true;
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
bool is_focused = false;
bool is_alive = false;
uint64_t frame_count = 0;
bool focused = true;
/* Snapshot of the output size. The video thread sets it through
* video_driver_set_output_dims() while this function runs, so it
* is re-read before the menu/widgets pass further down. */
unsigned output_dims = 0;
bool rarch_is_initialized = !!runloop_is_inited();
bool runloop_paused = !!(runloop_st->flags & RUNLOOP_FLAG_PAUSED);
bool pause_nonactive = settings->bools.pause_nonactive;
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
bool menu_driver_binding_state = !!(menu_st->flags & MENU_ST_FLAG_IS_BINDING);
bool menu_was_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 (gfx_widgets_ai_service_overlay_get_state() == 3)
{
command_event(CMD_EVENT_PAUSE, NULL);
gfx_widgets_ai_service_overlay_set_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
/* (The menu's controller combination has pressed the menu toggle by
* now, if it is set and held: input_combos_frame().) */
/* What was down when the menu opened or closed, a bind or a text
* entry ended or the mouse clicked is held back, each control until
* it is let go (input_driver_hold_held_input()). While it is, the
* pause is kept as it was. */
if (input_driver_hold_bits(&current_bits))
{
if ( runloop_paused
&& !runloop_st->paused_hotkey
&& menu_pause_libretro)
BIT256_SET(current_bits, RARCH_PAUSE_TOGGLE);
else if (runloop_st->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 (menu_pause_libretro)
command_event(CMD_EVENT_PAUSE, NULL);
else
video_driver_cached_frame();
}
}
#endif
/* Pump this thread's own window queue every iteration, threaded
* video included. With threaded video the RetroArch window lives on
* the video thread and pumps itself from the driver's alive(), but
* the companion windows are created here, and PeekMessage(NULL)
* only ever returns the calling thread's messages, so this is the
* only pump they have: gated off, they went "Not Responding" the
* moment threaded video was on. Under non-threaded video the main
* window's messages are on this queue too and win32_check_window()
* also pumps them; whichever runs first dispatches, nothing is seen
* twice. Cocoa's process_events is a no-op. */
{
const ui_application_t *application = uico_st->drv
? uico_st->drv->application
: NULL;
if (application)
application->process_events();
}
frame_count = video_st->frame_count;
/* current_video and data have independent lifetimes: driver_uninit()
* clears data while leaving the vtable pointer installed, and only
* retroarch_deinit_drivers() clears current_video. Every alive()
* implementation dereferences its argument, so the handle has to be
* checked as well as the vtable. */
is_alive = (video_st->current_video && video_st->data)
? 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
/* Automatic mouse grab on focus */
if ( settings->bools.input_auto_mouse_grab
&& (is_focused)
&& (is_focused != (((runloop_st->flags & RUNLOOP_FLAG_FOCUSED)) > 0)))
input_pointer_capture_hold(INPUT_CAPTURE_AUTO_FOCUS);
if (is_focused)
runloop_st->flags |= RUNLOOP_FLAG_FOCUSED;
else
runloop_st->flags &= ~RUNLOOP_FLAG_FOCUSED;
/* One read of the output size, shared by the overlay and
* FULL aspect checks below. */
output_dims = video_driver_get_output_dims();
#ifdef HAVE_OVERLAY
if (settings->bools.input_overlay_enable)
{
static unsigned last_dims = 0;
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);
/* When pointer input is enabled, soft-hide instead of
* unloading so mouse/lightgun input remains functional.
* Level-triggered: enforce flag state every frame. */
if ( settings->bools.input_overlay_pointer_enable
&& input_st->overlay_ptr)
{
if (controller_connected)
input_st->overlay_ptr->flags |= INPUT_OVERLAY_GAMEPAD_HIDDEN;
else
input_st->overlay_ptr->flags &= ~INPUT_OVERLAY_GAMEPAD_HIDDEN;
}
else if (controller_connected != last_controller_connected)
{
if (controller_connected)
input_overlay_unload();
else
input_overlay_init();
}
last_controller_connected = controller_connected;
}
#if defined(ANDROID)
/* Hide the on-screen overlay while a stylus is in use, and bring
* it back once the pen has been idle for a moment (the window is
* android_input.c's). The overlay is hidden, not unloaded:
* unloading and reloading a pack is file and texture work, and a
* pen leaving and returning would trigger it over and over while
* a core runs. While hidden it takes no touches and answers no
* pointer queries (INPUT_OVERLAY_STYLUS_HIDDEN), so the pen
* reaches the core through the input driver whether or not the
* overlay is set up as a pointing device.
* Level-triggered, like the gamepad soft-hide above: the overlay
* can be replaced at any time, so the flag is set on whichever
* one is current this frame. */
if (input_st->overlay_ptr)
{
if ( settings->bools.input_stylus_enable
&& android_input_stylus_recently_active())
input_st->overlay_ptr->flags |= INPUT_OVERLAY_STYLUS_HIDDEN;
else
input_st->overlay_ptr->flags &= ~INPUT_OVERLAY_STYLUS_HIDDEN;
}
#endif
/* Check next overlay hotkey */
HOTKEY_CHECK(RARCH_OVERLAY_NEXT, CMD_EVENT_OVERLAY_NEXT, true, &check_next_rotation);
/* Check whether video aspect has changed */
if (output_dims != last_dims)
{
/* 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_(output_dims,
settings->bools.input_overlay_enable,
input_st->overlay_ptr);
last_dims = output_dims;
}
/* 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 output size (the full window).
*
* This keeps its own last_dims rather than sharing the overlay
* block's: the two checks are gated independently, so a shared
* value updated by one would hide a size change from the other.
*
* It is polled here rather than driven from
* video_driver_set_output_dims() because that is called from the
* video thread under threaded video, while
* video_driver_set_aspect_ratio() issues blocking driver pokes
* through the thread wrapper.
*/
if (settings->uints.video_aspect_ratio_idx == ASPECT_RATIO_FULL)
{
static unsigned last_dims = 0;
/* Check whether video aspect has changed */
if (output_dims != last_dims)
{
/* Update set aspect ratio so the full matches the current video width & height */
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
last_dims = output_dims;
}
}
/* Check reset hotkey */
if (runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING)
{
static bool reset_key = false;
static bool old_reset_key = false;
bool trig_reset_key;
reset_key = BIT256_GET(current_bits, RARCH_RESET);
trig_reset_key = reset_key && !old_reset_key;
old_reset_key = reset_key;
/* Check double press if enabled */
if ( trig_reset_key
&& settings->bools.confirm_reset)
{
static retro_time_t reset_key_time = 0;
retro_time_t cur_time = current_time;
trig_reset_key = (cur_time - reset_key_time < QUIT_DELAY_USEC);
reset_key_time = cur_time;
if (!trig_reset_key)
{
const char *_msg = msg_hash_to_str(MSG_PRESS_AGAIN_TO_RESET);
float target_hz = 0.0;
runloop_environment_cb(
RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE, &target_hz);
runloop_msg_queue_push(_msg, strlen(_msg), 1, QUIT_DELAY_USEC * target_hz / 1000000,
true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
}
}
if (trig_reset_key)
command_event(CMD_EVENT_RESET, NULL);
}
/* Check close content hotkey */
if (runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING)
{
static bool close_key = false;
static bool old_close_key = false;
bool trig_close_key;
close_key = BIT256_GET(current_bits, RARCH_CLOSE_CONTENT_KEY);
trig_close_key = close_key && !old_close_key;
old_close_key = close_key;
/* Check double press if enabled */
if ( trig_close_key
&& settings->bools.confirm_close)
{
static retro_time_t close_key_time = 0;
retro_time_t cur_time = current_time;
trig_close_key = (cur_time - close_key_time < QUIT_DELAY_USEC);
close_key_time = cur_time;
if (!trig_close_key)
{
const char *_msg = msg_hash_to_str(MSG_PRESS_AGAIN_TO_CLOSE_CONTENT);
float target_hz = 0.0;
runloop_environment_cb(
RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE, &target_hz);
runloop_msg_queue_push(_msg, strlen(_msg), 1, QUIT_DELAY_USEC * target_hz / 1000000,
true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
}
}
if (trig_close_key)
command_event(CMD_EVENT_CLOSE_CONTENT, NULL);
}
/* Check quit hotkey */
if (!(input_st->flags & INP_FLAG_WAIT_INPUT_RELEASE))
{
static bool quit_key = false;
static bool old_quit_key = false;
static bool runloop_exec = false;
bool trig_quit_key;
quit_key = BIT256_GET(current_bits, RARCH_QUIT_KEY);
trig_quit_key = quit_key && !old_quit_key;
/* The quit controller combination presses the quit hotkey, when
* it is set and held (input_combos_frame()): like the key, it
* counts on the frame it is first down, or one press would also
* be the second press that 'confirm_quit' asks for. */
old_quit_key = quit_key;
/* Check double press if enabled */
if ( trig_quit_key
&& settings->bools.confirm_quit)
{
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)
{
const char *_msg = msg_hash_to_str(MSG_PRESS_AGAIN_TO_QUIT);
float target_hz = 0.0;
runloop_environment_cb(
RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE, &target_hz);
runloop_msg_queue_push(_msg, strlen(_msg), 1, QUIT_DELAY_USEC * target_hz / 1000000,
true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
}
}
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 (!*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("[Screenshot] Taking a screenshot before exiting...\n");
/* Take a screenshot before we exit. */
if (!take_screenshot(settings->paths.directory_screenshot,
screenshot_path,
false,
video_driver_cached_frame_is_hw_render(),
fullpath,
false))
{
RARCH_ERR("[Screenshot] 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;
}
}
}
#ifdef HAVE_MENU
/* Check menu hotkey */
{
static bool old_pressed = false;
bool pressed = BIT256_GET(current_bits, RARCH_MENU_TOGGLE)
&& memcmp(settings->arrays.menu_driver, "null", 5) != 0;
bool core_type_is_dummy = runloop_st->current_core_type == CORE_TYPE_DUMMY;
if (pressed && !old_pressed)
{
bool core_is_running = runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING;
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
{
if (rarch_is_initialized && !core_type_is_dummy && core_is_running)
retroarch_menu_running_finished(false);
}
else
retroarch_menu_running();
}
/* Initial menu toggle on startup */
else if (core_type_is_dummy && !(menu_st->flags & MENU_ST_FLAG_ALIVE))
retroarch_menu_running();
old_pressed = pressed;
}
#endif
#if defined(HAVE_MENU) || defined(HAVE_GFX_WIDGETS)
/* Per-eye UI lays out at one eye's size. */
output_dims = video_driver_get_ui_dims();
gfx_animation_update(
current_time,
settings->bools.menu_timedate_enable,
settings->floats.menu_ticker_speed,
output_dims);
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
bool rarch_force_fullscreen = ((uint32_t)retro_atomic_load_relaxed_int(&video_st->flags) &
VIDEO_FLAG_FORCE_FULLSCREEN) ? true : false;
bool video_is_fullscreen = settings->bools.video_fullscreen
|| rarch_force_fullscreen;
#ifdef HAVE_THREADS
/* Under the threaded video wrapper the worker lays out,
* animates and iterates the widgets (gfx_widgets_worker_step());
* what that asks of this thread is done here - and so are the
* badge loads the achievement widgets ask for from over there */
if (retro_atomic_load_relaxed_int(&p_dispwidget->worker))
{
#ifdef HAVE_CHEEVOS
rcheevos_badge_cache_service();
#endif
gfx_widgets_iterate_main();
}
else
#endif
gfx_widgets_iterate(
p_disp,
settings,
output_dims,
video_is_fullscreen,
settings->paths.directory_assets,
settings->paths.path_font,
VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st));
}
#ifdef HAVE_THREADS
/* A teardown handed off when they went, and an init put off by it */
else if (gfx_widgets_poll_teardown())
command_event(CMD_EVENT_OSD_NOTIFICATION_TOGGLE, NULL);
#endif
#endif
#ifdef HAVE_MENU
if (menu_st->flags & MENU_ST_FLAG_ALIVE)
{
enum menu_action action;
static input_bits_t old_input = {{0}};
/* the time of the poll whose bits arrive next frame */
static retro_time_t input_poll_time_us;
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;
/* 'current_bits' was collected from the previous poll. On the
* first menu frame that poll was taken outside the menu, so
* the current time stands in for it. */
if (!menu_was_alive)
input_poll_time_us = current_time;
menu_st->input_time_us = input_poll_time_us;
input_poll_time_us = current_time;
input_driver_poll_between_frames(cbs->poll_cb);
bits_clear_bits(trigger_input.data, old_input.data,
ARRAY_SIZE(trigger_input.data));
/* 'trigger_input' is fully derived at this point, so record
* the input state now rather than at the end of the block.
* Several pending-action paths below (config replace, quick
* menu, startup page, close content) return early, which
* would otherwise leave 'old_input' holding a stale baseline
* and cause a still-held button to be re-reported as a fresh
* press on the following frame */
old_input = current_bits;
action = (enum menu_action)menu_event(
settings,
&current_bits, &trigger_input, display_kb);
#ifdef HAVE_NETWORKING
if (!netplay_allow_pause)
focused = true;
else
#endif
{
if (pause_nonactive)
focused = is_focused || video_driver_headset_focused();
else
focused = true;
}
/* 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. */
/* A paused core asks for the views status only when it runs, so a
* change (Stereo Mode, a headset) may show or hide its options. */
if ( video_driver_views_status_changed()
&& retroarch_ctl(RARCH_CTL_CORE_OPTION_UPDATE_DISPLAY, NULL))
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
| MENU_ST_FLAG_PREVENT_POPULATE;
#ifdef HAVE_CONFIGFILE
/* If a configuration file load was requested on the previous
* frame, perform it now - before the menu is iterated and while
* no menu list/driver pointers are held on the stack.
* config_replace() triggers a full driver/menu reinit (and may
* free and recreate the menu driver), so it must never run from
* within menu iteration. Exit afterwards to start the next frame
* with a freshly (re)built menu. */
/* A content load in flight owns the session until it is through:
* a pending config replace or close starts another load (of the
* dummy core) and a pending core reload loads a library into a
* session the job is still building, so each waits, flag set,
* for the frame after the load. */
if ( (menu_st->flags & MENU_ST_FLAG_PENDING_CONFIG_REPLACE)
&& !runloop_st->content_switching)
{
bool config_save_on_exit = settings->bools.config_save_on_exit;
menu_st->flags &= ~MENU_ST_FLAG_PENDING_CONFIG_REPLACE;
config_replace(config_save_on_exit, menu_st->pending_config_path);
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
#endif
/* 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;
menu_st->flags &= ~MENU_ST_FLAG_PENDING_STARTUP_PAGE;
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
/* Navigate to initial startup page */
else if (menu_st->flags & MENU_ST_FLAG_PENDING_STARTUP_PAGE)
{
unsigned startup_page = settings->uints.menu_startup_page;
switch (startup_page)
{
default:
case MENU_STARTUP_PAGE_MAIN_MENU:
break;
case MENU_STARTUP_PAGE_HISTORY:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_HISTORY_TAB),
NULL,
msg_hash_to_str(MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY),
MENU_SETTING_ACTION,
0, 0, ACTION_OK_DL_GENERIC);
break;
case MENU_STARTUP_PAGE_FAVORITES:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES_TAB),
NULL,
MENU_ENUM_LABEL_FAVORITES_TAB_STR,
MENU_SETTING_ACTION,
0, 0, ACTION_OK_DL_FAVORITES_LIST);
break;
case MENU_STARTUP_PAGE_CONTENTLESS_CORES:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENTLESS_CORES_TAB),
NULL,
MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES_STR,
MENU_SETTING_ACTION,
0, 0, ACTION_OK_DL_CONTENTLESS_CORES_LIST);
break;
case MENU_STARTUP_PAGE_EXPLORE:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_TAB),
NULL,
MENU_ENUM_LABEL_GOTO_EXPLORE_STR,
MENU_EXPLORE_TAB,
0, 0, ACTION_OK_DL_EXPLORE_LIST);
break;
case MENU_STARTUP_PAGE_PLAYLISTS:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB),
NULL,
MENU_ENUM_LABEL_PLAYLISTS_TAB_STR,
MENU_SETTING_ACTION,
0, 0, ACTION_OK_DL_CONTENT_COLLECTION_LIST);
break;
case MENU_STARTUP_PAGE_LOAD_CONTENT:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_LOAD_CONTENT_LIST),
NULL,
msg_hash_to_str(MENU_ENUM_LABEL_LOAD_CONTENT_LIST),
MENU_SETTING_ACTION,
0, 0, ACTION_OK_DL_GENERIC);
break;
case MENU_STARTUP_PAGE_START_DIRECTORY:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES),
NULL,
MENU_ENUM_LABEL_FAVORITES_STR,
MENU_SETTING_ACTION_FAVORITES_DIR,
0, 0, ACTION_OK_DL_CONTENT_LIST);
break;
case MENU_STARTUP_PAGE_DOWNLOADS:
generic_action_ok_displaylist_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DOWNLOADED_FILE_DETECT_CORE_LIST),
settings->paths.directory_core_assets,
MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST_STR,
MENU_SETTING_ACTION,
0, 0, ACTION_OK_DL_CONTENT_LIST);
break;
}
if (startup_page != MENU_STARTUP_PAGE_MAIN_MENU)
{
menu_displaylist_info_t info;
menu_displaylist_info_init(&info);
info.flags |= MD_FLAG_NEED_PUSH;
menu_displaylist_process(&info);
menu_displaylist_info_free(&info);
}
menu_st->flags &= ~MENU_ST_FLAG_PENDING_STARTUP_PAGE;
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
else if ( ((menu_st->flags & MENU_ST_FLAG_PENDING_CLOSE_CONTENT)
|| (menu_st->flags & MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH))
&& !runloop_st->content_switching)
{
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *menu_stack = menu_list ? MENU_LIST_GET(menu_list, (unsigned)0) : NULL;
const char *deferred_path = menu ? menu->deferred_path : NULL;
const char *flush_target = MENU_ENUM_LABEL_MAIN_MENU_STR;
size_t stack_offset = 1;
unsigned i = 0;
bool reset_navigation = true;
/* Loop backwards through the menu stack to
* find a known reference point */
while (menu_stack && (menu_stack->size >= stack_offset))
{
const char *parent_label = menu_stack->list[
menu_stack->size - stack_offset].label;
if (!parent_label || !*parent_label)
continue;
/* If core was launched via a playlist or Explore, flush
* to playlist entry menu */
if ( ( string_is_equal(parent_label, MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS_STR)
|| string_is_equal(parent_label, MENU_ENUM_LABEL_EXPLORE_TAB_STR))
&& deferred_path && *deferred_path
)
{
if (string_is_equal(parent_label, MENU_ENUM_LABEL_EXPLORE_TAB_STR))
flush_target = MENU_ENUM_LABEL_EXPLORE_TAB_STR;
else
flush_target = MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS_STR;
break;
}
/* If core was launched via 'Contentless Cores' menu,
* flush to 'Contentless Cores' menu */
else if ( string_is_equal(parent_label,
MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB_STR)
|| string_is_equal(parent_label,
MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST_STR))
{
flush_target = parent_label;
reset_navigation = false;
break;
}
stack_offset++;
}
if (!(menu_st->flags & MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH))
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
menu_entries_flush_stack(flush_target, 0);
/* An annoyance - some menu drivers (Ozone...) set
* MENU_ST_FLAG_PREVENT_POPULATE in awkward
* places, which can cause breakage here when flushing
* the menu stack. We therefore have to unset
* MENU_ST_FLAG_PREVENT_POPULATE */
menu_st->flags &= ~MENU_ST_FLAG_PREVENT_POPULATE;
/* Single-click playlist return */
if (settings->bools.input_menu_singleclick_playlists && reset_navigation)
{
size_t new_selection = menu_st->selection_ptr;
menu_entries_pop_stack(&new_selection, 0, 0);
menu_st->selection_ptr = new_selection;
reset_navigation = false;
}
/* Ozone requires thumbnail refreshing */
if (menu_st->driver_ctx && menu_st->driver_ctx->refresh_thumbnail_image)
menu_st->driver_ctx->refresh_thumbnail_image(
menu_st->userdata, i);
if (reset_navigation)
menu_st->selection_ptr = 0;
menu_st->flags &= ~(MENU_ST_FLAG_PENDING_CLOSE_CONTENT
| MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH);
menu_st->pending_env_shutdown_content_path[0] = '\0';
/* Reload core on launch failure if manually loaded */
if ( !path_is_empty(RARCH_PATH_CORE_LAST)
&& !(menu_st->flags & MENU_ST_FLAG_PENDING_RELOAD_CORE))
{
menu_st->flags |= MENU_ST_FLAG_PENDING_RELOAD_CORE;
menu_st->flags |= MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH;
}
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
else
{
#ifdef HAVE_COMMAND
/* a MENU request's action, run as the menu's own input is */
action = (enum menu_action)command_menu_action(action);
#endif
if (!menu_driver_iterate(menu_st, p_disp, anim_get_ptr(),
settings, action, current_time))
{
if (err_on_init)
{
content_ctx_info_t content_info = {0};
task_push_start_dummy_core(&content_info);
}
else
retroarch_menu_running_finished(false);
}
#ifdef HAVE_COMMAND
command_menu_iterated();
#endif
}
/* Handle pending core reload separately after menu driver iterate */
if ( (menu_st->flags & MENU_ST_FLAG_PENDING_RELOAD_CORE)
&& !runloop_st->content_switching)
{
#ifdef HAVE_DYNAMIC
const char *a = path_get(RARCH_PATH_CORE_LAST);
#endif
menu_st->flags &= ~MENU_ST_FLAG_PENDING_RELOAD_CORE;
#ifdef HAVE_DYNAMIC
if (a && *a)
{
/* Reload the core library only. The active session at
* this point is the dummy core, so the current core
* type must remain CORE_TYPE_DUMMY. Loading through
* task_push_load_new_core() latched CORE_TYPE_PLAIN
* (and RUNLOOP_FLAG_HAS_SET_CORE) here, which made the
* frontend believe real content was running: after
* 'Close Content' on a contentless (SUPPORT_NO_GAME)
* core the menu could be toggled out into the dummy
* session, CORE_RUNNING became set, and the Main Menu
* then offered only the Quick Menu entry while
* Load/Start/Unload Core became unreachable until a
* new game was loaded or RetroArch was restarted. */
path_set(RARCH_PATH_CORE, a);
command_event(CMD_EVENT_LOAD_CORE, NULL);
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
| MENU_ST_FLAG_PREVENT_POPULATE;
}
#endif
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
if (focused || !(runloop_st->flags & RUNLOOP_FLAG_IDLE))
{
#ifdef HAVE_NETWORKING
const bool menu_pauses_core = menu_pause_libretro && netplay_allow_pause;
#else
const bool menu_pauses_core = menu_pause_libretro;
#endif
const bool libretro_running = runloop_is_libretro_running(runloop_st,
menu_pauses_core);
const bool display_rate = (runloop_menu_rate(settings,
runloop_st, menu_pauses_core)
& PACE_FACT_MENU_DISPLAY_RATE) != 0;
/* At 'Display Rate' a core running behind the menu keeps its
* own clock (see runloop_menu_core_due()); fast-forward still
* runs it every iteration, as fast as the menu goes. */
bool core_due = true;
runloop_st->menu_core_skipped = false;
if ( libretro_running
&& display_rate
&& !(runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
{
core_due = runloop_menu_core_due(runloop_st, video_st,
settings, current_time);
runloop_st->menu_core_skipped = !core_due;
}
else
runloop_st->menu_core_due_ns = 0;
runloop_menu_audio_nonblock(settings, input_st, audio_st,
display_rate);
/* menu_driver_iterate() above dispatches entry actions, which
* can tear down and recreate the menu handle (menu driver
* change, any CMD_EVENT_REINIT from an action) - the pointer
* cached at the top of this function is stale from here on.
* Re-fetch before the render block below reads and writes
* through it; the second re-fetch further down covers
* core_run() inside display_menu_libretro() invalidating it
* again. */
menu = menu_st->driver_data;
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)
&& *menu->menu_state_msg)
{
if (menu->driver_ctx->render_messagebox)
menu->driver_ctx->render_messagebox(
menu->userdata,
menu->menu_state_msg);
}
if (BIT64_GET(menu->state, MENU_STATE_BLIT))
{
if (menu->driver_ctx->render)
menu->driver_ctx->render(
menu->userdata,
output_dims,
(runloop_st->flags & RUNLOOP_FLAG_IDLE) ? true : false);
}
if ( (menu_st->flags & MENU_ST_FLAG_ALIVE)
&& !(runloop_st->flags & RUNLOOP_FLAG_IDLE))
/* A frame the core is not due presents the last one it
* made, with the menu over it */
if (display_menu_libretro(runloop_st, input_st,
settings->floats.slowmotion_ratio,
libretro_running && core_due, current_time))
video_driver_cached_frame();
/* Core execution inside display_menu_libretro() can trigger
* a driver reinit (e.g. RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO
* -> CMD_EVENT_REINIT), which frees and recreates the menu
* handle - the cached pointer must be re-fetched before it
* is dereferenced again */
if ((menu = menu_st->driver_data))
{
if (menu->driver_ctx && menu->driver_ctx->set_texture)
menu->driver_ctx->set_texture(menu->userdata);
menu->state = 0;
}
}
/* Feed the audio device one frame of silence while the core is
* not running behind the menu, so the stream never starves or
* stops: the device stays in the same state it is in during
* play, rate control keeps its footing, and menu sounds, the
* mixer and thumbnail video playback mix into this stream
* through audio_driver_flush(), which in the menu is driven only
* from here. */
if (!libretro_running)
audio_driver_menu_sample(display_rate);
}
/* Note: 'old_input' is recorded earlier, immediately after
* 'trigger_input' is derived */
/* Handle dialog confirmed event */
if (menu_st->dialog_st.pending_cmd != CMD_EVENT_NONE)
{
/* Event also wants to resume */
if (!command_event((enum event_command)menu_st->dialog_st.pending_cmd, NULL))
command_event(CMD_EVENT_RESUME, NULL);
menu_dialog_confirm_clear(menu_st);
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
if ( !focused
|| (runloop_st->flags & RUNLOOP_FLAG_IDLE)
|| (runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED))
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
else
#endif /* HAVE_MENU */
#endif /* defined(HAVE_MENU) || defined(HAVE_GFX_WIDGETS) */
{
if (runloop_st->flags & RUNLOOP_FLAG_IDLE)
{
input_driver_poll_between_frames(cbs->poll_cb);
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
}
/* Check fullscreen hotkey */
HOTKEY_CHECK(RARCH_FULLSCREEN_TOGGLE_KEY, CMD_EVENT_FULLSCREEN_TOGGLE, true, NULL);
/* Check turbo toggle hotkey */
HOTKEY_CHECK(RARCH_TURBO_FIRE_TOGGLE, CMD_EVENT_TURBO_FIRE_TOGGLE, true, NULL);
/* Check mouse grab hotkey */
HOTKEY_CHECK(RARCH_GRAB_MOUSE_TOGGLE, CMD_EVENT_GRAB_MOUSE_TOGGLE, true, NULL);
/* 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 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 laser pointer hotkey: here, so it works in the menu too */
HOTKEY_CHECK(RARCH_LASER_POINTER_TOGGLE, CMD_EVENT_LASER_POINTER_TOGGLE, true, NULL);
/* Check headset view hotkey */
HOTKEY_CHECK(RARCH_HEADSET_VIEW_TOGGLE, CMD_EVENT_HEADSET_VIEW_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
/* Check headset recenter hotkey: here, so it works in the menu too */
HOTKEY_CHECK(RARCH_HEADSET_RECENTER, CMD_EVENT_HEADSET_RECENTER, true, NULL);
#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)
#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)
{
input_driver_poll_between_frames(cbs->poll_cb);
return RUNLOOP_STATE_PAUSE;
}
if (runloop_st->rewind_st.flags & STATE_MGR_REWIND_ST_FLAG_INIT_PENDING)
runloop_rewind_init_pending(runloop_st);
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)
&& menu_pause_libretro)
#endif
,
s, sizeof(s), &t);
if (rewind_pressed != old_rewind_pressed)
{
if (settings->bools.audio_rewind_mute && rewind_pressed)
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_MUTED);
else
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_MUTED);
}
old_rewind_pressed = rewind_pressed;
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
if (rewinding && settings->bools.notification_show_fast_forward)
video_st->main_flags |= VIDEO_FLAG_WIDGETS_REWINDING;
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_REWINDING;
}
else
#endif
{
if (rewinding && settings->bools.notification_show_fast_forward)
runloop_msg_queue_push(s, strlen(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
)
{
input_driver_poll_between_frames(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;
audio_driver_pause_fade(false);
}
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(
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)
return RUNLOOP_STATE_MENU;
#endif
#ifdef HAVE_NETWORKING
if (netplay_allow_pause)
#endif
/* A core that has run no frame cannot be paused, and waiting for
* focus instead would present nothing: OpenXR gives focus only to
* a session that presents. */
if (pause_nonactive && (runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING))
focused = is_focused
|| video_driver_headset_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);
bool pause_on_disconnect = settings->bools.pause_on_disconnect;
/* Reset frameadvance pause when triggering pause */
if (pause_pressed)
pauseframeadvance = false;
/* Allow unpausing with Start */
if (runloop_paused && pause_on_disconnect)
pause_pressed |= BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_START);
#ifdef HAVE_CHEEVOS
if (cheevos_hardcore_active)
{
if (!(runloop_st->flags & RUNLOOP_FLAG_PAUSED))
{
/* In hardcore mode, the user is only allowed to pause infrequently. */
if ((pause_pressed && !old_pause_pressed) ||
(!focused && old_focus && pause_nonactive))
{
/* If the user is trying to pause, check to see if it's allowed. */
if (!rcheevos_is_pause_allowed())
{
pause_pressed = false;
if (pause_nonactive)
focused = true;
}
}
}
}
else /* frame advance not allowed in hardcore */
#endif
{
frameadvance_pressed = BIT256_GET(current_bits, RARCH_FRAMEADVANCE);
/* RUN_FRAMES advances one frame each iteration until through */
frameadvance_trigger = (frameadvance_pressed && !old_frameadvance)
|| (runloop_st->frame_work & RUNLOOP_WORK_RUN_FRAMES);
/* 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(
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;
#ifdef HAVE_CHEEVOS
else if (!cheevos_hardcore_active)
#else
else
#endif
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
if (!focused && !runloop_paused)
{
input_driver_poll_between_frames(cbs->poll_cb);
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
/* Do delayed disk insert when disk is changed without ejecting */
if (runloop_st->pending_disk_control_insert)
{
runloop_st->pending_disk_control_insert--;
if (!runloop_st->pending_disk_control_insert)
{
rarch_system_info_t *sys_info = &runloop_st->system;
if (sys_info)
disk_control_set_eject_state(&sys_info->disk_control, false, true);
}
}
/* Apply any pending fastmotion override parameters */
if (runloop_st->fastmotion_override.pending)
{
runloop_apply_fastmotion_override(runloop_st,
settings->bools.frame_time_counter_auto_reset,
settings->floats.fastforward_ratio,
settings->bools.audio_fastforward_mute);
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 (check2 && runloop_paused)
{
new_button_state = false;
new_hold_button_state = false;
input_st->flags |= INP_FLAG_NONBLOCKING;
}
#ifdef HAVE_NETWORKING
if (check2 && !netplay_allow_timeskip)
check2 = false;
#endif
if (check2)
{
runloop_apply_fastmotion(
!(input_st->flags & INP_FLAG_NONBLOCKING));
}
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->main_flags |= VIDEO_FLAG_WIDGETS_FASTMOTION;
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_FASTMOTION;
}
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_FASTMOTION;
}
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)
{
const char *_msg = msg_hash_to_str(MSG_FAST_FORWARD);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 1, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
}
#if defined(HAVE_GFX_WIDGETS)
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_FASTMOTION;
#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;
}
#ifdef HAVE_NETWORKING
if ((runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
&& !netplay_allow_timeskip)
runloop_st->flags &= ~RUNLOOP_FLAG_SLOWMOTION;
#endif
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)
{
const char *_msg = msg_hash_to_str(MSG_SLOW_MOTION_REWIND);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 1, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
else
#endif
{
const char *_msg = msg_hash_to_str(MSG_SLOW_MOTION);
runloop_msg_queue_push(_msg, strlen(_msg), 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;
}
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
if (settings->bools.notification_show_fast_forward)
{
if (runloop_st->flags & RUNLOOP_FLAG_SLOWMOTION)
video_st->main_flags |= VIDEO_FLAG_WIDGETS_SLOWMOTION;
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_SLOWMOTION;
}
else
video_st->main_flags &= ~VIDEO_FLAG_WIDGETS_SLOWMOTION;
}
#endif
}
/* Check save state slot hotkeys */
{
int state_slot = settings->ints.state_slot;
static bool old_slot_increase = false;
static bool old_slot_decrease = false;
bool slot_increase = BIT256_GET(current_bits, RARCH_STATE_SLOT_PLUS);
bool slot_decrease = BIT256_GET(current_bits, RARCH_STATE_SLOT_MINUS);
bool check = false;
if (slot_increase && !old_slot_increase)
{
check = true;
state_slot++;
/* Wrap-around to 0 */
if (state_slot > 999)
state_slot = 0;
}
else if (slot_decrease && !old_slot_decrease)
{
check = true;
state_slot--;
/* Wrap to Auto */
if (state_slot < 0)
state_slot = -1;
}
if (check)
{
size_t _len;
char msg[128];
configuration_set_int(settings, settings->ints.state_slot, state_slot);
_len = strlcpy(msg, msg_hash_to_str(MSG_STATE_SLOT), sizeof(msg));
_len += snprintf(msg + _len, sizeof(msg) - _len, ": %d", state_slot);
#ifdef HAVE_GFX_WIDGETS
#ifdef HAVE_SCREENSHOTS
if (dispwidget_get_ptr()->active && settings->bools.savestate_thumbnail_enable)
{
char path[PATH_MAX_LENGTH];
gfx_savestate_thumbnail_get_path(path, sizeof(path),
runloop_st->name.savestate, state_slot);
if (state_slot < 0)
snprintf(msg, sizeof(msg), "%s", "Auto");
else
snprintf(msg, sizeof(msg), "%d", state_slot);
gfx_widget_state_slot_show(dispwidget_get_ptr(), msg, path);
}
else
#endif
if (dispwidget_get_ptr()->active)
gfx_widget_set_generic_message(msg, 1000);
else
#endif
runloop_msg_queue_push(msg, _len, 2, 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
old_slot_increase = slot_increase;
old_slot_decrease = 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));
_len += snprintf(msg + _len, sizeof(msg) - _len,
": %d", settings->ints.replay_slot);
if (cur_replay_slot < 0)
_len += strlcpy_lit(msg + _len, " (Auto)", sizeof(msg) - _len);
#ifdef HAVE_GFX_WIDGETS
if (dispwidget_get_ptr()->active)
gfx_widget_set_generic_message(msg, 1000);
else
#endif
runloop_msg_queue_push(msg, _len, 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 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);
HOTKEY_CHECK(RARCH_SAVE_REPLAY_CHECKPOINT_KEY, CMD_EVENT_SAVE_REPLAY_CHECKPOINT, true, NULL);
HOTKEY_CHECK(RARCH_PREV_REPLAY_CHECKPOINT_KEY, CMD_EVENT_PREV_REPLAY_CHECKPOINT, true, NULL);
HOTKEY_CHECK(RARCH_NEXT_REPLAY_CHECKPOINT_KEY, CMD_EVENT_NEXT_REPLAY_CHECKPOINT, 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);
#ifdef HAVE_VIDEO_FILTER
/* Check Video Filter hotkey */
HOTKEY_CHECK(RARCH_VIDEO_FILTER_TOGGLE, CMD_VIDEO_FILTER_TOGGLE, true, NULL);
#endif
#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);
{
/* Check shader hold hotkey */
static bool old_shader_hold_button_state = false;
bool new_shader_hold_button_state = BIT256_GET(
current_bits, RARCH_SHADER_HOLD);
if (old_shader_hold_button_state != new_shader_hold_button_state)
command_event(CMD_EVENT_SHADER_TOGGLE, NULL);
old_shader_hold_button_state = new_shader_hold_button_state;
}
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)
{
input_driver_poll_between_frames(cbs->poll_cb);
return RUNLOOP_STATE_PAUSE;
}
#if HAVE_MENU
if (menu_was_alive && (runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING))
return RUNLOOP_STATE_MENU;
#endif
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.
**/
/* Recovery from a lost GPU device, with a bound on how hard it is
* tried. A device lost once - a TDR, a driver update, a GPU reset -
* comes back on the first rebuild, and that rebuild happens at once.
* A device that is lost again straight after being rebuilt is a
* driver or hardware that is failing, and rebuilding every frame on
* it is a busy loop of full driver reinitialisations, each of which
* fails: the retries back off instead, doubling from one second, and
* after a run of them the rebuild is abandoned with a message rather
* than attempted forever. A loss well clear of the previous one (a
* minute or more) is a fresh incident and starts the count again. */
#define GPU_LOST_RETRY_BASE_USEC 1000000
#define GPU_LOST_RETRY_MAX_USEC 16000000
#define GPU_LOST_GIVE_UP_AFTER 6
#define GPU_LOST_FRESH_AFTER_USEC 60000000
static void runloop_gpu_device_lost(runloop_state_t *runloop_st)
{
retro_time_t now = cpu_features_get_time_usec();
int reinit_flags = DRIVER_VIDEO_MASK | DRIVER_INPUT_MASK
| DRIVER_MENU_MASK;
/* Not yet due: leave the flag set and try again on a later frame */
if (now < runloop_st->gpu_lost_retry_at)
return;
video_driver_modify_disp_flags(0, VIDEO_FLAG_GPU_DEVICE_LOST);
if ( runloop_st->gpu_lost_count
&& now - runloop_st->gpu_lost_last > GPU_LOST_FRESH_AFTER_USEC)
runloop_st->gpu_lost_count = 0;
runloop_st->gpu_lost_last = now;
runloop_st->gpu_lost_count++;
if (runloop_st->gpu_lost_count > GPU_LOST_GIVE_UP_AFTER)
{
const char *msg = "The GPU device keeps being lost; giving up on recovering the video driver.";
RARCH_ERR("[Video] %s\n", msg);
runloop_msg_queue_push(msg, strlen(msg), 1, 240, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
/* Look again only after a fresh incident's worth of time */
runloop_st->gpu_lost_retry_at = now + GPU_LOST_FRESH_AFTER_USEC;
return;
}
if (runloop_st->gpu_lost_count > 1)
{
retro_time_t delay = (retro_time_t)GPU_LOST_RETRY_BASE_USEC
<< (runloop_st->gpu_lost_count - 2);
if (delay > GPU_LOST_RETRY_MAX_USEC)
delay = GPU_LOST_RETRY_MAX_USEC;
runloop_st->gpu_lost_retry_at = now + delay;
RARCH_ERR("[Video] The GPU device was lost again (%u in a row); "
"next rebuild in %u ms.\n",
runloop_st->gpu_lost_count, (unsigned)(delay / 1000));
}
else
{
runloop_st->gpu_lost_retry_at = 0;
RARCH_ERR("[Video] The GPU device was lost; reinitialising the video driver.\n");
}
command_event(CMD_EVENT_REINIT, &reinit_flags);
}
int runloop_iterate(void)
{
retro_time_t pace_limit_min;
retro_time_t pace_now;
int64_t pace_limit_ns;
runloop_pace_facts_t pace_facts;
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 *rec_st = recording_state_get_ptr();
camera_driver_state_t *camera_st = camera_state_get_ptr();
uico_driver_state_t *uico_st = uico_state_get_ptr();
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = &runloop_state;
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
retro_time_t current_time = cpu_features_get_time_usec();
#ifdef HAVE_NETWORKING
bool netplay_is_enabled = netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
bool netplay_allow_timeskip = netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL);
bool netplay_allow_pause = netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
#else
bool netplay_allow_timeskip = false;
bool netplay_allow_pause = false;
#endif
#ifdef HAVE_MENU
bool menu_pause_libretro = settings->bools.menu_pause_libretro && netplay_allow_pause;
bool core_paused =
!!(runloop_st->flags & RUNLOOP_FLAG_PAUSED)
|| (menu_pause_libretro && (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE));
#else
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
bool core_paused = !!(runloop_st->flags & RUNLOOP_FLAG_PAUSED);
#endif
float slowmotion_ratio = settings->floats.slowmotion_ratio;
bool audio_sync = settings->bools.audio_sync;
bool frame_advanced = false;
bool savestate_automatic_enable = settings->uints.savestate_automatic_interval > 0;
recording_gpu_poll();
runloop_msg_queue_drain_deferred();
/* A quit waiting for its state to be written: the core is not run
* and nothing is read but the input the window needs drained; the
* last frame and the "Saving state" notice are presented, paced as
* any frame is, until the quit is finished and the loop ends. */
if (runloop_st->quit_pending)
{
if (retroarch_main_quit_poll())
{
runloop_st->frame_limit_anchor_ns = 0;
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_RUNNING;
return -1;
}
input_driver_poll_between_frames(input_driver_poll);
video_driver_cached_frame();
return 1;
}
/* A video driver that lost its GPU device - a TDR on Windows, a GPU
* reset elsewhere - sets this from whichever thread saw it, and
* nothing it does on that device works from then on: every frame
* fails, and the window shows whatever the last good present left.
* Rebuilding the video driver here, on the thread that owns
* drivers, is the recovery: the context comes back on the
* recovered device, and a hardware core gets context_reset. */
if ((uint32_t)retro_atomic_load_relaxed_int(&video_st->flags)
& VIDEO_FLAG_GPU_DEVICE_LOST)
runloop_gpu_device_lost(runloop_st);
if ((uint32_t)retro_atomic_load_relaxed_int(&video_st->flags)
& VIDEO_FLAG_DRIVER_REINIT)
{
int reinit_flags = DRIVER_VIDEO_MASK | DRIVER_INPUT_MASK
| DRIVER_MENU_MASK;
video_driver_modify_disp_flags(0, VIDEO_FLAG_DRIVER_REINIT);
RARCH_LOG("[Video] Reinitialising the video driver at its request.\n");
command_event(CMD_EVENT_REINIT, &reinit_flags);
}
#ifdef HAVE_DISCORD
if (runloop_st->frame_work & RUNLOOP_WORK_DISCORD)
discord_poll(current_time);
#endif
#ifdef HAVE_BSV_MOVIE
if (input_st->bsv_movie_state_next_handle)
bsv_movie_dequeue_next(input_st);
#endif
#ifdef ANDROID
/* Outside the core. APP_CMD_PAUSE is read by the input driver's poll,
* which a core enters from within retro_run(), so the save it asks for
* is performed here instead of where the command arrives. */
android_input_flush_pending_state();
/* Same poll, same reason: entering Java is only safe on the OS
* stack, and a libco core reaches the poll on its own. */
android_input_flush_pending_haptics();
#endif
/* Tick deferred shader compilation (one pass per frame) */
video_driver_shader_deferred_tick();
#ifdef HAVE_OPENXR
video_driver_headset_poll();
#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))
| !!rec_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_FLAGS_GET(audio_st) & 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);
}
/* A driver's request to be reinitialised - a device change or an
* unplug - is taken here, once a frame, on the main thread with no
* audio lock held: the reinit frees the state lock and joins the
* audio thread, so it cannot run from a flush or from that thread. */
if (audio_driver_take_reinit_request())
{
RARCH_LOG("[Audio] Driver reinit requested...\n");
command_event(CMD_EVENT_AUDIO_REINIT, NULL);
#ifdef HAVE_MICROPHONE
command_event(CMD_EVENT_MICROPHONE_REINIT, NULL);
#endif
}
#ifdef HAVE_THREADS
if (runloop_st->flags & RUNLOOP_FLAG_AUTOSAVE)
autosave_check();
#endif
#ifdef HAVE_COMMAND
/* before the frame is decided: a STEPS checkpoint with the core off
* the stack, and a list it ends runs no frame past it */
if (runloop_st->frame_work & RUNLOOP_WORK_RUN_FRAMES)
command_run_frames_between();
#endif
switch ((enum runloop_state_enum)runloop_check_state(
input_st, audio_st, video_st,
uico_st,
((retroarch_get_flags() & RARCH_FLAGS_ERR_ON_INIT) > 0),
settings, current_time, netplay_allow_pause,
netplay_allow_timeskip))
{
case RUNLOOP_STATE_QUIT:
runloop_st->frame_limit_anchor_ns = 0;
runloop_st->flags &= ~RUNLOOP_FLAG_CORE_RUNNING;
command_event(CMD_EVENT_QUIT, NULL);
/* Waiting for a state to be written: the frames go on until
* it is, at the top of this function. */
if (runloop_st->quit_pending)
return 1;
return -1;
case RUNLOOP_STATE_POLLED_AND_CONTINUE:
runloop_st->pace = RUNLOOP_PACE_NONE;
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_WROTE);
/* Config replaced, startup page pushed, core loaded, dialog
* command run, list cache flushed: one-shot work is done and
* the next iteration should begin fresh. Nothing to wait for;
* the 10 ms below was a visible stall on every one of them. */
if (runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED)
return -1;
return 1;
case RUNLOOP_STATE_POLLED_AND_SLEEP:
runloop_st->pace = RUNLOOP_PACE_NONE;
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_WROTE);
if (runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED)
return -1;
#ifdef HAVE_NETWORKING
/* FIXME: This is an ugly way to tell Netplay this... */
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
#if defined(__EMSCRIPTEN__) && !defined(EMSCRIPTEN_ASYNCIFY) && !defined(PROXY_TO_PTHREAD)
/* A deferred main-loop timeout: the browser's yield, not a
* wait of ours. */
platform_emscripten_deferred_sleep(10);
#else
#if defined(ANDROID)
/* When IDLE, android_input_poll() already blocked on the looper
* until the OS sent something; a wait on top only delays the
* response to it. Unfocused-but-foreground still waits. */
if (!(runloop_st->flags & RUNLOOP_FLAG_IDLE))
#endif
runloop_idle_wait();
#endif
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
#ifdef HAVE_BSV_MOVIE
if (input_st->bsv_movie_state.flags &
(BSV_FLAG_MOVIE_FORCE_CHECKPOINT |
BSV_FLAG_MOVIE_PREV_CHECKPOINT |
BSV_FLAG_MOVIE_NEXT_CHECKPOINT |
BSV_FLAG_MOVIE_SEEK_TO_FRAME))
{
runloop_st->flags &= ~RUNLOOP_FLAG_PAUSED;
runloop_st->run_frames_and_pause = 2;
audio_driver_pause_fade(false);
}
#endif
video_driver_cached_frame();
goto end;
case RUNLOOP_STATE_MENU:
#if defined(HAVE_MENU) && defined(HAVE_NETWORKING)
/* FIXME: This is an ugly way to tell Netplay this... */
if (menu_pause_libretro && netplay_is_enabled)
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
#ifdef HAVE_CHEEVOS
if (runloop_st->frame_work & RUNLOOP_WORK_CHEEVOS)
{
if (runloop_is_libretro_running(runloop_st, menu_pause_libretro))
{
if (!runloop_st->menu_core_skipped)
rcheevos_test();
}
else
rcheevos_idle();
}
#endif
#ifdef HAVE_MENU
{
unsigned menu_rate = runloop_menu_rate(settings, runloop_st,
menu_pause_libretro);
/* Menu Frame Rate 'Content Rate', the core paused behind the
* menu: the content's period. The silence the menu feeds holds
* the loop there when audio blocks; when it does not, the
* timer does (see runloop_pace_sources()). */
if (menu_rate & PACE_FACT_MENU_CONTENT_RATE)
{
runloop_set_frame_limit(&video_st->av_info, 1.0f);
goto end;
}
/* When content is actively running behind the menu (menu_pause_libretro
* is off), core_run() -> audio_driver_write() already paces the iterate
* loop at the audio buffer's drain rate -- i.e. the core's natural fps.
* Layering the refresh-rate timer throttle below on top of that is
* redundant double-pacing: two clocks holding one loop drift
* against each other and the slower one wins, so the sleep lands
* after the audio low-water mark and stutters audio. Defer
* pacing to the audio backpressure path. Not at Menu Frame Rate
* 'Display Rate': there the core runs on its own clock, its
* audio does not block, and the menu takes the display's pace
* below. */
else if ( audio_sync
&& !(menu_rate & PACE_FACT_MENU_DISPLAY_RATE)
&& runloop_is_libretro_running(runloop_st, menu_pause_libretro))
{
/* Make sure no stale frame_limit_minimum_time from a prior
* iteration (e.g. just before menu_pause_libretro was toggled
* off) leaks into the sleep block below. */
runloop_st->frame_limit_minimum_time = 0;
runloop_st->frame_limit_minimum_time_ns = 0;
goto end;
}
else if (( (settings->bools.video_vsync)
|| (settings->bools.video_scanline_sync))
&& (runloop_st->flags & RUNLOOP_FLAG_FOCUSED)
#ifdef HAVE_THREADS
/* Behind the threaded video wrapper vsync holds the
* video thread, not this one: at 'Display Rate' the
* timer below holds the menu instead */
&& !( (menu_rate & PACE_FACT_MENU_DISPLAY_RATE)
&& video_st->thread_wrapper_active)
#endif
)
goto end;
/* Otherwise run menu in video refresh rate speed. */
if (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
{
/* At 'Display Rate', the rate of the monitor the window
* is on */
float hz = runloop_display_hz(video_st, settings,
(menu_rate & PACE_FACT_MENU_DISPLAY_RATE) != 0);
runloop_st->frame_limit_minimum_time = (retro_time_t)
roundf(1000000.0f / hz);
runloop_st->frame_limit_minimum_time_ns =
runloop_content_frame_time_ns(hz);
}
else
runloop_set_frame_limit(&video_st->av_info, settings->floats.fastforward_ratio);
}
#endif
goto end;
case RUNLOOP_STATE_ITERATE:
/* A staged content load has no core to run between its
* stages: poll, present what is on screen, and pace as a
* paused frame does. */
if (runloop_st->content_switching)
{
input_driver_poll_between_frames(input_driver_poll);
video_driver_cached_frame();
goto end;
}
runloop_st->flags |= RUNLOOP_FLAG_CORE_RUNNING;
break;
}
#ifdef HAVE_THREADS
if (runloop_st->flags & RUNLOOP_FLAG_AUTOSAVE)
autosave_lock();
#endif
/* driver_camera_start() raised the bit only with a pollable
* driver and registered caps; stop, teardown and a camera_allow
* change drop it. */
if (runloop_st->frame_work & RUNLOOP_WORK_CAMERA)
camera_st->driver->poll(camera_st->data,
camera_st->cb.frame_raw_framebuffer,
camera_st->cb.frame_opengl_texture,
camera_st->cb.frame_planar);
#ifdef HAVE_OPENXR
if (runloop_st->core_vr_content)
video_driver_vr_sample_tracking();
#endif
/* Measure the time between core_run() and video_driver_frame() */
runloop_st->core_run_time = cpu_features_get_time_usec();
/* Whether this frame is presented, before the core makes it: a core
* that asks (GET_AUDIO_VIDEO_ENABLE) need not render a frame that
* fast-forward drops */
video_driver_ff_frameskip_decide(runloop_st->core_run_time);
{
#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_is_enabled;
#endif
if (want_runahead)
frame_advanced = runahead_run(
runloop_st,
run_ahead_num_frames,
run_ahead_hide_warnings,
run_ahead_secondary_instance);
else if (runloop_st->preempt_data)
frame_advanced = preempt_run(runloop_st->preempt_data, runloop_st);
else
#endif
frame_advanced = core_run();
}
runloop_load_deferred_auto_state();
#ifdef HAVE_COMMAND
if (frame_advanced && (runloop_st->frame_work & RUNLOOP_WORK_RUN_FRAMES))
command_run_frames_tick();
else if (frame_advanced
&& (runloop_st->frame_work & RUNLOOP_WORK_FREE_FRAME))
command_run_frames_free_frame();
/* LOAD_CONTENT ... paused, from no running core: pausing waits for
* the first frame, which is what marks a core running */
if (frame_advanced && (runloop_st->frame_work & RUNLOOP_WORK_PAUSE_LOADED))
{
runloop_frame_work_set(RUNLOOP_WORK_PAUSE_LOADED, false);
command_event(CMD_EVENT_PAUSE, NULL);
}
#else
(void)frame_advanced;
#endif
/* 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 (runloop_st->frame_work & RUNLOOP_WORK_CHEEVOS)
rcheevos_test();
#endif
#ifdef HAVE_CHEATS
if (runloop_st->frame_work & RUNLOOP_WORK_CHEATS)
cheat_manager_apply_retro_cheats();
#endif
/* The core's rumble and LED calls stored what it asked for; write
* the frame's last values now that it is off the stack. After the
* cheats, which can rumble too. One load and a compare each when
* nothing was set. */
input_driver_flush_rumble();
led_driver_flush();
/* a first press seen by this frame's poll: the user gets its core
* port now, with the core off the stack */
if (input_st->first_press_pending)
input_first_press_apply();
#ifdef HAVE_PRESENCE
/* "In a game" does not change at the core rate; the sinks
* (Discord at 10 Hz, Steam) are written on the same cadence. */
if (runloop_st->frame_work & RUNLOOP_WORK_PRESENCE)
presence_poll(PRESENCE_GAME, current_time);
#endif
#ifdef HAVE_BSV_MOVIE
/* The movie handle lives on input_st, which this frame already
* has in cache; no bit needed. */
if (input_st->bsv_movie_state_handle)
{
if (!BSV_MOVIE_IS_NETPLAY(input_st->bsv_movie_state_handle))
bsv_movie_next_frame(input_st,
settings->uints.replay_checkpoint_interval,
settings->bools.replay_checkpoint_deserialize);
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)
{
bool netplay_movie = BSV_MOVIE_IS_NETPLAY(
input_st->bsv_movie_state_handle);
movie_stop(input_st);
if (!netplay_movie)
command_event(CMD_EVENT_PAUSE, NULL);
}
}
#endif
#ifdef HAVE_THREADS
if (runloop_st->flags & RUNLOOP_FLAG_AUTOSAVE)
autosave_unlock();
#endif
/* Check if we should save state automatically */
if (savestate_automatic_enable)
content_save_state_automatic(current_time);
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_FLAGS_GET(audio_st) & AUDIO_FLAG_ACTIVE)
&& (audio_st->context_audio_data))
audio_driver_set_nonblock_state(true);
}
runloop_st->fastforward_after_frames++;
if (runloop_st->fastforward_after_frames == 6)
{
/* Blocking audio */
if ( (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_ACTIVE)
&& (audio_st->context_audio_data))
audio_driver_set_nonblock_state(audio_sync ? false : true);
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));
/* The menu at Menu Frame Rate 'Display Rate' keeps the display's
* period the menu path set; the content's is for the content. */
else if (!(runloop_menu_rate(settings, runloop_st, menu_pause_libretro)
& PACE_FACT_MENU_DISPLAY_RATE))
runloop_set_frame_limit(&video_st->av_info, 1.0f);
}
/* Record which sources held the pace this iteration. The other three
* block inside their own subsystems; only the timer is decided here.
* See enum runloop_pace_source.
*
* Computed at the end of the iteration and read by the overlay during
* the next one - one frame stale by design. It must not be reset at
* the top of the iteration: the overlay reads it from inside
* video_driver_frame(), which runs between the top and here, and a
* reset there made it read NONE on every frame. Paths that return
* before this block set it themselves, through the same function. */
/* How long the last iteration actually took, smoothed. One clock
* read on a path that already takes several, and the only way to
* tell a source that is holding the loop from one that merely says
* it is - headless SDL2 with no vblank sets the vsync bit and
* blocks on nothing.
*
* The reading stands for the whole block below: the pace gather and
* the decision between here and the limiter are flag tests, so the
* frame's end is this instant and the limiter schedules against it
* rather than taking the clock a second time. */
pace_now = cpu_features_get_time_usec();
if (runloop_st->pace_iter_last)
{
retro_time_t delta = pace_now - runloop_st->pace_iter_last;
/* Samples longer than a quarter second are not pacing, they
* are a stall - a state load, a shader rebuild, a menu that
* blocked - and one of them dragged an eight-sample average
* from 60 fps to 8 in testing, taking several frames to
* recover. Nothing that is really holding the loop runs
* slower than 4 fps, so they are dropped rather than
* smoothed. */
if (runloop_pace_sample_usable(delta))
{
if (runloop_st->pace_period_usec)
runloop_st->pace_period_usec +=
(delta - runloop_st->pace_period_usec) / 8;
else
runloop_st->pace_period_usec = delta;
}
}
runloop_st->pace_iter_last = pace_now;
/* What the frame limiter below will pace to. Normally the
* fast-forward limit; replaced by the content frame time when
* nothing else is pacing at all (see below). */
pace_limit_min = runloop_st->frame_limit_minimum_time;
pace_limit_ns = runloop_st->frame_limit_minimum_time_ns;
/* One decision from this iteration's facts, gathered once - see
* runloop_pace_decide() in runloop.h, and the table in
* samples/runloop/pacing that pins it row by row. The audio write
* flag is read by the gather and cleared here. */
pace_facts = runloop_pace_gather(settings, menu_pause_libretro,
frame_advanced);
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_WROTE);
runloop_st->pace = runloop_pace_sources(pace_facts);
/* Nothing to present to - a minimised or zero-sized window, a
* surface the compositor has suspended, a swapchain that could not
* be created. The frame goes nowhere and nothing display-side can
* hold the loop; left alone, it spins. The wait belongs here, with
* the rest of the pacing, and only when nothing else holds the
* loop - audio still blocks with the window hidden, and fast-forward
* is meant to run unthrottled. */
if (runloop_pace_no_window(runloop_st->pace, pace_facts))
runloop_st->pace |= RUNLOOP_PACE_NOWINDOW;
if (runloop_st->pace & RUNLOOP_PACE_NOWINDOW)
{
/* One frame of content time, so a window that comes back is
* noticed within a frame and the core keeps its own rate while
* hidden. */
retro_sleep_us((unsigned)runloop_content_frame_time_us(video_st->core_hz));
return 1;
}
/* Nothing at all is holding the loop: vsync off, audio sync off or
* not writing, no scanline lock, and no fast-forward limit to fall
* back on - frame_limit_minimum_time is zero whenever the ratio is
* "unlimited", which is the default, so the timer clause above
* cannot engage however slowly the core is running. The timer holds
* the loop to the display rate, which audio rate control can follow
* and which Scanline Sync is aiming at while it recalibrates. */
if (runloop_pace_gap_engages(runloop_st->pace,
(pace_facts & PACE_FACT_NONBLOCKING) != 0,
(pace_facts & PACE_FACT_FASTMOTION) != 0,
(pace_facts & PACE_FACT_SCANLINE_SYNC) != 0,
(pace_facts & PACE_FACT_RATE_CONTROL) != 0))
{
runloop_st->pace |= RUNLOOP_PACE_TIMER;
pace_limit_min = runloop_content_frame_time_us(
(video_st->video_refresh_rate_original)
? video_st->video_refresh_rate_original
: settings->floats.video_refresh_rate);
pace_limit_ns = runloop_content_frame_time_ns(
(video_st->video_refresh_rate_original)
? video_st->video_refresh_rate_original
: settings->floats.video_refresh_rate);
}
/* The low-latency backend releases the next frame: the hold is the
* driver's signal, taken here where the frame delay would have
* slept, before the next iteration samples input. The timer's
* period, where the timer would hold, goes to it as the minimum
* present interval; nothing is slept twice. A release the driver
* could not give leaves the frame to the pacing below, and the
* limiter's schedule restarts rather than catching up. */
if (runloop_st->pace & RUNLOOP_PACE_DRIVER)
{
retro_time_t period = runloop_content_frame_time_us(video_st->core_hz);
/* The interval the next frame's sleep is to keep, carried to the
* presenter with that frame */
video_st->driver_pacing_interval_us = runloop_pace_driver_interval(
runloop_st->pace, pace_limit_min, period);
if (video_st->poke->driver_pacing_wait(video_st->data,
runloop_st->driver_pacing_frame_id,
runloop_st->driver_pacing_next_id + 1,
(unsigned)(2 * period + 10000)))
{
uint64_t id = ++runloop_st->driver_pacing_next_id;
runloop_st->driver_pacing_frame_id = id;
video_st->driver_pacing_id = id;
runloop_st->frame_limit_anchor_ns = 0;
if (video_st->poke->driver_pacing_mark)
video_st->poke->driver_pacing_mark(video_st->data, id,
VIDEO_DRIVER_PACING_MARK_SIMULATION_START);
goto driver_paced;
}
runloop_st->driver_pacing_frame_id = 0;
video_st->driver_pacing_id = 0;
runloop_st->pace &= ~RUNLOOP_PACE_DRIVER;
}
/* if there's a fast forward limit, inject sleeps to keep from going too fast. */
{
retro_time_t frame_limit_min = pace_limit_min;
if (runloop_st->pace & RUNLOOP_PACE_TIMER)
{
const retro_time_t end_frame_time = pace_now;
const retro_time_t to_sleep_us = runloop_pace_schedule(
&runloop_st->frame_limit_anchor_ns,
pace_limit_ns ? pace_limit_ns : (int64_t)frame_limit_min * 1000,
end_frame_time);
if (to_sleep_us > 0)
{
#if defined(__EMSCRIPTEN__) && !defined(EMSCRIPTEN_ASYNCIFY) && !defined(PROXY_TO_PTHREAD)
/* Emscripten paces through a deferred main loop timeout
* that is expressed in whole milliseconds, so it cannot
* act on a sub-millisecond remainder, nor spin. */
platform_emscripten_deferred_sleep((int)(to_sleep_us / 1000));
#else
/* Sleep short of the deadline by the measured margin,
* then spin the remainder: the sleep decides how much is
* spun, the clock decides where the frame lands. The
* sleep is absolute - retro_sleep_until_us re-arms
* against the deadline itself - so the time between
* reading the clock and entering the kernel, and any
* early or interrupted wake, no longer land in the
* margin; what the margin measures now is purely the
* kernel's own overshoot, which is what it was for. */
const retro_time_t deadline = runloop_st->frame_limit_anchor_ns / 1000;
retro_time_t now = end_frame_time;
if (to_sleep_us > runloop_st->frame_limit_margin)
{
const retro_time_t asked_until =
deadline - runloop_st->frame_limit_margin;
retro_sleep_until_us(asked_until);
now = cpu_features_get_time_usec();
runloop_st->frame_limit_margin = runloop_pace_margin_update(
runloop_st->frame_limit_margin,
now - asked_until, frame_limit_min);
}
while (now < deadline)
{
retro_cpu_relax();
now = cpu_features_get_time_usec();
}
#endif
return 1;
}
}
}
/* Frame delay. */
if ( !(input_st->flags & INP_FLAG_NONBLOCKING)
|| (runloop_st->flags & RUNLOOP_FLAG_FASTMOTION))
video_frame_delay(video_st, settings);
driver_paced:
/* 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;
audio_driver_pause_fade(true);
}
}
return 0;
}
void runloop_msg_queue_deinit(void)
{
runloop_state_t *runloop_st = &runloop_state;
#ifdef HAVE_THREADS
{
mpsc_stack_node_t *link =
mpsc_stack_drain(&runloop_st->msg_queue_deferred);
while (link)
{
struct runloop_deferred_msg *node =
(struct runloop_deferred_msg *)link;
link = link->next;
free(node->msg);
free(node->title);
free(node);
}
}
#endif
msg_queue_deinitialize(&runloop_st->msg_queue);
runloop_st->msg_queue_size = 0;
}
void runloop_msg_queue_drain_deferred(void)
{
#ifdef HAVE_THREADS
runloop_state_t *runloop_st = &runloop_state;
mpsc_stack_node_t *link = NULL;
if (mpsc_stack_empty(&runloop_st->msg_queue_deferred))
return;
/* The stack chains newest-first; replay oldest-first. */
link = mpsc_stack_reverse(
mpsc_stack_drain(&runloop_st->msg_queue_deferred));
while (link)
{
struct runloop_deferred_msg *node =
(struct runloop_deferred_msg *)link;
link = link->next;
if (node->core_status)
{
/* The STATUS slot's own rule, applied here on the main
* thread: overwrite only at the same or higher priority. */
if ( !runloop_st->core_status_msg.set
|| (runloop_st->core_status_msg.priority <= node->prio))
{
if (node->msg && *node->msg)
{
/* The same store the direct path makes, for the same
* reason: the guard bites on the held priority. */
strlcpy(runloop_st->core_status_msg.str, node->msg,
sizeof(runloop_st->core_status_msg.str));
runloop_st->core_status_msg.priority = node->prio;
runloop_st->core_status_msg.duration = (float)node->duration;
runloop_st->core_status_msg.set = true;
}
else
{
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;
}
}
}
else if (node->msg)
runloop_msg_queue_push(node->msg, node->len, node->prio,
node->duration, node->flush, node->title,
node->icon, node->category);
free(node->msg);
free(node->title);
free(node);
}
#endif
}
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
mpsc_stack_init(&runloop_st->msg_queue_deferred);
runloop_st->msg_queue_main_id = sthread_get_current_thread_id();
#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
dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
bool widgets_active = p_dispwidget->active;
/* An empty message only completes an existing widget's task link. */
if (widgets_active)
{
if (*msg)
{
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,
strlen(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) ? true : false
#else
false
#endif
);
}
else
#endif
if (*msg)
runloop_msg_queue_push(msg, strlen(msg), prio, duration, flush, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
bool runloop_get_current_savestate_path(char *s, size_t len)
{
settings_t *settings = config_get_ptr();
int state_slot = settings ? settings->ints.state_slot : 0;
return runloop_get_savestate_path(s, len, state_slot);
}
bool runloop_get_savestate_path(char *s, size_t len, int state_slot)
{
runloop_state_t *runloop_st = &runloop_state;
const char *name_savestate = NULL;
if (!s)
return false;
name_savestate = runloop_st->name.savestate;
if (!name_savestate || !*name_savestate)
return false;
if (state_slot < 0)
fill_pathname_join_delim(s, name_savestate, "auto", '.', len);
else
{
size_t _len = strlcpy(s, name_savestate, len);
if (state_slot > 0)
snprintf(s + _len, len - _len, "%d", state_slot);
}
return true;
}
bool runloop_get_replay_path(char *s, size_t len, int slot)
{
size_t _len;
runloop_state_t *runloop_st = &runloop_state;
const char *name_replay = NULL;
if (!s)
return false;
name_replay = runloop_st->name.replay;
if (!name_replay || !*name_replay)
return false;
_len = strlcpy(s, name_replay, len);
if (slot >= 0)
snprintf(s + _len, len - _len, "%d", slot);
return true;
}
bool runloop_get_entry_state_path(char *s, size_t len, int slot)
{
size_t _len;
runloop_state_t *runloop_st = &runloop_state;
const char *name_savestate = NULL;
if (!s)
return false;
name_savestate = runloop_st->name.savestate;
if (!name_savestate || !*name_savestate)
return false;
_len = strlcpy(s, name_savestate, len);
snprintf(s + _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_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;
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_USE_CORE_PACKET_INTERFACE, NULL))
{
/* 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);
/* The float batch entry the core holds by pointer is gated, not
* swapped. */
audio_driver_set_float_gate(audio_float_gate_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);
audio_driver_set_float_gate(NULL);
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)
== RUNAHEAD_COPY_READY)
&& (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)
== RUNAHEAD_COPY_READY)
&& (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 = (enum 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));
memset(&input_st->touchpad_requested, 0,
sizeof(input_st->touchpad_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;
runloop_state_t *runloop_st = &runloop_state;
video_driver_cached_frame_invalidate();
#ifdef HAVE_WEBMPLAYER
if (runloop_st->current_core_type == CORE_TYPE_WEBM)
libretro_media_set_video_hw_refresh(video_driver_movie_refresh, NULL);
#endif
#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 && (load_info->content->elems[0].data && *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);
/* Whatever the core set while loading: its first frame may be some
* way off, behind the menu or a pause. */
input_driver_flush_rumble();
led_driver_flush();
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;
#ifdef HAVE_GAME_AI
/* load models */
game_ai_load(load_info->info->path, runloop_st->current_core.retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM), runloop_st->current_core.retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM), libretro_log_cb);
#endif
return true;
}
runloop_st->current_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
return false;
}
bool core_get_system_info(struct retro_system_info *sysinfo)
{
runloop_state_t *runloop_st = &runloop_state;
if (!sysinfo || !runloop_st->current_core.retro_get_system_info)
return false;
runloop_st->current_core.retro_get_system_info(sysinfo);
return true;
}
bool core_unserialize(retro_ctx_serialize_info_t *info)
{
runloop_state_t *runloop_st = &runloop_state;
bool ok;
if (!info)
return false;
#ifdef HAVE_THREADS
video_thread_core_context_take();
#endif
ok = runloop_st->current_core.retro_unserialize(info->data_const, info->size);
#ifdef HAVE_THREADS
video_thread_core_context_release();
#endif
if (!ok)
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;
/* Drop video-driver caches of core-owned GPU resources before
* retro_reset() is allowed to destroy them. No-op on software
* cores or on drivers that do not implement the hook. */
video_driver_invalidate_hw_render_cache();
/* retro_reset() may reallocate the core's framebuffer. */
video_driver_cached_frame_retire();
#ifdef HAVE_THREADS
video_thread_core_context_take();
#endif
runloop_st->current_core.retro_reset();
#ifdef HAVE_THREADS
video_thread_core_context_release();
#endif
}
static void runloop_load_deferred_auto_state(void)
{
runloop_state_t *runloop_st = &runloop_state;
settings_t *settings;
if (!runloop_st->auto_state_load_pending)
return;
settings = config_get_ptr();
if (!settings->bools.savestate_auto_load)
{
runloop_st->auto_state_load_pending = false;
runloop_st->auto_state_load_ready = false;
runloop_st->auto_state_load_core_ran = false;
runloop_st->auto_state_load_attempted = true;
RARCH_LOG("[State] Deferred auto-load canceled because Auto Load State is disabled.\n");
return;
}
if ( runloop_st->content_closing
|| !(runloop_st->current_core.flags & RETRO_CORE_FLAG_GAME_LOADED))
{
runloop_st->auto_state_load_pending = false;
runloop_st->auto_state_load_ready = false;
runloop_st->auto_state_load_core_ran = false;
runloop_st->auto_state_load_attempted = true;
return;
}
/* A core may need several frames before serialization is available. */
if (!runloop_st->auto_state_load_core_ran || !core_serialize_size())
return;
runloop_st->auto_state_load_pending = false;
runloop_st->auto_state_load_attempted = true;
runloop_st->auto_state_load_ready = true;
command_event_load_auto_state();
runloop_st->auto_state_load_ready = false;
runloop_st->auto_state_load_core_ran = false;
}
bool 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;
enum poll_type new_poll_type= (core_poll_type_override != POLL_TYPE_OVERRIDE_DONTCARE)
? (enum poll_type)(core_poll_type_override - 1)
: (enum poll_type)(current_core->poll_type);
bool early_polling = (new_poll_type == POLL_TYPE_EARLY);
bool late_polling = (new_poll_type == POLL_TYPE_LATE);
bool ran = false;
#ifdef HAVE_NETWORKING
/* Declared with the other locals and assigned below, so the
* closing guard can return before netplay's pre-frame call
* without putting a declaration after a statement. */
bool netplay_preframe;
#endif
/* The core is being torn down, or replaced: do not run it.
*
* retro_run() must not be entered once closing has begun, because
* the teardown unloads the library that function lives in. A
* staged content load closes the core and then returns to the
* frame loop - while a save state task is still inside the core,
* and between its stages - so frames run here with the flags set.
*
* Poll and present anyway rather than returning bare, so input
* keeps being drained and whatever the close has put on screen
* keeps being drawn. Same shape as the netplay-paused case
* below, for the same reason: a frame that stops being produced
* reads as a hang. */
if (runloop_st->content_closing || runloop_st->content_switching)
{
input_driver_poll_between_frames(input_driver_poll);
video_driver_cached_frame();
return false;
}
#ifdef HAVE_NETWORKING
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_between_frames(input_driver_poll);
video_driver_cached_frame();
return false;
}
#endif
/* The flag says whether this frame's poll has happened. Early
* polling polls here; normal and late start the frame without it,
* and input_poll or the first input_state sets it. */
if (early_polling)
{
runloop_driver_pacing_input_sampled();
input_driver_poll();
current_core->flags |= RETRO_CORE_FLAG_INPUT_POLLED;
}
else
current_core->flags &= ~RETRO_CORE_FLAG_INPUT_POLLED;
/* Content can be marked CORE_RUNNING after a failed/partial load
* (e.g. archive member opened with no core). Never call through
* a NULL retro_run — that is an immediate SIGSEGV. */
if (current_core->retro_run)
{
/* The flags this iteration's checks flipped, fast-forward above
* all, reach the frame's own audio rather than the next frame's. */
audio_driver_publish_runloop();
#ifdef HAVE_THREADS
/* One GL context handed between the threads: the core's for the
* frame, the video thread's between frames. */
video_thread_core_context_take();
#endif
current_core->retro_run();
#ifdef HAVE_THREADS
video_thread_core_context_release();
#endif
ran = true;
if (runloop_st->auto_state_load_pending)
runloop_st->auto_state_load_core_ran = true;
audio_driver_frame_end();
}
#ifdef HAVE_GAME_AI
{
settings_t *settings = config_get_ptr();
bool override_p1 = settings->bools.game_ai_override_p1;
bool override_p2 = settings->bools.game_ai_override_p2;
bool show_debug = settings->bools.game_ai_show_debug;
/* Skip the call entirely when no GameAI feature is active for this
* frame. Avoids per-frame indirect dispatch into the loaded GameAI
* library (game_ai_lib_set_show_debug) that game_ai_think runs
* unconditionally when an AI has been instantiated. */
if (override_p1 || override_p2 || show_debug)
{
video_driver_state_t *video_st = video_state_get_ptr();
struct game_ai_think_ctx ctx;
ctx.override_p1 = override_p1;
ctx.override_p2 = override_p2;
ctx.show_debug = show_debug;
ctx.pix_fmt = video_st->pix_fmt;
/* Same-thread access (we're on the runloop thread, same as
* the producer), but route through cached_frame_read for
* lifecycle correctness once the field becomes private. */
video_driver_cached_frame_read(&ctx, runloop_game_ai_think_cb);
}
}
#endif
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
return ran;
}
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);
strlcpy(runloop_st->runtime_content_path_basename, path,
sizeof(runloop_st->runtime_content_path_basename));
#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 (*runloop_st->runtime_content_path_basename)
fill_pathname_dir(runloop_st->runtime_content_path_basename, path,
"", sizeof(runloop_st->runtime_content_path_basename));
#endif
/* Truncate path to last dot, but not when the path is
* relative and begins with a dot. */
if ( (dst = strrchr(runloop_st->runtime_content_path_basename, '.'))
&& (dst - runloop_st->runtime_content_path_basename > 0))
*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))
fill_pathname(runloop_st->name.savefile,
runloop_st->runtime_content_path_basename,
".srm",
sizeof(runloop_st->name.savefile));
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL))
fill_pathname(runloop_st->name.savestate,
runloop_st->runtime_content_path_basename,
".state",
sizeof(runloop_st->name.savestate));
#ifdef HAVE_BSV_MOVIE
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL))
fill_pathname(
runloop_st->name.replay,
runloop_st->runtime_content_path_basename,
".replay",
sizeof(runloop_st->name.replay));
#endif
#ifdef HAVE_CHEATS
if (*runloop_st->runtime_content_path_basename)
fill_pathname(
runloop_st->name.cheatfile,
runloop_st->runtime_content_path_basename,
".cht",
sizeof(runloop_st->name.cheatfile));
#endif
}
void runloop_path_set_redirect(settings_t *settings,
const char *old_savefile_dir,
const char *old_savestate_dir)
{
char content_dir_name[DIR_MAX_LENGTH];
char new_savefile_dir[DIR_MAX_LENGTH];
char new_savestate_dir[DIR_MAX_LENGTH];
char intermediate_savefile_dir[DIR_MAX_LENGTH];
char intermediate_savestate_dir[DIR_MAX_LENGTH];
runloop_state_t *runloop_st = &runloop_state;
struct retro_system_info *sysinfo = &runloop_st->system.info;
bool sort_savefiles = settings->bools.sort_savefiles_enable;
bool sort_savefiles_by_content = settings->bools.sort_savefiles_by_content_enable;
bool sort_savestates = settings->bools.sort_savestates_enable;
bool sort_savestates_by_content = 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(intermediate_savefile_dir, old_savefile_dir, sizeof(intermediate_savefile_dir));
strlcpy(intermediate_savestate_dir, old_savestate_dir, sizeof(intermediate_savestate_dir));
/* Get content directory name, if per-content-directory
* saves/states are enabled */
if ( (sort_savefiles_by_content || sort_savestates_by_content)
&& *runloop_st->runtime_content_path_basename)
fill_pathname_parent_dir_name(content_dir_name,
runloop_st->runtime_content_path_basename,
sizeof(content_dir_name));
/* Set savefile directory if empty to content directory */
if ( !*intermediate_savefile_dir
|| savefiles_in_content_dir)
{
fill_pathname_basedir(
intermediate_savefile_dir,
runloop_st->runtime_content_path_basename,
sizeof(intermediate_savefile_dir));
if (!*intermediate_savefile_dir)
RARCH_LOG("[Override] Cannot resolve save file path.\n");
}
/* Set savestate directory if empty based on content directory */
if ( !*intermediate_savestate_dir
|| savestates_in_content_dir)
{
fill_pathname_basedir(intermediate_savestate_dir,
runloop_st->runtime_content_path_basename,
sizeof(intermediate_savestate_dir));
if (!*intermediate_savestate_dir)
RARCH_LOG("[Override] Cannot resolve save state file path.\n");
}
strlcpy(new_savefile_dir, intermediate_savefile_dir,
sizeof(new_savefile_dir));
strlcpy(new_savestate_dir, intermediate_savestate_dir,
sizeof(new_savestate_dir));
if (sysinfo && (sysinfo->library_name && *sysinfo->library_name))
{
#ifdef HAVE_MENU
if (!string_is_equal(sysinfo->library_name,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE)))
#endif
{
/* Per-core and/or per-content-directory saves */
if ( (sort_savefiles || sort_savefiles_by_content)
&& *new_savefile_dir)
{
/* Append content directory name to save location */
if (sort_savefiles_by_content)
fill_pathname_join_special(
new_savefile_dir,
intermediate_savefile_dir,
content_dir_name,
sizeof(new_savefile_dir));
/* Append library_name to the save location */
if (sort_savefiles)
fill_pathname_join(
new_savefile_dir,
new_savefile_dir,
sysinfo->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)
&& !path_mkdir(new_savefile_dir))
{
RARCH_LOG("[Override] %s %s\n",
msg_hash_to_str(MSG_REVERTING_SAVEFILE_DIRECTORY_TO),
intermediate_savefile_dir);
strlcpy(new_savefile_dir,
intermediate_savefile_dir,
sizeof(new_savefile_dir));
}
}
/* Per-core and/or per-content-directory savestates */
if ( (sort_savestates || sort_savestates_by_content)
&& *new_savestate_dir)
{
/* Append content directory name to savestate location */
if (sort_savestates_by_content)
fill_pathname_join_special(
new_savestate_dir,
intermediate_savestate_dir,
content_dir_name,
sizeof(new_savestate_dir));
/* Append library_name to the savestate location */
if (sort_savestates)
fill_pathname_join(
new_savestate_dir,
new_savestate_dir,
sysinfo->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)
&& !path_mkdir(new_savestate_dir))
{
RARCH_LOG("[Override] %s %s\n",
msg_hash_to_str(MSG_REVERTING_SAVESTATE_DIRECTORY_TO),
intermediate_savestate_dir);
strlcpy(new_savestate_dir,
intermediate_savestate_dir,
sizeof(new_savestate_dir));
}
}
}
}
#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)
&& !netplay_driver_ctl(RARCH_NETPLAY_CTL_USE_CORE_PACKET_INTERFACE, 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 (sysinfo && (sysinfo->library_name && *sysinfo->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,
(*runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: sysinfo->library_name,
FILE_PATH_SRM_EXTENSION,
sizeof(runloop_st->name.savefile));
RARCH_LOG("[Override] %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,
(*runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: sysinfo->library_name,
FILE_PATH_STATE_EXTENSION,
sizeof(runloop_st->name.savestate));
fill_pathname_dir(runloop_st->name.replay,
(*runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: sysinfo->library_name,
FILE_PATH_BSV_EXTENSION,
sizeof(runloop_st->name.replay));
RARCH_LOG("[Override] %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,
(*runloop_st->runtime_content_path_basename)
? runloop_st->runtime_content_path_basename
: sysinfo->library_name,
FILE_PATH_CHT_EXTENSION,
sizeof(runloop_st->name.cheatfile));
RARCH_LOG("[Override] %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)
{
size_t 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);
fill_pathname(str, argv[i], "", sizeof(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));
strlcpy(runloop_st->name.replay, savestate_dir,
sizeof(runloop_st->name.replay));
}
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("[Override] %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVESTATE_TO),
runloop_st->name.savestate);
}
}