mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-23 16:09:47 +00:00
34a18f64e8
strlcpy and/or a file_path function that does strlcpy under the hood
8545 lines
274 KiB
C
8545 lines
274 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<64> Santoni
|
||
* Copyright (C) 2016-2019 - Brad Parker
|
||
* Copyright (C) 2016-2019 - Andr<64>s Su<53>rez (input mapper code)
|
||
* Copyright (C) 2016-2017 - Gregor Richards (network code)
|
||
*
|
||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||
* of the GNU General Public License as published by the Free Software Found-
|
||
* ation, either version 3 of the License, or (at your option) any later version.
|
||
*
|
||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||
* PURPOSE. See the GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||
* If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#ifdef _WIN32
|
||
#ifdef _XBOX
|
||
#include <xtl.h>
|
||
#else
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#include <windows.h>
|
||
#endif
|
||
#if defined(DEBUG) && defined(HAVE_DRMINGW)
|
||
#include "exchndl.h"
|
||
#endif
|
||
#endif
|
||
|
||
#if defined(DINGUX)
|
||
#include <sys/types.h>
|
||
#include <unistd.h>
|
||
#endif
|
||
|
||
#if (defined(__linux__) || defined(__unix__) || defined(DINGUX)) && !defined(EMSCRIPTEN)
|
||
#include <signal.h>
|
||
#endif
|
||
|
||
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
|
||
#ifndef LEGACY_WIN32
|
||
#define LEGACY_WIN32
|
||
#endif
|
||
#endif
|
||
|
||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||
#include <objbase.h>
|
||
#include <process.h>
|
||
#endif
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <stdarg.h>
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <ctype.h>
|
||
#include <errno.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 <time/rtime.h>
|
||
|
||
#include <libretro.h>
|
||
#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_assert.h>
|
||
#include <retro_miscellaneous.h>
|
||
#include <queues/message_queue.h>
|
||
#include <lists/dir_list.h>
|
||
|
||
#ifdef EMSCRIPTEN
|
||
#include <emscripten/emscripten.h>
|
||
#endif
|
||
|
||
#ifdef HAVE_LIBNX
|
||
#include <switch.h>
|
||
#endif
|
||
|
||
#if defined(HAVE_LAKKA) || defined(HAVE_LIBNX)
|
||
#include "switch_performance_profiles.h"
|
||
#endif
|
||
|
||
#if defined(ANDROID)
|
||
#include "play_feature_delivery/play_feature_delivery.h"
|
||
#endif
|
||
|
||
#ifdef HAVE_PRESENCE
|
||
#include "network/presence.h"
|
||
#endif
|
||
#ifdef HAVE_DISCORD
|
||
#include "network/discord.h"
|
||
#endif
|
||
|
||
#include "config.def.h"
|
||
|
||
#include "runtime_file.h"
|
||
#include "runloop.h"
|
||
#include "camera/camera_driver.h"
|
||
#include "location_driver.h"
|
||
#include "record/record_driver.h"
|
||
|
||
#ifdef HAVE_CONFIG_H
|
||
#include "config.h"
|
||
#endif
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
#include <net/net_compat.h>
|
||
#include <net/net_socket.h>
|
||
#endif
|
||
|
||
#include <audio/audio_resampler.h>
|
||
|
||
#include "audio/audio_driver.h"
|
||
#include "gfx/gfx_animation.h"
|
||
#include "gfx/gfx_display.h"
|
||
#include "gfx/gfx_thumbnail.h"
|
||
#include "gfx/video_filter.h"
|
||
|
||
#include "input/input_osk.h"
|
||
|
||
#ifdef HAVE_MENU
|
||
#include "menu/menu_cbs.h"
|
||
#include "menu/menu_driver.h"
|
||
#include "menu/menu_input.h"
|
||
#include "menu/menu_dialog.h"
|
||
#include "menu/menu_input_bind_dialog.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_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 "driver.h"
|
||
#include "msg_hash.h"
|
||
#include "paths.h"
|
||
#include "file_path_special.h"
|
||
#include "ui/ui_companion_driver.h"
|
||
#include "verbosity.h"
|
||
|
||
#include "frontend/frontend_driver.h"
|
||
#ifdef HAVE_THREADS
|
||
#include "gfx/video_thread_wrapper.h"
|
||
#endif
|
||
#include "gfx/video_display_server.h"
|
||
#ifdef HAVE_CRTSWITCHRES
|
||
#include "gfx/video_crt_switch.h"
|
||
#endif
|
||
#ifdef HAVE_BLUETOOTH
|
||
#include "bluetooth/bluetooth_driver.h"
|
||
#endif
|
||
#include "misc/cpufreq/cpufreq.h"
|
||
#include "led/led_driver.h"
|
||
#include "midi_driver.h"
|
||
#include "location_driver.h"
|
||
#include "core.h"
|
||
#include "configuration.h"
|
||
#include "list_special.h"
|
||
#include "core_option_manager.h"
|
||
#ifdef HAVE_CHEATS
|
||
#include "cheat_manager.h"
|
||
#endif
|
||
#ifdef HAVE_REWIND
|
||
#include "state_manager.h"
|
||
#endif
|
||
#include "tasks/task_content.h"
|
||
#include "tasks/task_file_transfer.h"
|
||
#include "tasks/task_powerstate.h"
|
||
#include "tasks/tasks_internal.h"
|
||
#include "performance_counters.h"
|
||
|
||
#include "version.h"
|
||
#include "version_git.h"
|
||
|
||
#include "retroarch.h"
|
||
|
||
#include "accessibility.h"
|
||
|
||
#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
|
||
#include "SDL.h"
|
||
#endif
|
||
|
||
#ifdef HAVE_LAKKA
|
||
#include "lakka.h"
|
||
#endif
|
||
|
||
#define SHADER_FILE_WATCH_DELAY_MSEC 500
|
||
|
||
#define QUIT_DELAY_USEC 3 * 1000000 /* 3 seconds */
|
||
|
||
#define DEFAULT_NETWORK_GAMEPAD_PORT 55400
|
||
#define UDP_FRAME_PACKETS 16
|
||
|
||
#ifdef HAVE_ZLIB
|
||
#define DEFAULT_EXT "zip"
|
||
#else
|
||
#define DEFAULT_EXT ""
|
||
#endif
|
||
|
||
#ifdef HAVE_DYNAMIC
|
||
#define SYMBOL(x) do { \
|
||
function_t func = dylib_proc(lib_handle_local, #x); \
|
||
memcpy(¤t_core->x, &func, sizeof(func)); \
|
||
if (!current_core->x) { RARCH_ERR("Failed to load symbol: \"%s\"\n", #x); retroarch_fail(1, "init_libretro_symbols()"); } \
|
||
} while (0)
|
||
#else
|
||
#define SYMBOL(x) current_core->x = x
|
||
#endif
|
||
|
||
#define SYMBOL_DUMMY(x) current_core->x = libretro_dummy_##x
|
||
|
||
#ifdef HAVE_FFMPEG
|
||
#define SYMBOL_FFMPEG(x) current_core->x = libretro_ffmpeg_##x
|
||
#endif
|
||
|
||
#ifdef HAVE_MPV
|
||
#define SYMBOL_MPV(x) current_core->x = libretro_mpv_##x
|
||
#endif
|
||
|
||
#ifdef HAVE_IMAGEVIEWER
|
||
#define SYMBOL_IMAGEVIEWER(x) current_core->x = libretro_imageviewer_##x
|
||
#endif
|
||
|
||
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
|
||
#define SYMBOL_NETRETROPAD(x) current_core->x = libretro_netretropad_##x
|
||
#endif
|
||
|
||
#if defined(HAVE_VIDEOPROCESSOR)
|
||
#define SYMBOL_VIDEOPROCESSOR(x) current_core->x = libretro_videoprocessor_##x
|
||
#endif
|
||
|
||
#define CORE_SYMBOLS(x) \
|
||
x(retro_init); \
|
||
x(retro_deinit); \
|
||
x(retro_api_version); \
|
||
x(retro_get_system_info); \
|
||
x(retro_get_system_av_info); \
|
||
x(retro_set_environment); \
|
||
x(retro_set_video_refresh); \
|
||
x(retro_set_audio_sample); \
|
||
x(retro_set_audio_sample_batch); \
|
||
x(retro_set_input_poll); \
|
||
x(retro_set_input_state); \
|
||
x(retro_set_controller_port_device); \
|
||
x(retro_reset); \
|
||
x(retro_run); \
|
||
x(retro_serialize_size); \
|
||
x(retro_serialize); \
|
||
x(retro_unserialize); \
|
||
x(retro_cheat_reset); \
|
||
x(retro_cheat_set); \
|
||
x(retro_load_game); \
|
||
x(retro_load_game_special); \
|
||
x(retro_unload_game); \
|
||
x(retro_get_region); \
|
||
x(retro_get_memory_data); \
|
||
x(retro_get_memory_size);
|
||
|
||
#ifdef _WIN32
|
||
#define PERF_LOG_FMT "[PERF]: Avg (%s): %I64u ticks, %I64u runs.\n"
|
||
#else
|
||
#define PERF_LOG_FMT "[PERF]: Avg (%s): %llu ticks, %llu runs.\n"
|
||
#endif
|
||
|
||
static runloop_state_t runloop_state = {0};
|
||
|
||
/* GLOBAL POINTER GETTERS */
|
||
runloop_state_t *runloop_state_get_ptr(void)
|
||
{
|
||
return &runloop_state;
|
||
}
|
||
|
||
#ifdef HAVE_REWIND
|
||
bool state_manager_frame_is_reversed(void)
|
||
{
|
||
return runloop_state.rewind_st.frame_is_reversed;
|
||
}
|
||
#endif
|
||
|
||
content_state_t *content_state_get_ptr(void)
|
||
{
|
||
return &runloop_state.content_st;
|
||
}
|
||
|
||
/* Get the current subsystem rom id */
|
||
unsigned content_get_subsystem_rom_id(void)
|
||
{
|
||
return runloop_state.content_st.pending_subsystem_rom_id;
|
||
}
|
||
|
||
/* Get the current subsystem */
|
||
int content_get_subsystem(void)
|
||
{
|
||
return runloop_state.content_st.pending_subsystem_id;
|
||
}
|
||
|
||
struct retro_perf_counter **retro_get_perf_counter_libretro(void)
|
||
{
|
||
return runloop_state.perf_counters_libretro;
|
||
}
|
||
|
||
unsigned retro_get_perf_count_libretro(void)
|
||
{
|
||
return runloop_state.perf_ptr_libretro;
|
||
}
|
||
|
||
void runloop_performance_counter_register(struct retro_perf_counter *perf)
|
||
{
|
||
if ( perf->registered
|
||
|| runloop_state.perf_ptr_libretro >= MAX_COUNTERS)
|
||
return;
|
||
|
||
runloop_state.perf_counters_libretro[runloop_state.perf_ptr_libretro++] = perf;
|
||
perf->registered = true;
|
||
}
|
||
|
||
void runloop_log_counters(
|
||
struct retro_perf_counter **counters, unsigned num)
|
||
{
|
||
int i;
|
||
for (i = 0; i < num; i++)
|
||
{
|
||
if (counters[i]->call_cnt)
|
||
{
|
||
RARCH_LOG(PERF_LOG_FMT,
|
||
counters[i]->ident,
|
||
(uint64_t)counters[i]->total /
|
||
(uint64_t)counters[i]->call_cnt,
|
||
(uint64_t)counters[i]->call_cnt);
|
||
}
|
||
}
|
||
}
|
||
|
||
void runloop_perf_log(void)
|
||
{
|
||
RARCH_LOG("[PERF]: Performance counters (libretro):\n");
|
||
runloop_log_counters(runloop_state.perf_counters_libretro,
|
||
runloop_state.perf_ptr_libretro);
|
||
}
|
||
|
||
static bool runloop_environ_cb_get_system_info(unsigned cmd, void *data)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
rarch_system_info_t *system = &runloop_st->system;
|
||
|
||
switch (cmd)
|
||
{
|
||
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||
*runloop_st->load_no_content_hook = *(const bool*)data;
|
||
break;
|
||
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
|
||
{
|
||
size_t i, j, size;
|
||
const struct retro_subsystem_info *info =
|
||
(const struct retro_subsystem_info*)data;
|
||
settings_t *settings = config_get_ptr();
|
||
unsigned log_level = settings->uints.libretro_log_level;
|
||
|
||
runloop_st->subsystem_current_count = 0;
|
||
|
||
RARCH_LOG("[Environ]: SET_SUBSYSTEM_INFO.\n");
|
||
|
||
for (i = 0; info[i].ident; i++)
|
||
{
|
||
if (log_level != RETRO_LOG_DEBUG)
|
||
continue;
|
||
|
||
RARCH_LOG("Subsystem ID: %d\nSpecial game type: %s\n Ident: %s\n ID: %u\n Content:\n",
|
||
i,
|
||
info[i].desc,
|
||
info[i].ident,
|
||
info[i].id
|
||
);
|
||
for (j = 0; j < info[i].num_roms; j++)
|
||
{
|
||
RARCH_LOG(" %s (%s)\n",
|
||
info[i].roms[j].desc, info[i].roms[j].required ?
|
||
"required" : "optional");
|
||
}
|
||
}
|
||
|
||
size = i;
|
||
|
||
if (log_level == RETRO_LOG_DEBUG)
|
||
{
|
||
RARCH_LOG("Subsystems: %d\n", i);
|
||
if (size > SUBSYSTEM_MAX_SUBSYSTEMS)
|
||
RARCH_WARN("Subsystems exceed subsystem max, clamping to %d\n", SUBSYSTEM_MAX_SUBSYSTEMS);
|
||
}
|
||
|
||
if (system)
|
||
{
|
||
for (i = 0; i < size && i < SUBSYSTEM_MAX_SUBSYSTEMS; i++)
|
||
{
|
||
struct retro_subsystem_info *subsys_info = &runloop_st->subsystem_data[i];
|
||
struct retro_subsystem_rom_info *subsys_rom_info = runloop_st->subsystem_data_roms[i];
|
||
/* Nasty, but have to do it like this since
|
||
* the pointers are const char *
|
||
* (if we don't free them, we get a memory leak) */
|
||
if (!string_is_empty(subsys_info->desc))
|
||
free((char *)subsys_info->desc);
|
||
if (!string_is_empty(subsys_info->ident))
|
||
free((char *)subsys_info->ident);
|
||
subsys_info->desc = strdup(info[i].desc);
|
||
subsys_info->ident = strdup(info[i].ident);
|
||
subsys_info->id = info[i].id;
|
||
subsys_info->num_roms = info[i].num_roms;
|
||
|
||
if (log_level == RETRO_LOG_DEBUG)
|
||
if (subsys_info->num_roms > SUBSYSTEM_MAX_SUBSYSTEM_ROMS)
|
||
RARCH_WARN("Subsystems exceed subsystem max roms, clamping to %d\n", SUBSYSTEM_MAX_SUBSYSTEM_ROMS);
|
||
|
||
for (j = 0; j < subsys_info->num_roms && j < SUBSYSTEM_MAX_SUBSYSTEM_ROMS; j++)
|
||
{
|
||
/* Nasty, but have to do it like this since
|
||
* the pointers are const char *
|
||
* (if we don't free them, we get a memory leak) */
|
||
if (!string_is_empty(subsys_rom_info[j].desc))
|
||
free((char *)
|
||
subsys_rom_info[j].desc);
|
||
if (!string_is_empty(
|
||
subsys_rom_info[j].valid_extensions))
|
||
free((char *)
|
||
subsys_rom_info[j].valid_extensions);
|
||
subsys_rom_info[j].desc =
|
||
strdup(info[i].roms[j].desc);
|
||
subsys_rom_info[j].valid_extensions =
|
||
strdup(info[i].roms[j].valid_extensions);
|
||
subsys_rom_info[j].required =
|
||
info[i].roms[j].required;
|
||
subsys_rom_info[j].block_extract =
|
||
info[i].roms[j].block_extract;
|
||
subsys_rom_info[j].need_fullpath =
|
||
info[i].roms[j].need_fullpath;
|
||
}
|
||
|
||
subsys_info->roms = subsys_rom_info;
|
||
}
|
||
|
||
runloop_st->subsystem_current_count =
|
||
size <= SUBSYSTEM_MAX_SUBSYSTEMS
|
||
? size
|
||
: SUBSYSTEM_MAX_SUBSYSTEMS;
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
#ifdef HAVE_DYNAMIC
|
||
/**
|
||
* libretro_get_environment_info:
|
||
* @func : Function pointer for get_environment_info.
|
||
* @load_no_content : If true, core should be able to auto-start
|
||
* without any content loaded.
|
||
*
|
||
* Sets environment callback in order to get statically known
|
||
* information from it.
|
||
*
|
||
* Fetched via environment callbacks instead of
|
||
* retro_get_system_info(), as this info is part of extensions.
|
||
*
|
||
* Should only be called once right after core load to
|
||
* avoid overwriting the "real" environ callback.
|
||
*
|
||
* For statically linked cores, pass retro_set_environment as argument.
|
||
*/
|
||
void libretro_get_environment_info(
|
||
void (*func)(retro_environment_t),
|
||
bool *load_no_content)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
runloop_st->load_no_content_hook = load_no_content;
|
||
|
||
/* load_no_content gets set in this callback. */
|
||
func(runloop_environ_cb_get_system_info);
|
||
|
||
/* It's possible that we just set get_system_info callback
|
||
* to the currently running core.
|
||
*
|
||
* Make sure we reset it to the actual environment callback.
|
||
* Ignore any environment callbacks here in case we're running
|
||
* on the non-current core. */
|
||
runloop_st->ignore_environment_cb = true;
|
||
func(runloop_environment_cb);
|
||
runloop_st->ignore_environment_cb = false;
|
||
}
|
||
|
||
static dylib_t load_dynamic_core(const char *path, char *buf,
|
||
size_t size)
|
||
{
|
||
#if defined(ANDROID)
|
||
/* Can't resolve symlinks when dealing with cores
|
||
* installed via play feature delivery, because the
|
||
* source files have non-standard file names (which
|
||
* will not be recognised by regular core handling
|
||
* routines) */
|
||
bool resolve_symlinks = !play_feature_delivery_enabled();
|
||
#else
|
||
bool resolve_symlinks = true;
|
||
#endif
|
||
|
||
/* Can't lookup symbols in itself on UWP */
|
||
#if !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
|
||
if (dylib_proc(NULL, "retro_init"))
|
||
{
|
||
/* Try to verify that -lretro was not linked in from other modules
|
||
* since loading it dynamically and with -l will fail hard. */
|
||
RARCH_ERR("Serious problem. RetroArch wants to load libretro cores"
|
||
" dynamically, but it is already linked.\n");
|
||
RARCH_ERR("This could happen if other modules RetroArch depends on "
|
||
"link against libretro directly.\n");
|
||
RARCH_ERR("Proceeding could cause a crash. Aborting ...\n");
|
||
retroarch_fail(1, "load_dynamic_core()");
|
||
}
|
||
#endif
|
||
|
||
/* Need to use absolute path for this setting. It can be
|
||
* saved to content history, and a relative path would
|
||
* break in that scenario. */
|
||
path_resolve_realpath(buf, size, resolve_symlinks);
|
||
return dylib_load(path);
|
||
}
|
||
|
||
static dylib_t libretro_get_system_info_lib(const char *path,
|
||
struct retro_system_info *info, bool *load_no_content)
|
||
{
|
||
dylib_t lib = dylib_load(path);
|
||
void (*proc)(struct retro_system_info*);
|
||
|
||
if (!lib)
|
||
return NULL;
|
||
|
||
proc = (void (*)(struct retro_system_info*))
|
||
dylib_proc(lib, "retro_get_system_info");
|
||
|
||
if (!proc)
|
||
{
|
||
dylib_close(lib);
|
||
return NULL;
|
||
}
|
||
|
||
proc(info);
|
||
|
||
if (load_no_content)
|
||
{
|
||
void (*set_environ)(retro_environment_t);
|
||
*load_no_content = false;
|
||
set_environ = (void (*)(retro_environment_t))
|
||
dylib_proc(lib, "retro_set_environment");
|
||
|
||
if (set_environ)
|
||
libretro_get_environment_info(set_environ, load_no_content);
|
||
}
|
||
|
||
return lib;
|
||
}
|
||
#endif
|
||
|
||
static void runloop_update_runtime_log(
|
||
runloop_state_t *runloop_st,
|
||
const char *dir_runtime_log,
|
||
const char *dir_playlist,
|
||
bool log_per_core)
|
||
{
|
||
/* Initialise runtime log file */
|
||
runtime_log_t *runtime_log = runtime_log_init(
|
||
runloop_st->runtime_content_path,
|
||
runloop_st->runtime_core_path,
|
||
dir_runtime_log,
|
||
dir_playlist,
|
||
log_per_core);
|
||
|
||
if (!runtime_log)
|
||
return;
|
||
|
||
/* Add additional runtime */
|
||
runtime_log_add_runtime_usec(runtime_log,
|
||
runloop_st->core_runtime_usec);
|
||
|
||
/* Update 'last played' entry */
|
||
runtime_log_set_last_played_now(runtime_log);
|
||
|
||
/* Save runtime log file */
|
||
runtime_log_save(runtime_log);
|
||
|
||
/* Clean up */
|
||
free(runtime_log);
|
||
}
|
||
|
||
|
||
void runloop_runtime_log_deinit(
|
||
runloop_state_t *runloop_st,
|
||
bool content_runtime_log,
|
||
bool content_runtime_log_aggregate,
|
||
const char *dir_runtime_log,
|
||
const char *dir_playlist)
|
||
{
|
||
if (verbosity_is_enabled())
|
||
{
|
||
int n;
|
||
char log[PATH_MAX_LENGTH] = {0};
|
||
unsigned hours = 0;
|
||
unsigned minutes = 0;
|
||
unsigned seconds = 0;
|
||
|
||
runtime_log_convert_usec2hms(
|
||
runloop_st->core_runtime_usec,
|
||
&hours, &minutes, &seconds);
|
||
|
||
n =
|
||
snprintf(log, sizeof(log),
|
||
"[Core]: Content ran for a total of:"
|
||
" %02u hours, %02u minutes, %02u seconds.",
|
||
hours, minutes, seconds);
|
||
if ((n < 0) || (n >= PATH_MAX_LENGTH))
|
||
n = 0; /* Just silence any potential gcc warnings... */
|
||
(void)n;
|
||
RARCH_LOG("%s\n",log);
|
||
}
|
||
|
||
/* Only write to file if content has run for a non-zero length of time */
|
||
if (runloop_st->core_runtime_usec > 0)
|
||
{
|
||
/* Per core logging */
|
||
if (content_runtime_log)
|
||
runloop_update_runtime_log(runloop_st, dir_runtime_log, dir_playlist, true);
|
||
|
||
/* Aggregate logging */
|
||
if (content_runtime_log_aggregate)
|
||
runloop_update_runtime_log(runloop_st, dir_runtime_log, dir_playlist, false);
|
||
}
|
||
|
||
/* Reset runtime + content/core paths, to prevent any
|
||
* possibility of duplicate logging */
|
||
runloop_st->core_runtime_usec = 0;
|
||
memset(runloop_st->runtime_content_path, 0,
|
||
sizeof(runloop_st->runtime_content_path));
|
||
memset(runloop_st->runtime_core_path, 0,
|
||
sizeof(runloop_st->runtime_core_path));
|
||
}
|
||
|
||
static bool runloop_clear_all_thread_waits(
|
||
unsigned clear_threads, void *data)
|
||
{
|
||
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)
|
||
return true;
|
||
|
||
switch (type)
|
||
{
|
||
case RETRO_HW_CONTEXT_VULKAN:
|
||
if (!string_is_equal(video_ident, "vulkan"))
|
||
return false;
|
||
break;
|
||
#if defined(HAVE_OPENGL_CORE)
|
||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||
if (!string_is_equal(video_ident, "glcore"))
|
||
return false;
|
||
break;
|
||
#else
|
||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||
#endif
|
||
case RETRO_HW_CONTEXT_OPENGLES2:
|
||
case RETRO_HW_CONTEXT_OPENGLES3:
|
||
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
|
||
case RETRO_HW_CONTEXT_OPENGL:
|
||
if (!string_is_equal(video_ident, "gl") &&
|
||
!string_is_equal(video_ident, "glcore"))
|
||
return false;
|
||
break;
|
||
case RETRO_HW_CONTEXT_DIRECT3D:
|
||
if (!(string_is_equal(video_ident, "d3d11") && major == 11))
|
||
return false;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool dynamic_request_hw_context(enum retro_hw_context_type type,
|
||
unsigned minor, unsigned major)
|
||
{
|
||
switch (type)
|
||
{
|
||
case RETRO_HW_CONTEXT_NONE:
|
||
RARCH_LOG("Requesting no HW context.\n");
|
||
break;
|
||
|
||
case RETRO_HW_CONTEXT_VULKAN:
|
||
#ifdef HAVE_VULKAN
|
||
RARCH_LOG("Requesting Vulkan context.\n");
|
||
break;
|
||
#else
|
||
RARCH_ERR("Requesting Vulkan context, but RetroArch is not compiled against Vulkan. Cannot use HW context.\n");
|
||
return false;
|
||
#endif
|
||
|
||
#if defined(HAVE_OPENGLES)
|
||
|
||
#if (defined(HAVE_OPENGLES2) || defined(HAVE_OPENGLES3))
|
||
case RETRO_HW_CONTEXT_OPENGLES2:
|
||
case RETRO_HW_CONTEXT_OPENGLES3:
|
||
RARCH_LOG("Requesting OpenGLES%u context.\n",
|
||
type == RETRO_HW_CONTEXT_OPENGLES2 ? 2 : 3);
|
||
break;
|
||
|
||
#if defined(HAVE_OPENGLES3)
|
||
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
|
||
#ifndef HAVE_OPENGLES3_2
|
||
if (major == 3 && minor == 2)
|
||
{
|
||
RARCH_ERR("Requesting OpenGLES%u.%u context, but RetroArch is compiled against a lesser version. Cannot use HW context.\n",
|
||
major, minor);
|
||
return false;
|
||
}
|
||
#endif
|
||
#if !defined(HAVE_OPENGLES3_2) && !defined(HAVE_OPENGLES3_1)
|
||
if (major == 3 && minor == 1)
|
||
{
|
||
RARCH_ERR("Requesting OpenGLES%u.%u context, but RetroArch is compiled against a lesser version. Cannot use HW context.\n",
|
||
major, minor);
|
||
return false;
|
||
}
|
||
#endif
|
||
RARCH_LOG("Requesting OpenGLES%u.%u context.\n",
|
||
major, minor);
|
||
break;
|
||
#endif
|
||
|
||
#endif
|
||
case RETRO_HW_CONTEXT_OPENGL:
|
||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||
RARCH_ERR("Requesting OpenGL context, but RetroArch "
|
||
"is compiled against OpenGLES. Cannot use HW context.\n");
|
||
return false;
|
||
|
||
#elif defined(HAVE_OPENGL) || defined(HAVE_OPENGL_CORE)
|
||
case RETRO_HW_CONTEXT_OPENGLES2:
|
||
case RETRO_HW_CONTEXT_OPENGLES3:
|
||
RARCH_ERR("Requesting OpenGLES%u context, but RetroArch "
|
||
"is compiled against OpenGL. Cannot use HW context.\n",
|
||
type == RETRO_HW_CONTEXT_OPENGLES2 ? 2 : 3);
|
||
return false;
|
||
|
||
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
|
||
RARCH_ERR("Requesting OpenGLES%u.%u context, but RetroArch "
|
||
"is compiled against OpenGL. Cannot use HW context.\n",
|
||
major, minor);
|
||
return false;
|
||
|
||
case RETRO_HW_CONTEXT_OPENGL:
|
||
RARCH_LOG("Requesting OpenGL context.\n");
|
||
break;
|
||
|
||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||
/* TODO/FIXME - we should do a check here to see if
|
||
* the requested core GL version is supported */
|
||
RARCH_LOG("Requesting core OpenGL context (%u.%u).\n",
|
||
major, minor);
|
||
break;
|
||
#endif
|
||
|
||
#if defined(HAVE_D3D9) || defined(HAVE_D3D11)
|
||
case RETRO_HW_CONTEXT_DIRECT3D:
|
||
switch (major)
|
||
{
|
||
#ifdef HAVE_D3D9
|
||
case 9:
|
||
RARCH_LOG("Requesting D3D9 context.\n");
|
||
break;
|
||
#endif
|
||
#ifdef HAVE_D3D11
|
||
case 11:
|
||
RARCH_LOG("Requesting D3D11 context.\n");
|
||
break;
|
||
#endif
|
||
default:
|
||
RARCH_LOG("Requesting unknown context.\n");
|
||
return false;
|
||
}
|
||
break;
|
||
#endif
|
||
|
||
default:
|
||
RARCH_LOG("Requesting unknown context.\n");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static void libretro_log_cb(
|
||
enum retro_log_level level,
|
||
const char *fmt, ...)
|
||
{
|
||
va_list vp;
|
||
settings_t *settings = config_get_ptr();
|
||
unsigned libretro_log_level = settings->uints.libretro_log_level;
|
||
|
||
if ((unsigned)level < libretro_log_level)
|
||
return;
|
||
|
||
if (!verbosity_is_enabled())
|
||
return;
|
||
|
||
va_start(vp, fmt);
|
||
|
||
switch (level)
|
||
{
|
||
case RETRO_LOG_DEBUG:
|
||
RARCH_LOG_V("[libretro DEBUG]", fmt, vp);
|
||
break;
|
||
|
||
case RETRO_LOG_INFO:
|
||
RARCH_LOG_OUTPUT_V("[libretro INFO]", fmt, vp);
|
||
break;
|
||
|
||
case RETRO_LOG_WARN:
|
||
RARCH_WARN_V("[libretro WARN]", fmt, vp);
|
||
break;
|
||
|
||
case RETRO_LOG_ERROR:
|
||
RARCH_ERR_V("[libretro ERROR]", fmt, vp);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
va_end(vp);
|
||
}
|
||
|
||
static size_t mmap_add_bits_down(size_t n)
|
||
{
|
||
n |= n >> 1;
|
||
n |= n >> 2;
|
||
n |= n >> 4;
|
||
n |= n >> 8;
|
||
n |= n >> 16;
|
||
|
||
/* double shift to avoid warnings on 32bit (it's dead code,
|
||
* but compilers suck) */
|
||
if (sizeof(size_t) > 4)
|
||
n |= n >> 16 >> 16;
|
||
|
||
return n;
|
||
}
|
||
|
||
static size_t mmap_inflate(size_t addr, size_t mask)
|
||
{
|
||
while (mask)
|
||
{
|
||
size_t tmp = (mask - 1) & ~mask;
|
||
|
||
/* to put in an 1 bit instead, OR in tmp+1 */
|
||
addr = ((addr & ~tmp) << 1) | (addr & tmp);
|
||
mask = mask & (mask - 1);
|
||
}
|
||
|
||
return addr;
|
||
}
|
||
|
||
static size_t mmap_reduce(size_t addr, size_t mask)
|
||
{
|
||
while (mask)
|
||
{
|
||
size_t tmp = (mask - 1) & ~mask;
|
||
addr = (addr & tmp) | ((addr >> 1) & ~tmp);
|
||
mask = (mask & (mask - 1)) >> 1;
|
||
}
|
||
|
||
return addr;
|
||
}
|
||
|
||
|
||
static size_t mmap_highest_bit(size_t n)
|
||
{
|
||
n = mmap_add_bits_down(n);
|
||
return n ^ (n >> 1);
|
||
}
|
||
|
||
static bool mmap_preprocess_descriptors(
|
||
rarch_memory_descriptor_t *first, unsigned count)
|
||
{
|
||
size_t top_addr = 1;
|
||
rarch_memory_descriptor_t *desc = NULL;
|
||
const rarch_memory_descriptor_t *end = first + count;
|
||
size_t highest_reachable = 0;
|
||
|
||
for (desc = first; desc < end; desc++)
|
||
{
|
||
if (desc->core.select != 0)
|
||
top_addr |= desc->core.select;
|
||
else
|
||
top_addr |= desc->core.start + desc->core.len - 1;
|
||
}
|
||
|
||
top_addr = mmap_add_bits_down(top_addr);
|
||
|
||
for (desc = first; desc < end; desc++)
|
||
{
|
||
if (desc->core.select == 0)
|
||
{
|
||
if (desc->core.len == 0)
|
||
return false;
|
||
|
||
if ((desc->core.len & (desc->core.len - 1)) != 0)
|
||
return false;
|
||
|
||
desc->core.select = top_addr & ~mmap_inflate(mmap_add_bits_down(desc->core.len - 1),
|
||
desc->core.disconnect);
|
||
}
|
||
|
||
if (desc->core.len == 0)
|
||
desc->core.len = mmap_add_bits_down(mmap_reduce(top_addr & ~desc->core.select,
|
||
desc->core.disconnect)) + 1;
|
||
|
||
if (desc->core.start & ~desc->core.select)
|
||
return false;
|
||
|
||
highest_reachable = mmap_inflate(desc->core.len - 1, desc->core.disconnect);
|
||
|
||
/* Disconnect unselected bits that are too high to ever
|
||
* index into the core's buffer. Higher addresses will
|
||
* repeat / mirror the buffer as long as they match select */
|
||
while (mmap_highest_bit(top_addr & ~desc->core.select & ~desc->core.disconnect) >
|
||
mmap_highest_bit(highest_reachable))
|
||
desc->core.disconnect |= mmap_highest_bit(top_addr & ~desc->core.select & ~desc->core.disconnect);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static void runloop_deinit_core_options(
|
||
bool game_options_active,
|
||
const char *path_core_options,
|
||
core_option_manager_t *core_options)
|
||
{
|
||
/* Check whether game-specific options file is being used */
|
||
if (!string_is_empty(path_core_options))
|
||
{
|
||
config_file_t *conf_tmp = NULL;
|
||
|
||
/* We only need to save configuration settings for
|
||
* the current core
|
||
* > If game-specific options file exists, have
|
||
* to read it (to ensure file only gets written
|
||
* if config values change)
|
||
* > Otherwise, create a new, empty config_file_t
|
||
* object */
|
||
if (path_is_valid(path_core_options))
|
||
conf_tmp = config_file_new_from_path_to_string(path_core_options);
|
||
|
||
if (!conf_tmp)
|
||
conf_tmp = config_file_new_alloc();
|
||
|
||
if (conf_tmp)
|
||
{
|
||
core_option_manager_flush(
|
||
core_options,
|
||
conf_tmp);
|
||
RARCH_LOG("[Core]: Saved %s-specific core options to \"%s\".\n",
|
||
game_options_active ? "game" : "folder", path_core_options);
|
||
config_file_write(conf_tmp, path_core_options, true);
|
||
config_file_free(conf_tmp);
|
||
conf_tmp = NULL;
|
||
}
|
||
path_clear(RARCH_PATH_CORE_OPTIONS);
|
||
}
|
||
else
|
||
{
|
||
const char *path = core_options->conf_path;
|
||
core_option_manager_flush(
|
||
core_options,
|
||
core_options->conf);
|
||
RARCH_LOG("[Core]: Saved core options file to \"%s\".\n", path);
|
||
config_file_write(core_options->conf, path, true);
|
||
}
|
||
|
||
if (core_options)
|
||
core_option_manager_free(core_options);
|
||
}
|
||
|
||
static bool validate_per_core_options(char *s,
|
||
size_t len, bool mkdir,
|
||
const char *core_name, const char *game_name)
|
||
{
|
||
char config_directory[PATH_MAX_LENGTH];
|
||
config_directory[0] = '\0';
|
||
|
||
if ( (!s)
|
||
|| (len < 1)
|
||
|| string_is_empty(core_name)
|
||
|| string_is_empty(game_name))
|
||
return false;
|
||
|
||
fill_pathname_application_special(config_directory,
|
||
sizeof(config_directory), APPLICATION_SPECIAL_DIRECTORY_CONFIG);
|
||
|
||
fill_pathname_join_special_ext(s,
|
||
config_directory, core_name, game_name,
|
||
".opt", len);
|
||
|
||
/* No need to make a directory if file already exists... */
|
||
if (mkdir && !path_is_valid(s))
|
||
{
|
||
char new_path[PATH_MAX_LENGTH];
|
||
fill_pathname_join(new_path,
|
||
config_directory, core_name, sizeof(new_path));
|
||
if (!path_is_directory(new_path))
|
||
path_mkdir(new_path);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
static bool validate_game_options(
|
||
const char *core_name,
|
||
char *s, size_t len, bool mkdir)
|
||
{
|
||
const char *game_name = path_basename_nocompression(path_get(RARCH_PATH_BASENAME));
|
||
return validate_per_core_options(s, len, mkdir,
|
||
core_name, game_name);
|
||
}
|
||
|
||
/**
|
||
* game_specific_options:
|
||
*
|
||
* Returns: true (1) if a game specific core
|
||
* options path has been found,
|
||
* otherwise false (0).
|
||
**/
|
||
static bool validate_game_specific_options(char **output)
|
||
{
|
||
char game_options_path[PATH_MAX_LENGTH];
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
game_options_path[0] = '\0';
|
||
|
||
if (!validate_game_options(
|
||
runloop_st->system.info.library_name,
|
||
game_options_path,
|
||
sizeof(game_options_path), false) ||
|
||
!path_is_valid(game_options_path))
|
||
return false;
|
||
|
||
RARCH_LOG("[Core]: %s \"%s\".\n",
|
||
msg_hash_to_str(MSG_GAME_SPECIFIC_CORE_OPTIONS_FOUND_AT),
|
||
game_options_path);
|
||
*output = strdup(game_options_path);
|
||
return true;
|
||
}
|
||
|
||
static bool validate_folder_options(
|
||
char *s, size_t len, bool mkdir)
|
||
{
|
||
char folder_name[PATH_MAX_LENGTH];
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
const char *core_name = runloop_st->system.info.library_name;
|
||
const char *game_path = path_get(RARCH_PATH_BASENAME);
|
||
|
||
folder_name[0] = '\0';
|
||
|
||
if (string_is_empty(game_path))
|
||
return false;
|
||
|
||
fill_pathname_parent_dir_name(folder_name,
|
||
game_path, sizeof(folder_name));
|
||
|
||
return validate_per_core_options(s, len, mkdir,
|
||
core_name, folder_name);
|
||
}
|
||
|
||
|
||
/**
|
||
* validate_folder_specific_options:
|
||
*
|
||
* Returns: true (1) if a folder specific core
|
||
* options path has been found,
|
||
* otherwise false (0).
|
||
**/
|
||
static bool validate_folder_specific_options(
|
||
char **output)
|
||
{
|
||
char folder_options_path[PATH_MAX_LENGTH];
|
||
folder_options_path[0] ='\0';
|
||
|
||
if (!validate_folder_options(
|
||
folder_options_path,
|
||
sizeof(folder_options_path), false) ||
|
||
!path_is_valid(folder_options_path))
|
||
return false;
|
||
|
||
RARCH_LOG("[Core]: %s \"%s\".\n",
|
||
msg_hash_to_str(MSG_FOLDER_SPECIFIC_CORE_OPTIONS_FOUND_AT),
|
||
folder_options_path);
|
||
*output = strdup(folder_options_path);
|
||
return true;
|
||
}
|
||
|
||
/* Fetches core options path for current core/content
|
||
* - path: path from which options should be read
|
||
* from/saved to
|
||
* - src_path: in the event that 'path' file does not
|
||
* yet exist, provides source path from which initial
|
||
* options should be extracted
|
||
*
|
||
* NOTE: caller must ensure
|
||
* path and src_path are NULL-terminated
|
||
* */
|
||
static void runloop_init_core_options_path(
|
||
settings_t *settings,
|
||
char *path, size_t len,
|
||
char *src_path, size_t src_len)
|
||
{
|
||
char *game_options_path = NULL;
|
||
char *folder_options_path = NULL;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool game_specific_options = settings->bools.game_specific_options;
|
||
|
||
/* Check whether game-specific options exist */
|
||
if (game_specific_options &&
|
||
validate_game_specific_options(&game_options_path))
|
||
{
|
||
/* Notify system that we have a valid core options
|
||
* override */
|
||
path_set(RARCH_PATH_CORE_OPTIONS, game_options_path);
|
||
runloop_st->game_options_active = true;
|
||
runloop_st->folder_options_active = false;
|
||
|
||
/* Copy options path */
|
||
strlcpy(path, game_options_path, len);
|
||
|
||
free(game_options_path);
|
||
}
|
||
/* Check whether folder-specific options exist */
|
||
else if (game_specific_options &&
|
||
validate_folder_specific_options(
|
||
&folder_options_path))
|
||
{
|
||
/* Notify system that we have a valid core options
|
||
* override */
|
||
path_set(RARCH_PATH_CORE_OPTIONS, folder_options_path);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = true;
|
||
|
||
/* Copy options path */
|
||
strlcpy(path, folder_options_path, len);
|
||
|
||
free(folder_options_path);
|
||
}
|
||
else
|
||
{
|
||
char global_options_path[PATH_MAX_LENGTH];
|
||
char per_core_options_path[PATH_MAX_LENGTH];
|
||
bool per_core_options_exist = false;
|
||
bool per_core_options = !settings->bools.global_core_options;
|
||
const char *path_core_options = settings->paths.path_core_options;
|
||
|
||
per_core_options_path[0] = '\0';
|
||
|
||
if (per_core_options)
|
||
{
|
||
const char *core_name = runloop_st->system.info.library_name;
|
||
/* Get core-specific options path
|
||
* > if validate_per_core_options() returns
|
||
* false, then per-core options are disabled (due to
|
||
* unknown system errors...) */
|
||
per_core_options = validate_per_core_options(
|
||
per_core_options_path, sizeof(per_core_options_path), true,
|
||
core_name, core_name);
|
||
|
||
/* If we can use per-core options, check whether an options
|
||
* file already exists */
|
||
if (per_core_options)
|
||
per_core_options_exist = path_is_valid(per_core_options_path);
|
||
}
|
||
|
||
/* If not using per-core options, or if a per-core options
|
||
* file does not yet exist, must fetch 'global' options path */
|
||
if (!per_core_options || !per_core_options_exist)
|
||
{
|
||
const char *options_path = path_core_options;
|
||
|
||
if (!string_is_empty(options_path))
|
||
strlcpy(global_options_path,
|
||
options_path, sizeof(global_options_path));
|
||
else if (!path_is_empty(RARCH_PATH_CONFIG))
|
||
fill_pathname_resolve_relative(
|
||
global_options_path, path_get(RARCH_PATH_CONFIG),
|
||
FILE_PATH_CORE_OPTIONS_CONFIG, sizeof(global_options_path));
|
||
}
|
||
|
||
/* Allocate correct path/src_path strings */
|
||
if (per_core_options)
|
||
{
|
||
strlcpy(path, per_core_options_path, len);
|
||
|
||
if (!per_core_options_exist)
|
||
strlcpy(src_path, global_options_path, src_len);
|
||
}
|
||
else
|
||
strlcpy(path, global_options_path, len);
|
||
|
||
/* Notify system that we *do not* have a valid core options
|
||
* options override */
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
}
|
||
}
|
||
|
||
static core_option_manager_t *runloop_init_core_options(
|
||
settings_t *settings,
|
||
const struct retro_core_options_v2 *options_v2)
|
||
{
|
||
bool categories_enabled = settings->bools.core_option_category_enable;
|
||
char options_path[PATH_MAX_LENGTH];
|
||
char src_options_path[PATH_MAX_LENGTH];
|
||
|
||
/* Ensure these are NULL-terminated */
|
||
options_path[0] = '\0';
|
||
src_options_path[0] = '\0';
|
||
|
||
/* Get core options file path */
|
||
runloop_init_core_options_path(settings,
|
||
options_path, sizeof(options_path),
|
||
src_options_path, sizeof(src_options_path));
|
||
|
||
if (!string_is_empty(options_path))
|
||
return core_option_manager_new(options_path,
|
||
src_options_path, options_v2,
|
||
categories_enabled);
|
||
return NULL;
|
||
}
|
||
|
||
|
||
static core_option_manager_t *runloop_init_core_variables(
|
||
settings_t *settings, const struct retro_variable *vars)
|
||
{
|
||
char options_path[PATH_MAX_LENGTH];
|
||
char src_options_path[PATH_MAX_LENGTH];
|
||
|
||
/* Ensure these are NULL-terminated */
|
||
options_path[0] = '\0';
|
||
src_options_path[0] = '\0';
|
||
|
||
/* Get core options file path */
|
||
runloop_init_core_options_path(
|
||
settings,
|
||
options_path, sizeof(options_path),
|
||
src_options_path, sizeof(src_options_path));
|
||
|
||
if (!string_is_empty(options_path))
|
||
return core_option_manager_new_vars(options_path, src_options_path, vars);
|
||
return NULL;
|
||
}
|
||
|
||
static void runloop_core_msg_queue_push(
|
||
struct retro_system_av_info *av_info,
|
||
const struct retro_message_ext *msg)
|
||
{
|
||
double fps;
|
||
unsigned duration_frames;
|
||
enum message_queue_category category;
|
||
|
||
/* Assign category */
|
||
switch (msg->level)
|
||
{
|
||
case RETRO_LOG_WARN:
|
||
category = MESSAGE_QUEUE_CATEGORY_WARNING;
|
||
break;
|
||
case RETRO_LOG_ERROR:
|
||
category = MESSAGE_QUEUE_CATEGORY_ERROR;
|
||
break;
|
||
case RETRO_LOG_INFO:
|
||
case RETRO_LOG_DEBUG:
|
||
default:
|
||
category = MESSAGE_QUEUE_CATEGORY_INFO;
|
||
break;
|
||
}
|
||
|
||
/* Get duration in frames */
|
||
fps = (av_info && (av_info->timing.fps > 0)) ? av_info->timing.fps : 60.0;
|
||
duration_frames = (unsigned)((fps * (float)msg->duration / 1000.0f) + 0.5f);
|
||
|
||
/* Note: Do not flush the message queue here - a core
|
||
* may need to send multiple notifications simultaneously */
|
||
runloop_msg_queue_push(msg->msg,
|
||
msg->priority, duration_frames,
|
||
false, NULL, MESSAGE_QUEUE_ICON_DEFAULT,
|
||
category);
|
||
}
|
||
|
||
static void core_performance_counter_start(
|
||
struct retro_perf_counter *perf)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool runloop_perfcnt_enable = runloop_st->perfcnt_enable;
|
||
|
||
if (runloop_perfcnt_enable)
|
||
{
|
||
perf->call_cnt++;
|
||
perf->start = cpu_features_get_perf_counter();
|
||
}
|
||
}
|
||
|
||
static void core_performance_counter_stop(struct retro_perf_counter *perf)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool runloop_perfcnt_enable = runloop_st->perfcnt_enable;
|
||
|
||
if (runloop_perfcnt_enable)
|
||
perf->total += cpu_features_get_perf_counter() - perf->start;
|
||
}
|
||
|
||
|
||
bool runloop_environment_cb(unsigned cmd, void *data)
|
||
{
|
||
unsigned p;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
recording_state_t *recording_st = recording_state_get_ptr();
|
||
|
||
settings_t *settings = config_get_ptr();
|
||
rarch_system_info_t *system = &runloop_st->system;
|
||
bool ignore_environment_cb = runloop_st->ignore_environment_cb;
|
||
|
||
if (ignore_environment_cb)
|
||
return false;
|
||
|
||
/* RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE gets called
|
||
* by every core on every frame. Handle it first,
|
||
* to avoid the overhead of traversing the subsequent
|
||
* (enormous) case statement */
|
||
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
|
||
{
|
||
if (runloop_st->core_options)
|
||
*(bool*)data = runloop_st->core_options->updated;
|
||
else
|
||
*(bool*)data = false;
|
||
|
||
return true;
|
||
}
|
||
|
||
switch (cmd)
|
||
{
|
||
case RETRO_ENVIRONMENT_GET_OVERSCAN:
|
||
{
|
||
bool video_crop_overscan = settings->bools.video_crop_overscan;
|
||
*(bool*)data = !video_crop_overscan;
|
||
RARCH_LOG("[Environ]: GET_OVERSCAN: %u\n",
|
||
(unsigned)!video_crop_overscan);
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_CAN_DUPE:
|
||
*(bool*)data = true;
|
||
RARCH_LOG("[Environ]: GET_CAN_DUPE: true\n");
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_VARIABLE:
|
||
{
|
||
unsigned log_level = settings->uints.libretro_log_level;
|
||
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_LOG("[Environ]: GET_VARIABLE %s: not implemented.\n",
|
||
var->key);
|
||
return true;
|
||
}
|
||
|
||
#ifdef HAVE_RUNAHEAD
|
||
if (runloop_st->core_options->updated)
|
||
runloop_st->has_variable_update = true;
|
||
#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 (log_level == RETRO_LOG_DEBUG)
|
||
{
|
||
char s[128];
|
||
s[0] = '\0';
|
||
|
||
snprintf(s, sizeof(s), "[Environ]: GET_VARIABLE: %s = \"%s\"\n",
|
||
var->key, var->value ? var->value :
|
||
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE));
|
||
RARCH_LOG("%s", s);
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_VARIABLE:
|
||
{
|
||
unsigned log_level = settings->uints.libretro_log_level;
|
||
const struct retro_variable *var = (const struct retro_variable*)data;
|
||
size_t opt_idx;
|
||
size_t val_idx;
|
||
|
||
/* If core passes NULL to the callback, return
|
||
* value indicates whether callback is supported */
|
||
if (!var)
|
||
return true;
|
||
|
||
if (string_is_empty(var->key) ||
|
||
string_is_empty(var->value))
|
||
return false;
|
||
|
||
if (!runloop_st->core_options)
|
||
{
|
||
RARCH_LOG("[Environ]: SET_VARIABLE %s: not implemented.\n",
|
||
var->key);
|
||
return false;
|
||
}
|
||
|
||
/* Check whether key is valid */
|
||
if (!core_option_manager_get_idx(runloop_st->core_options,
|
||
var->key, &opt_idx))
|
||
{
|
||
RARCH_LOG("[Environ]: SET_VARIABLE %s: invalid key.\n",
|
||
var->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_LOG("[Environ]: SET_VARIABLE %s: invalid value: %s\n",
|
||
var->key, 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);
|
||
|
||
if (log_level == RETRO_LOG_DEBUG)
|
||
RARCH_LOG("[Environ]: SET_VARIABLE %s:\n\t%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->game_options_active,
|
||
path_get(RARCH_PATH_CORE_OPTIONS),
|
||
runloop_st->core_options);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
runloop_st->core_options = NULL;
|
||
}
|
||
if ((new_vars = runloop_init_core_variables(
|
||
settings,
|
||
(const struct retro_variable *)data)))
|
||
runloop_st->core_options = new_vars;
|
||
}
|
||
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS:
|
||
RARCH_LOG("[Environ]: SET_CORE_OPTIONS.\n");
|
||
|
||
{
|
||
/* Parse core_option_definition array to
|
||
* create retro_core_options_v2 struct */
|
||
struct retro_core_options_v2 *options_v2 =
|
||
core_option_manager_convert_v1(
|
||
(const struct retro_core_option_definition*)data);
|
||
|
||
if (runloop_st->core_options)
|
||
{
|
||
runloop_deinit_core_options(
|
||
runloop_st->game_options_active,
|
||
path_get(RARCH_PATH_CORE_OPTIONS),
|
||
runloop_st->core_options);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
runloop_st->core_options = NULL;
|
||
}
|
||
|
||
if (options_v2)
|
||
{
|
||
/* Initialise core options */
|
||
core_option_manager_t *new_vars = runloop_init_core_options(settings, options_v2);
|
||
if (new_vars)
|
||
runloop_st->core_options = new_vars;
|
||
/* Clean up */
|
||
core_option_manager_free_converted(options_v2);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL:
|
||
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL.\n");
|
||
|
||
{
|
||
/* Parse core_options_intl to create
|
||
* retro_core_options_v2 struct */
|
||
struct retro_core_options_v2 *options_v2 =
|
||
core_option_manager_convert_v1_intl(
|
||
(const struct retro_core_options_intl*)data);
|
||
|
||
if (runloop_st->core_options)
|
||
{
|
||
runloop_deinit_core_options(
|
||
runloop_st->game_options_active,
|
||
path_get(RARCH_PATH_CORE_OPTIONS),
|
||
runloop_st->core_options);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
runloop_st->core_options = NULL;
|
||
}
|
||
|
||
if (options_v2)
|
||
{
|
||
/* Initialise core options */
|
||
core_option_manager_t *new_vars = runloop_init_core_options(settings, options_v2);
|
||
|
||
if (new_vars)
|
||
runloop_st->core_options = new_vars;
|
||
|
||
/* Clean up */
|
||
core_option_manager_free_converted(options_v2);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2:
|
||
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2.\n");
|
||
|
||
{
|
||
core_option_manager_t *new_vars = NULL;
|
||
const struct retro_core_options_v2 *options_v2 =
|
||
(const struct retro_core_options_v2 *)data;
|
||
bool categories_enabled =
|
||
settings->bools.core_option_category_enable;
|
||
|
||
if (runloop_st->core_options)
|
||
{
|
||
runloop_deinit_core_options(
|
||
runloop_st->game_options_active,
|
||
path_get(RARCH_PATH_CORE_OPTIONS),
|
||
runloop_st->core_options);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
runloop_st->core_options = NULL;
|
||
}
|
||
|
||
if (options_v2)
|
||
{
|
||
new_vars = runloop_init_core_options(settings, options_v2);
|
||
if (new_vars)
|
||
runloop_st->core_options = new_vars;
|
||
}
|
||
|
||
/* Return value does not indicate success.
|
||
* Callback returns 'true' if core option
|
||
* categories are supported/enabled,
|
||
* otherwise 'false'. */
|
||
return categories_enabled;
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL:
|
||
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL.\n");
|
||
|
||
{
|
||
/* Parse retro_core_options_v2_intl to create
|
||
* retro_core_options_v2 struct */
|
||
core_option_manager_t *new_vars = NULL;
|
||
struct retro_core_options_v2 *options_v2 =
|
||
core_option_manager_convert_v2_intl(
|
||
(const struct retro_core_options_v2_intl*)data);
|
||
bool categories_enabled =
|
||
settings->bools.core_option_category_enable;
|
||
|
||
if (runloop_st->core_options)
|
||
{
|
||
runloop_deinit_core_options(
|
||
runloop_st->game_options_active,
|
||
path_get(RARCH_PATH_CORE_OPTIONS),
|
||
runloop_st->core_options);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
runloop_st->core_options = NULL;
|
||
}
|
||
|
||
if (options_v2)
|
||
{
|
||
/* Initialise core options */
|
||
new_vars = runloop_init_core_options(settings, options_v2);
|
||
if (new_vars)
|
||
runloop_st->core_options = new_vars;
|
||
|
||
/* Clean up */
|
||
core_option_manager_free_converted(options_v2);
|
||
}
|
||
|
||
/* Return value does not indicate success.
|
||
* Callback returns 'true' if core option
|
||
* categories are supported/enabled,
|
||
* otherwise 'false'. */
|
||
return categories_enabled;
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY:
|
||
RARCH_DBG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY.\n");
|
||
|
||
{
|
||
const struct retro_core_option_display *core_options_display =
|
||
(const struct retro_core_option_display *)data;
|
||
|
||
if (runloop_st->core_options && core_options_display)
|
||
core_option_manager_set_visible(
|
||
runloop_st->core_options,
|
||
core_options_display->key,
|
||
core_options_display->visible);
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK:
|
||
RARCH_DBG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK.\n");
|
||
|
||
{
|
||
const struct retro_core_options_update_display_callback
|
||
*update_display_callback =
|
||
(const struct retro_core_options_update_display_callback*)data;
|
||
|
||
if (update_display_callback &&
|
||
update_display_callback->callback)
|
||
runloop_st->core_options_callback.update_display =
|
||
update_display_callback->callback;
|
||
else
|
||
runloop_st->core_options_callback.update_display = NULL;
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION:
|
||
RARCH_LOG("[Environ]: GET_MESSAGE_INTERFACE_VERSION.\n");
|
||
/* Current API version is 1 */
|
||
*(unsigned *)data = 1;
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_MESSAGE:
|
||
{
|
||
const struct retro_message *msg = (const struct retro_message*)data;
|
||
RARCH_LOG("[Environ]: SET_MESSAGE: %s\n", msg->msg);
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (dispwidget_get_ptr()->active)
|
||
gfx_widget_set_libretro_message(
|
||
msg->msg,
|
||
roundf((float)msg->frames / 60.0f * 1000.0f));
|
||
else
|
||
#endif
|
||
runloop_msg_queue_push(msg->msg, 3, msg->frames,
|
||
true, NULL, MESSAGE_QUEUE_ICON_DEFAULT,
|
||
MESSAGE_QUEUE_CATEGORY_INFO);
|
||
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)
|
||
{
|
||
settings_t *settings = config_get_ptr();
|
||
unsigned log_level = settings->uints.frontend_log_level;
|
||
switch (msg->level)
|
||
{
|
||
case RETRO_LOG_DEBUG:
|
||
if (log_level == RETRO_LOG_DEBUG)
|
||
RARCH_LOG("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
|
||
break;
|
||
case RETRO_LOG_WARN:
|
||
RARCH_WARN("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
|
||
break;
|
||
case RETRO_LOG_ERROR:
|
||
RARCH_ERR("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
|
||
break;
|
||
case RETRO_LOG_INFO:
|
||
default:
|
||
RARCH_LOG("[Environ]: SET_MESSAGE_EXT: %s\n", msg->msg);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Display message via OSD, if required */
|
||
if (msg->target != RETRO_MESSAGE_TARGET_LOG)
|
||
{
|
||
switch (msg->type)
|
||
{
|
||
/* Handle 'status' messages */
|
||
case RETRO_MESSAGE_TYPE_STATUS:
|
||
|
||
/* Note: We need to lock a mutex here. Strictly
|
||
* speaking, 'core_status_msg' is not part
|
||
* of the message queue, but:
|
||
* - It may be implemented as a queue in the future
|
||
* - It seems unnecessary to create a new slock_t
|
||
* object for this type of message when
|
||
* _runloop_msg_queue_lock is already available
|
||
* We therefore just call runloop_msg_queue_lock()/
|
||
* runloop_msg_queue_unlock() in this case */
|
||
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
|
||
|
||
/* If a message is already set, only overwrite
|
||
* it if the new message has the same or higher
|
||
* priority */
|
||
if (!runloop_st->core_status_msg.set ||
|
||
(runloop_st->core_status_msg.priority <= msg->priority))
|
||
{
|
||
if (!string_is_empty(msg->msg))
|
||
{
|
||
strlcpy(runloop_st->core_status_msg.str, msg->msg,
|
||
sizeof(runloop_st->core_status_msg.str));
|
||
|
||
runloop_st->core_status_msg.duration = (float)msg->duration;
|
||
runloop_st->core_status_msg.set = true;
|
||
}
|
||
else
|
||
{
|
||
/* Ensure sane behaviour if core sends an
|
||
* empty message */
|
||
runloop_st->core_status_msg.str[0] = '\0';
|
||
runloop_st->core_status_msg.priority = 0;
|
||
runloop_st->core_status_msg.duration = 0.0f;
|
||
runloop_st->core_status_msg.set = false;
|
||
}
|
||
}
|
||
|
||
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
|
||
break;
|
||
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
/* Handle 'alternate' non-queued notifications */
|
||
case RETRO_MESSAGE_TYPE_NOTIFICATION_ALT:
|
||
{
|
||
video_driver_state_t *video_st =
|
||
video_state_get_ptr();
|
||
if (dispwidget_get_ptr()->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();
|
||
if (dispwidget_get_ptr()->active)
|
||
gfx_widget_set_progress_message(
|
||
msg->msg, msg->duration,
|
||
msg->priority, msg->progress);
|
||
else
|
||
runloop_core_msg_queue_push(
|
||
&video_st->av_info, msg);
|
||
|
||
}
|
||
break;
|
||
#endif
|
||
/* Handle standard (queued) notifications */
|
||
case RETRO_MESSAGE_TYPE_NOTIFICATION:
|
||
default:
|
||
{
|
||
video_driver_state_t *video_st =
|
||
video_state_get_ptr();
|
||
runloop_core_msg_queue_push(
|
||
&video_st->av_info, msg);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_ROTATION:
|
||
{
|
||
unsigned rotation = *(const unsigned*)data;
|
||
bool video_allow_rotate = settings->bools.video_allow_rotate;
|
||
|
||
RARCH_LOG("[Environ]: SET_ROTATION: %u\n", rotation);
|
||
if (!video_allow_rotate)
|
||
return false;
|
||
|
||
if (system)
|
||
system->rotation = rotation;
|
||
|
||
if (!video_driver_set_rotation(rotation))
|
||
return false;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SHUTDOWN:
|
||
{
|
||
#ifdef HAVE_MENU
|
||
struct menu_state *menu_st = menu_state_get_ptr();
|
||
#endif
|
||
/* This case occurs when a core (internally)
|
||
* requests a shutdown event */
|
||
RARCH_LOG("[Environ]: SHUTDOWN.\n");
|
||
|
||
runloop_st->shutdown_initiated = true;
|
||
runloop_st->core_shutdown_initiated = true;
|
||
#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->pending_env_shutdown_flush = true;
|
||
if (!string_is_empty(content_path))
|
||
strlcpy(menu_st->pending_env_shutdown_content_path,
|
||
content_path,
|
||
sizeof(menu_st->pending_env_shutdown_content_path));
|
||
else
|
||
menu_st->pending_env_shutdown_content_path[0] = '\0';
|
||
}
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
|
||
if (system)
|
||
{
|
||
system->performance_level = *(const unsigned*)data;
|
||
RARCH_LOG("[Environ]: PERFORMANCE_LEVEL: %u.\n",
|
||
system->performance_level);
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
||
{
|
||
const char *dir_system = settings->paths.directory_system;
|
||
bool systemfiles_in_content_dir = settings->bools.systemfiles_in_content_dir;
|
||
if (string_is_empty(dir_system) || systemfiles_in_content_dir)
|
||
{
|
||
const char *fullpath = path_get(RARCH_PATH_CONTENT);
|
||
if (!string_is_empty(fullpath))
|
||
{
|
||
char tmp_path[PATH_MAX_LENGTH];
|
||
if (string_is_empty(dir_system))
|
||
RARCH_WARN("[Environ]: SYSTEM DIR is empty, assume CONTENT DIR %s\n",
|
||
fullpath);
|
||
strlcpy(tmp_path, fullpath, sizeof(tmp_path));
|
||
path_basedir(tmp_path);
|
||
dir_set(RARCH_DIR_SYSTEM, tmp_path);
|
||
}
|
||
|
||
*(const char**)data = dir_get_ptr(RARCH_DIR_SYSTEM);
|
||
RARCH_LOG("[Environ]: SYSTEM_DIRECTORY: \"%s\".\n",
|
||
dir_system);
|
||
}
|
||
else
|
||
{
|
||
*(const char**)data = dir_system;
|
||
RARCH_LOG("[Environ]: SYSTEM_DIRECTORY: \"%s\".\n",
|
||
dir_system);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
||
RARCH_LOG("[Environ]: GET_SAVE_DIRECTORY.\n");
|
||
*(const char**)data = runloop_st->savefile_dir;
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_USERNAME:
|
||
*(const char**)data = *settings->paths.username ?
|
||
settings->paths.username : NULL;
|
||
RARCH_LOG("[Environ]: GET_USERNAME: \"%s\".\n",
|
||
settings->paths.username);
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_LANGUAGE:
|
||
#ifdef HAVE_LANGEXTRA
|
||
{
|
||
unsigned user_lang = *msg_hash_get_uint(MSG_HASH_USER_LANGUAGE);
|
||
*(unsigned *)data = user_lang;
|
||
RARCH_LOG("[Environ]: GET_LANGUAGE: \"%u\".\n", user_lang);
|
||
}
|
||
#endif
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
|
||
{
|
||
video_driver_state_t *video_st =
|
||
video_state_get_ptr();
|
||
enum retro_pixel_format pix_fmt =
|
||
*(const enum retro_pixel_format*)data;
|
||
|
||
switch (pix_fmt)
|
||
{
|
||
case RETRO_PIXEL_FORMAT_0RGB1555:
|
||
RARCH_LOG("[Environ]: SET_PIXEL_FORMAT: 0RGB1555.\n");
|
||
break;
|
||
|
||
case RETRO_PIXEL_FORMAT_RGB565:
|
||
RARCH_LOG("[Environ]: SET_PIXEL_FORMAT: RGB565.\n");
|
||
break;
|
||
case RETRO_PIXEL_FORMAT_XRGB8888:
|
||
RARCH_LOG("[Environ]: SET_PIXEL_FORMAT: XRGB8888.\n");
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
|
||
video_st->pix_fmt = pix_fmt;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
|
||
{
|
||
static const char *libretro_btn_desc[] = {
|
||
"B (bottom)", "Y (left)", "Select", "Start",
|
||
"D-Pad Up", "D-Pad Down", "D-Pad Left", "D-Pad Right",
|
||
"A (right)", "X (up)",
|
||
"L", "R", "L2", "R2", "L3", "R3",
|
||
};
|
||
|
||
if (system)
|
||
{
|
||
unsigned retro_id;
|
||
const struct retro_input_descriptor *desc = NULL;
|
||
memset((void*)&system->input_desc_btn, 0,
|
||
sizeof(system->input_desc_btn));
|
||
|
||
desc = (const struct retro_input_descriptor*)data;
|
||
|
||
for (; desc->description; desc++)
|
||
{
|
||
unsigned retro_port = desc->port;
|
||
|
||
retro_id = desc->id;
|
||
|
||
if (desc->port >= MAX_USERS)
|
||
continue;
|
||
|
||
if (desc->id >= RARCH_FIRST_CUSTOM_BIND)
|
||
continue;
|
||
|
||
switch (desc->device)
|
||
{
|
||
case RETRO_DEVICE_JOYPAD:
|
||
system->input_desc_btn[retro_port]
|
||
[retro_id] = desc->description;
|
||
break;
|
||
case RETRO_DEVICE_ANALOG:
|
||
switch (retro_id)
|
||
{
|
||
case RETRO_DEVICE_ID_ANALOG_X:
|
||
switch (desc->index)
|
||
{
|
||
case RETRO_DEVICE_INDEX_ANALOG_LEFT:
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_LEFT_X_PLUS] = desc->description;
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_LEFT_X_MINUS] = desc->description;
|
||
break;
|
||
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_RIGHT_X_PLUS] = desc->description;
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_RIGHT_X_MINUS] = desc->description;
|
||
break;
|
||
}
|
||
break;
|
||
case RETRO_DEVICE_ID_ANALOG_Y:
|
||
switch (desc->index)
|
||
{
|
||
case RETRO_DEVICE_INDEX_ANALOG_LEFT:
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_LEFT_Y_PLUS] = desc->description;
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_LEFT_Y_MINUS] = desc->description;
|
||
break;
|
||
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_RIGHT_Y_PLUS] = desc->description;
|
||
system->input_desc_btn[retro_port]
|
||
[RARCH_ANALOG_RIGHT_Y_MINUS] = desc->description;
|
||
break;
|
||
}
|
||
break;
|
||
case RETRO_DEVICE_ID_JOYPAD_R2:
|
||
switch (desc->index)
|
||
{
|
||
case RETRO_DEVICE_INDEX_ANALOG_BUTTON:
|
||
system->input_desc_btn[retro_port]
|
||
[retro_id] = desc->description;
|
||
break;
|
||
}
|
||
break;
|
||
case RETRO_DEVICE_ID_JOYPAD_L2:
|
||
switch (desc->index)
|
||
{
|
||
case RETRO_DEVICE_INDEX_ANALOG_BUTTON:
|
||
system->input_desc_btn[retro_port]
|
||
[retro_id] = desc->description;
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
RARCH_LOG("[Environ]: SET_INPUT_DESCRIPTORS:\n");
|
||
|
||
{
|
||
unsigned log_level = settings->uints.libretro_log_level;
|
||
|
||
if (log_level == RETRO_LOG_DEBUG)
|
||
{
|
||
unsigned input_driver_max_users =
|
||
settings->uints.input_max_users;
|
||
for (p = 0; p < input_driver_max_users; p++)
|
||
{
|
||
unsigned mapped_port = settings->uints.input_remap_ports[p];
|
||
|
||
for (retro_id = 0; retro_id < RARCH_FIRST_CUSTOM_BIND; retro_id++)
|
||
{
|
||
const char *description = system->input_desc_btn[mapped_port][retro_id];
|
||
|
||
if (!description)
|
||
continue;
|
||
|
||
RARCH_LOG(" RetroPad, Port %u, Button \"%s\" => \"%s\"\n",
|
||
p + 1, libretro_btn_desc[retro_id], description);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
runloop_st->current_core.has_set_input_descriptors = true;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
|
||
{
|
||
input_driver_state_t
|
||
*input_st = input_state_get_ptr();
|
||
const struct retro_keyboard_callback *info =
|
||
(const struct retro_keyboard_callback*)data;
|
||
retro_keyboard_event_t *frontend_key_event = &runloop_st->frontend_key_event;
|
||
retro_keyboard_event_t *key_event = &runloop_st->key_event;
|
||
|
||
RARCH_LOG("[Environ]: SET_KEYBOARD_CALLBACK.\n");
|
||
if (key_event)
|
||
*key_event = info->callback;
|
||
|
||
if (frontend_key_event && key_event)
|
||
*frontend_key_event = *key_event;
|
||
|
||
/* If a core calls RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK,
|
||
* then it is assumed that game focus mode is desired */
|
||
input_st->game_focus_state.core_requested = true;
|
||
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION:
|
||
RARCH_LOG("[Environ]: GET_DISK_CONTROL_INTERFACE_VERSION.\n");
|
||
/* Current API version is 1 */
|
||
*(unsigned *)data = 1;
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE:
|
||
{
|
||
const struct retro_disk_control_callback *control_cb =
|
||
(const struct retro_disk_control_callback*)data;
|
||
|
||
if (system)
|
||
{
|
||
RARCH_LOG("[Environ]: SET_DISK_CONTROL_INTERFACE.\n");
|
||
disk_control_set_callback(&system->disk_control, control_cb);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE:
|
||
{
|
||
const struct retro_disk_control_ext_callback *control_cb =
|
||
(const struct retro_disk_control_ext_callback*)data;
|
||
|
||
if (system)
|
||
{
|
||
RARCH_LOG("[Environ]: SET_DISK_CONTROL_EXT_INTERFACE.\n");
|
||
disk_control_set_ext_callback(&system->disk_control, control_cb);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER:
|
||
{
|
||
unsigned *cb = (unsigned*)data;
|
||
settings_t *settings = config_get_ptr();
|
||
const char *video_driver_name = settings->arrays.video_driver;
|
||
bool driver_switch_enable = settings->bools.driver_switch_enable;
|
||
|
||
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER, video driver name: %s.\n", video_driver_name);
|
||
|
||
if (string_is_equal(video_driver_name, "glcore"))
|
||
{
|
||
*cb = RETRO_HW_CONTEXT_OPENGL_CORE;
|
||
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_OPENGL_CORE.\n");
|
||
}
|
||
else if (string_is_equal(video_driver_name, "gl"))
|
||
{
|
||
*cb = RETRO_HW_CONTEXT_OPENGL;
|
||
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_OPENGL.\n");
|
||
}
|
||
else if (string_is_equal(video_driver_name, "vulkan"))
|
||
{
|
||
*cb = RETRO_HW_CONTEXT_VULKAN;
|
||
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_VULKAN.\n");
|
||
}
|
||
else if (!strncmp(video_driver_name, "d3d", 3))
|
||
{
|
||
*cb = RETRO_HW_CONTEXT_DIRECT3D;
|
||
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_DIRECT3D.\n");
|
||
}
|
||
else
|
||
{
|
||
*cb = RETRO_HW_CONTEXT_NONE;
|
||
RARCH_LOG("[Environ]: GET_PREFERRED_HW_RENDER - Context callback set to RETRO_HW_CONTEXT_NONE.\n");
|
||
}
|
||
|
||
if (!driver_switch_enable)
|
||
{
|
||
RARCH_LOG("[Environ]: Driver switching disabled, GET_PREFERRED_HW_RENDER will be ignored.\n");
|
||
return false;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_HW_RENDER:
|
||
case RETRO_ENVIRONMENT_SET_HW_RENDER | RETRO_ENVIRONMENT_EXPERIMENTAL:
|
||
{
|
||
settings_t *settings = config_get_ptr();
|
||
struct retro_hw_render_callback *cb =
|
||
(struct retro_hw_render_callback*)data;
|
||
video_driver_state_t *video_st =
|
||
video_state_get_ptr();
|
||
struct retro_hw_render_callback *hwr =
|
||
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(video_st);
|
||
|
||
if (!cb)
|
||
{
|
||
RARCH_ERR("[Environ]: SET_HW_RENDER - No valid callback passed, returning...\n");
|
||
return false;
|
||
}
|
||
|
||
RARCH_LOG("[Environ]: SET_HW_RENDER, context type: %s.\n", hw_render_context_name(cb->context_type, cb->version_major, cb->version_minor));
|
||
|
||
if (!dynamic_request_hw_context(
|
||
cb->context_type, cb->version_minor, cb->version_major))
|
||
{
|
||
RARCH_ERR("[Environ]: SET_HW_RENDER - Dynamic request HW context failed.\n");
|
||
return false;
|
||
}
|
||
|
||
if (!dynamic_verify_hw_context(
|
||
settings->arrays.video_driver,
|
||
settings->bools.driver_switch_enable,
|
||
cb->context_type, cb->version_minor, cb->version_major))
|
||
{
|
||
RARCH_ERR("[Environ]: SET_HW_RENDER: Dynamic verify HW context failed.\n");
|
||
return false;
|
||
}
|
||
|
||
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGL_CORE)
|
||
/* TODO/FIXME - should check first if an OpenGL
|
||
* driver is running */
|
||
if (cb->context_type == RETRO_HW_CONTEXT_OPENGL_CORE)
|
||
{
|
||
/* Ensure that the rest of the frontend knows
|
||
* we have a core context */
|
||
gfx_ctx_flags_t flags;
|
||
flags.flags = 0;
|
||
BIT32_SET(flags.flags, GFX_CTX_FLAGS_GL_CORE_CONTEXT);
|
||
|
||
video_context_driver_set_flags(&flags);
|
||
}
|
||
#endif
|
||
|
||
cb->get_current_framebuffer = video_driver_get_current_framebuffer;
|
||
cb->get_proc_address = video_driver_get_proc_address;
|
||
|
||
/* Old ABI. Don't copy garbage. */
|
||
if (cmd & RETRO_ENVIRONMENT_EXPERIMENTAL)
|
||
{
|
||
memcpy(hwr,
|
||
cb, offsetof(struct retro_hw_render_callback, stencil));
|
||
memset((uint8_t*)hwr + offsetof(struct retro_hw_render_callback, stencil),
|
||
0, sizeof(*cb) - offsetof(struct retro_hw_render_callback, stencil));
|
||
}
|
||
else
|
||
memcpy(hwr, cb, sizeof(*cb));
|
||
RARCH_LOG("Reached end of SET_HW_RENDER.\n");
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||
{
|
||
bool state = *(const bool*)data;
|
||
RARCH_LOG("[Environ]: SET_SUPPORT_NO_GAME: %s.\n", state ? "yes" : "no");
|
||
|
||
if (state)
|
||
content_set_does_not_need_content();
|
||
else
|
||
content_unset_does_not_need_content();
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH:
|
||
{
|
||
const char **path = (const char**)data;
|
||
RARCH_LOG("[Environ]: GET_LIBRETRO_PATH.\n");
|
||
#ifdef HAVE_DYNAMIC
|
||
*path = path_get(RARCH_PATH_CORE);
|
||
#else
|
||
*path = NULL;
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK:
|
||
#ifdef HAVE_THREADS
|
||
{
|
||
recording_state_t
|
||
*recording_st = recording_state_get_ptr();
|
||
audio_driver_state_t
|
||
*audio_st = audio_state_get_ptr();
|
||
const struct
|
||
retro_audio_callback *cb = (const struct retro_audio_callback*)data;
|
||
RARCH_LOG("[Environ]: SET_AUDIO_CALLBACK.\n");
|
||
#ifdef HAVE_NETWORKING
|
||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||
return false;
|
||
#endif
|
||
if (recording_st->data) /* A/V sync is a must. */
|
||
return false;
|
||
if (cb)
|
||
audio_st->callback = *cb;
|
||
}
|
||
break;
|
||
#else
|
||
return false;
|
||
#endif
|
||
|
||
case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
|
||
{
|
||
const struct retro_frame_time_callback *info =
|
||
(const struct retro_frame_time_callback*)data;
|
||
|
||
RARCH_LOG("[Environ]: SET_FRAME_TIME_CALLBACK.\n");
|
||
#ifdef HAVE_NETWORKING
|
||
/* retro_run() will be called in very strange and
|
||
* mysterious ways, have to disable it. */
|
||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||
return false;
|
||
#endif
|
||
runloop_st->frame_time = *info;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK:
|
||
{
|
||
const struct retro_audio_buffer_status_callback *info =
|
||
(const struct retro_audio_buffer_status_callback*)data;
|
||
|
||
RARCH_LOG("[Environ]: SET_AUDIO_BUFFER_STATUS_CALLBACK.\n");
|
||
|
||
if (info)
|
||
runloop_st->audio_buffer_status.callback = info->callback;
|
||
else
|
||
runloop_st->audio_buffer_status.callback = NULL;
|
||
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY:
|
||
{
|
||
unsigned audio_latency_default = settings->uints.audio_latency;
|
||
unsigned audio_latency_current =
|
||
(runloop_st->audio_latency > audio_latency_default) ?
|
||
runloop_st->audio_latency : audio_latency_default;
|
||
unsigned audio_latency_new;
|
||
|
||
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY.\n");
|
||
|
||
/* Sanitise input latency value */
|
||
runloop_st->audio_latency = 0;
|
||
if (data)
|
||
runloop_st->audio_latency = *(const unsigned*)data;
|
||
if (runloop_st->audio_latency > 512)
|
||
{
|
||
RARCH_WARN("[Environ]: Requested audio latency of %u ms - limiting to maximum of 512 ms.\n",
|
||
runloop_st->audio_latency);
|
||
runloop_st->audio_latency = 512;
|
||
}
|
||
|
||
/* Determine new set-point latency value */
|
||
if (runloop_st->audio_latency >= audio_latency_default)
|
||
audio_latency_new = runloop_st->audio_latency;
|
||
else
|
||
{
|
||
if (runloop_st->audio_latency != 0)
|
||
RARCH_WARN("[Environ]: Requested audio latency of %u ms is less than frontend default of %u ms."
|
||
" Using frontend default...\n",
|
||
runloop_st->audio_latency, audio_latency_default);
|
||
|
||
audio_latency_new = audio_latency_default;
|
||
}
|
||
|
||
/* Check whether audio driver requires reinitialisation
|
||
* (Identical to RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO,
|
||
* without video driver initialisation) */
|
||
if (audio_latency_new != audio_latency_current)
|
||
{
|
||
recording_state_t
|
||
*recording_st = recording_state_get_ptr();
|
||
bool video_fullscreen = settings->bools.video_fullscreen;
|
||
int reinit_flags = DRIVERS_CMD_ALL &
|
||
~(DRIVER_VIDEO_MASK | DRIVER_INPUT_MASK | DRIVER_MENU_MASK);
|
||
|
||
RARCH_LOG("[Environ]: Setting audio latency to %u ms.\n", audio_latency_new);
|
||
|
||
command_event(CMD_EVENT_REINIT, &reinit_flags);
|
||
video_driver_set_aspect_ratio();
|
||
|
||
/* Cannot continue recording with different
|
||
* parameters.
|
||
* Take the easiest route out and just restart
|
||
* the recording. */
|
||
|
||
if (recording_st->data)
|
||
{
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_RESTARTING_RECORDING_DUE_TO_DRIVER_REINIT),
|
||
2, 180, false,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
|
||
command_event(CMD_EVENT_RECORD_INIT, NULL);
|
||
}
|
||
|
||
/* Hide mouse cursor in fullscreen mode */
|
||
if (video_fullscreen)
|
||
video_driver_hide_mouse();
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
|
||
{
|
||
struct retro_rumble_interface *iface =
|
||
(struct retro_rumble_interface*)data;
|
||
|
||
RARCH_LOG("[Environ]: GET_RUMBLE_INTERFACE.\n");
|
||
iface->set_rumble_state = input_set_rumble_state;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES:
|
||
{
|
||
uint64_t *mask = (uint64_t*)data;
|
||
input_driver_state_t
|
||
*input_st = input_state_get_ptr();
|
||
|
||
RARCH_LOG("[Environ]: GET_INPUT_DEVICE_CAPABILITIES.\n");
|
||
if (!input_st->current_driver->get_capabilities ||
|
||
!input_st->current_data)
|
||
return false;
|
||
*mask = input_driver_get_capabilities();
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE:
|
||
{
|
||
settings_t *settings = config_get_ptr();
|
||
bool input_sensors_enable = settings->bools.input_sensors_enable;
|
||
struct retro_sensor_interface *iface = (struct retro_sensor_interface*)data;
|
||
|
||
RARCH_LOG("[Environ]: GET_SENSOR_INTERFACE.\n");
|
||
|
||
if (!input_sensors_enable)
|
||
return false;
|
||
|
||
iface->set_sensor_state = input_set_sensor_state;
|
||
iface->get_sensor_input = input_get_sensor_state;
|
||
break;
|
||
}
|
||
case RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE:
|
||
{
|
||
struct retro_camera_callback *cb =
|
||
(struct retro_camera_callback*)data;
|
||
camera_driver_state_t *camera_st = camera_state_get_ptr();
|
||
|
||
RARCH_LOG("[Environ]: GET_CAMERA_INTERFACE.\n");
|
||
cb->start = driver_camera_start;
|
||
cb->stop = driver_camera_stop;
|
||
|
||
camera_st->cb = *cb;
|
||
camera_st->active = (cb->caps != 0);
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
|
||
{
|
||
struct retro_location_callback *cb =
|
||
(struct retro_location_callback*)data;
|
||
location_driver_state_t
|
||
*location_st = location_state_get_ptr();
|
||
|
||
RARCH_LOG("[Environ]: GET_LOCATION_INTERFACE.\n");
|
||
cb->start = driver_location_start;
|
||
cb->stop = driver_location_stop;
|
||
cb->get_position = driver_location_get_position;
|
||
cb->set_interval = driver_location_set_interval;
|
||
|
||
if (system)
|
||
system->location_cb = *cb;
|
||
|
||
location_st->active = false;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
||
{
|
||
struct retro_log_callback *cb = (struct retro_log_callback*)data;
|
||
|
||
RARCH_LOG("[Environ]: GET_LOG_INTERFACE.\n");
|
||
cb->log = libretro_log_cb;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
|
||
{
|
||
struct retro_perf_callback *cb = (struct retro_perf_callback*)data;
|
||
|
||
RARCH_LOG("[Environ]: GET_PERF_INTERFACE.\n");
|
||
cb->get_time_usec = cpu_features_get_time_usec;
|
||
cb->get_cpu_features = cpu_features_get;
|
||
cb->get_perf_counter = cpu_features_get_perf_counter;
|
||
|
||
cb->perf_register = runloop_performance_counter_register;
|
||
cb->perf_start = core_performance_counter_start;
|
||
cb->perf_stop = core_performance_counter_stop;
|
||
cb->perf_log = runloop_perf_log;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
|
||
{
|
||
const char **dir = (const char**)data;
|
||
const char *dir_core_assets = settings->paths.directory_core_assets;
|
||
|
||
*dir = *dir_core_assets ?
|
||
dir_core_assets : NULL;
|
||
RARCH_LOG("[Environ]: CORE_ASSETS_DIRECTORY: \"%s\".\n",
|
||
dir_core_assets);
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
|
||
/**
|
||
* Update the system Audio/Video information.
|
||
* Will reinitialize audio/video drivers if needed.
|
||
* Used by RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO.
|
||
**/
|
||
{
|
||
const struct retro_system_av_info **info = (const struct retro_system_av_info**)&data;
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
struct retro_system_av_info *av_info = &video_st->av_info;
|
||
if (data)
|
||
{
|
||
int reinit_flags = DRIVERS_CMD_ALL;
|
||
settings_t *settings = config_get_ptr();
|
||
float refresh_rate = (*info)->timing.fps;
|
||
unsigned crt_switch_resolution = settings->uints.crt_switch_resolution;
|
||
bool video_fullscreen = settings->bools.video_fullscreen;
|
||
bool video_switch_refresh_rate = false;
|
||
bool no_video_reinit = true;
|
||
|
||
/* Refresh rate switch for regular displays */
|
||
if (video_display_server_has_resolution_list())
|
||
video_switch_refresh_rate_maybe(&refresh_rate, &video_switch_refresh_rate);
|
||
|
||
no_video_reinit = (
|
||
crt_switch_resolution == 0
|
||
&& video_switch_refresh_rate == false
|
||
&& data
|
||
&& ((*info)->geometry.max_width == av_info->geometry.max_width)
|
||
&& ((*info)->geometry.max_height == av_info->geometry.max_height));
|
||
|
||
/* First set new refresh rate and display rate, then after REINIT do
|
||
* another display rate change to make sure the change stays */
|
||
if (video_switch_refresh_rate && video_display_server_set_refresh_rate(refresh_rate))
|
||
video_monitor_set_refresh_rate(refresh_rate);
|
||
|
||
/* When not doing video reinit, we also must not do input and menu
|
||
* reinit, otherwise the input driver crashes and the menu gets
|
||
* corrupted. */
|
||
if (no_video_reinit)
|
||
reinit_flags =
|
||
DRIVERS_CMD_ALL &
|
||
~(DRIVER_VIDEO_MASK | DRIVER_INPUT_MASK |
|
||
DRIVER_MENU_MASK);
|
||
|
||
RARCH_LOG("[Environ]: SET_SYSTEM_AV_INFO: %ux%u, Aspect: %.3f, FPS: %.2f, Sample rate: %.2f Hz.\n",
|
||
(*info)->geometry.base_width, (*info)->geometry.base_height,
|
||
(*info)->geometry.aspect_ratio,
|
||
(*info)->timing.fps,
|
||
(*info)->timing.sample_rate);
|
||
|
||
memcpy(av_info, *info, sizeof(*av_info));
|
||
video_st->core_frame_time = 1000000 /
|
||
((video_st->av_info.timing.fps > 0.0) ?
|
||
video_st->av_info.timing.fps : 60.0);
|
||
|
||
command_event(CMD_EVENT_REINIT, &reinit_flags);
|
||
if (no_video_reinit)
|
||
video_driver_set_aspect_ratio();
|
||
|
||
if (video_switch_refresh_rate)
|
||
video_display_server_set_refresh_rate(refresh_rate);
|
||
|
||
/* Cannot continue recording with different parameters.
|
||
* Take the easiest route out and just restart
|
||
* the recording. */
|
||
if (recording_st->data)
|
||
{
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_RESTARTING_RECORDING_DUE_TO_DRIVER_REINIT),
|
||
2, 180, false,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
|
||
command_event(CMD_EVENT_RECORD_INIT, NULL);
|
||
}
|
||
|
||
/* Hide mouse cursor in fullscreen after
|
||
* a RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO call. */
|
||
if (video_fullscreen)
|
||
video_driver_hide_mouse();
|
||
|
||
/* Recalibrate frame delay target */
|
||
if (settings->bools.video_frame_delay_auto)
|
||
video_st->frame_delay_target = 0;
|
||
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
|
||
{
|
||
unsigned i;
|
||
const struct retro_subsystem_info *info =
|
||
(const struct retro_subsystem_info*)data;
|
||
unsigned log_level = settings->uints.libretro_log_level;
|
||
|
||
if (log_level == RETRO_LOG_DEBUG)
|
||
RARCH_LOG("[Environ]: SET_SUBSYSTEM_INFO.\n");
|
||
|
||
for (i = 0; info[i].ident; i++)
|
||
{
|
||
unsigned j;
|
||
if (log_level != RETRO_LOG_DEBUG)
|
||
continue;
|
||
|
||
RARCH_LOG("Special game type: %s\n Ident: %s\n ID: %u\n Content:\n",
|
||
info[i].desc,
|
||
info[i].ident,
|
||
info[i].id
|
||
);
|
||
for (j = 0; j < info[i].num_roms; j++)
|
||
{
|
||
RARCH_LOG(" %s (%s)\n",
|
||
info[i].roms[j].desc, info[i].roms[j].required ?
|
||
"required" : "optional");
|
||
}
|
||
}
|
||
|
||
if (system)
|
||
{
|
||
struct retro_subsystem_info *info_ptr = NULL;
|
||
free(system->subsystem.data);
|
||
system->subsystem.data = NULL;
|
||
system->subsystem.size = 0;
|
||
|
||
info_ptr = (struct retro_subsystem_info*)
|
||
malloc(i * sizeof(*info_ptr));
|
||
|
||
if (!info_ptr)
|
||
return false;
|
||
|
||
system->subsystem.data = info_ptr;
|
||
|
||
memcpy(system->subsystem.data, info,
|
||
i * sizeof(*system->subsystem.data));
|
||
system->subsystem.size = i;
|
||
runloop_st->current_core.has_set_subsystems = true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
|
||
{
|
||
unsigned i, j;
|
||
const struct retro_controller_info *info =
|
||
(const struct retro_controller_info*)data;
|
||
unsigned log_level = settings->uints.libretro_log_level;
|
||
|
||
RARCH_LOG("[Environ]: SET_CONTROLLER_INFO.\n");
|
||
|
||
for (i = 0; info[i].types; i++)
|
||
{
|
||
if (log_level != RETRO_LOG_DEBUG)
|
||
continue;
|
||
|
||
RARCH_LOG(" Controller port: %u\n", i + 1);
|
||
for (j = 0; j < info[i].num_types; j++)
|
||
RARCH_LOG(" %s (ID: %u)\n", info[i].types[j].desc,
|
||
info[i].types[j].id);
|
||
}
|
||
|
||
if (system)
|
||
{
|
||
struct retro_controller_info *info_ptr = NULL;
|
||
|
||
free(system->ports.data);
|
||
system->ports.data = NULL;
|
||
system->ports.size = 0;
|
||
|
||
info_ptr = (struct retro_controller_info*)calloc(i, sizeof(*info_ptr));
|
||
if (!info_ptr)
|
||
return false;
|
||
|
||
system->ports.data = info_ptr;
|
||
memcpy(system->ports.data, info,
|
||
i * sizeof(*system->ports.data));
|
||
system->ports.size = i;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
|
||
{
|
||
if (system)
|
||
{
|
||
unsigned i;
|
||
const struct retro_memory_map *mmaps =
|
||
(const struct retro_memory_map*)data;
|
||
rarch_memory_descriptor_t *descriptors = NULL;
|
||
unsigned int log_level = settings->uints.libretro_log_level;
|
||
|
||
RARCH_LOG("[Environ]: SET_MEMORY_MAPS.\n");
|
||
free((void*)system->mmaps.descriptors);
|
||
system->mmaps.descriptors = 0;
|
||
system->mmaps.num_descriptors = 0;
|
||
descriptors = (rarch_memory_descriptor_t*)
|
||
calloc(mmaps->num_descriptors,
|
||
sizeof(*descriptors));
|
||
|
||
if (!descriptors)
|
||
return false;
|
||
|
||
system->mmaps.descriptors = descriptors;
|
||
system->mmaps.num_descriptors = mmaps->num_descriptors;
|
||
|
||
for (i = 0; i < mmaps->num_descriptors; i++)
|
||
system->mmaps.descriptors[i].core = mmaps->descriptors[i];
|
||
|
||
mmap_preprocess_descriptors(descriptors, mmaps->num_descriptors);
|
||
|
||
if (log_level != RETRO_LOG_DEBUG)
|
||
break;
|
||
|
||
if (sizeof(void *) == 8)
|
||
RARCH_LOG(" ndx flags ptr offset start select disconn len addrspace\n");
|
||
else
|
||
RARCH_LOG(" ndx flags ptr offset start select disconn len addrspace\n");
|
||
|
||
for (i = 0; i < system->mmaps.num_descriptors; i++)
|
||
{
|
||
const rarch_memory_descriptor_t *desc =
|
||
&system->mmaps.descriptors[i];
|
||
char flags[7];
|
||
|
||
flags[0] = 'M';
|
||
if ((desc->core.flags & RETRO_MEMDESC_MINSIZE_8) == RETRO_MEMDESC_MINSIZE_8)
|
||
flags[1] = '8';
|
||
else if ((desc->core.flags & RETRO_MEMDESC_MINSIZE_4) == RETRO_MEMDESC_MINSIZE_4)
|
||
flags[1] = '4';
|
||
else if ((desc->core.flags & RETRO_MEMDESC_MINSIZE_2) == RETRO_MEMDESC_MINSIZE_2)
|
||
flags[1] = '2';
|
||
else
|
||
flags[1] = '1';
|
||
|
||
flags[2] = 'A';
|
||
if ((desc->core.flags & RETRO_MEMDESC_ALIGN_8) == RETRO_MEMDESC_ALIGN_8)
|
||
flags[3] = '8';
|
||
else if ((desc->core.flags & RETRO_MEMDESC_ALIGN_4) == RETRO_MEMDESC_ALIGN_4)
|
||
flags[3] = '4';
|
||
else if ((desc->core.flags & RETRO_MEMDESC_ALIGN_2) == RETRO_MEMDESC_ALIGN_2)
|
||
flags[3] = '2';
|
||
else
|
||
flags[3] = '1';
|
||
|
||
flags[4] = (desc->core.flags & RETRO_MEMDESC_BIGENDIAN) ? 'B' : 'b';
|
||
flags[5] = (desc->core.flags & RETRO_MEMDESC_CONST) ? 'C' : 'c';
|
||
flags[6] = 0;
|
||
|
||
RARCH_LOG(" %03u %s %p %08X %08X %08X %08X %08X %s\n",
|
||
i + 1, flags, desc->core.ptr, desc->core.offset, desc->core.start,
|
||
desc->core.select, desc->core.disconnect, desc->core.len,
|
||
desc->core.addrspace ? desc->core.addrspace : "");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
RARCH_WARN("[Environ]: SET_MEMORY_MAPS, but system pointer not initialized..\n");
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_GEOMETRY:
|
||
{
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
struct retro_system_av_info *av_info = &video_st->av_info;
|
||
struct retro_game_geometry *geom = (struct retro_game_geometry*)&av_info->geometry;
|
||
const struct retro_game_geometry *in_geom= (const struct retro_game_geometry*)data;
|
||
|
||
if (!geom)
|
||
return false;
|
||
|
||
/* Can potentially be called every frame,
|
||
* don't do anything unless required. */
|
||
if ( (geom->base_width != in_geom->base_width) ||
|
||
(geom->base_height != in_geom->base_height) ||
|
||
(geom->aspect_ratio != in_geom->aspect_ratio))
|
||
{
|
||
geom->base_width = in_geom->base_width;
|
||
geom->base_height = in_geom->base_height;
|
||
geom->aspect_ratio = in_geom->aspect_ratio;
|
||
|
||
RARCH_LOG("[Environ]: SET_GEOMETRY: %ux%u, Aspect: %.3f.\n",
|
||
geom->base_width, geom->base_height, geom->aspect_ratio);
|
||
|
||
/* Forces recomputation of aspect ratios if
|
||
* using core-dependent aspect ratios. */
|
||
video_driver_set_aspect_ratio();
|
||
|
||
/* TODO: Figure out what to do, if anything, with
|
||
recording. */
|
||
}
|
||
else
|
||
{
|
||
RARCH_LOG("[Environ]: SET_GEOMETRY.\n");
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER:
|
||
{
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
struct retro_framebuffer *fb = (struct retro_framebuffer*)data;
|
||
if (
|
||
video_st->poke
|
||
&& video_st->poke->get_current_software_framebuffer
|
||
&& video_st->poke->get_current_software_framebuffer(
|
||
video_st->data, fb))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE:
|
||
{
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
const struct retro_hw_render_interface **iface = (const struct retro_hw_render_interface **)data;
|
||
if (
|
||
video_st->poke
|
||
&& video_st->poke->get_hw_render_interface
|
||
&& video_st->poke->get_hw_render_interface(
|
||
video_st->data, iface))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS:
|
||
#ifdef HAVE_CHEEVOS
|
||
{
|
||
bool state = *(const bool*)data;
|
||
RARCH_LOG("[Environ]: SET_SUPPORT_ACHIEVEMENTS: %s.\n", state ? "yes" : "no");
|
||
rcheevos_set_support_cheevos(state);
|
||
}
|
||
#endif
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE:
|
||
{
|
||
video_driver_state_t *video_st =
|
||
video_state_get_ptr();
|
||
const struct retro_hw_render_context_negotiation_interface *iface =
|
||
(const struct retro_hw_render_context_negotiation_interface*)data;
|
||
RARCH_LOG("[Environ]: SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE.\n");
|
||
video_st->hw_render_context_negotiation = iface;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
|
||
{
|
||
uint64_t *quirks = (uint64_t *) data;
|
||
RARCH_LOG("[Environ]: SET_SERIALIZATION_QUIRKS.\n");
|
||
runloop_st->current_core.serialization_quirks_v = *quirks;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
|
||
#ifdef HAVE_LIBNX
|
||
RARCH_LOG("[Environ]: SET_HW_SHARED_CONTEXT - ignored for now.\n");
|
||
/* TODO/FIXME - Force this off for now for Switch
|
||
* until shared HW context can work there */
|
||
return false;
|
||
#else
|
||
RARCH_LOG("[Environ]: SET_HW_SHARED_CONTEXT.\n");
|
||
runloop_st->core_set_shared_context = true;
|
||
#endif
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_VFS_INTERFACE:
|
||
{
|
||
const uint32_t supported_vfs_version = 3;
|
||
static struct retro_vfs_interface vfs_iface =
|
||
{
|
||
/* VFS API v1 */
|
||
retro_vfs_file_get_path_impl,
|
||
retro_vfs_file_open_impl,
|
||
retro_vfs_file_close_impl,
|
||
retro_vfs_file_size_impl,
|
||
retro_vfs_file_tell_impl,
|
||
retro_vfs_file_seek_impl,
|
||
retro_vfs_file_read_impl,
|
||
retro_vfs_file_write_impl,
|
||
retro_vfs_file_flush_impl,
|
||
retro_vfs_file_remove_impl,
|
||
retro_vfs_file_rename_impl,
|
||
/* VFS API v2 */
|
||
retro_vfs_file_truncate_impl,
|
||
/* VFS API v3 */
|
||
retro_vfs_stat_impl,
|
||
retro_vfs_mkdir_impl,
|
||
retro_vfs_opendir_impl,
|
||
retro_vfs_readdir_impl,
|
||
retro_vfs_dirent_get_name_impl,
|
||
retro_vfs_dirent_is_dir_impl,
|
||
retro_vfs_closedir_impl
|
||
};
|
||
|
||
struct retro_vfs_interface_info *vfs_iface_info = (struct retro_vfs_interface_info *) data;
|
||
if (vfs_iface_info->required_interface_version <= supported_vfs_version)
|
||
{
|
||
RARCH_LOG("[Environ]: GET_VFS_INTERFACE. Core requested version >= V%d, providing V%d.\n",
|
||
vfs_iface_info->required_interface_version, supported_vfs_version);
|
||
vfs_iface_info->required_interface_version = supported_vfs_version;
|
||
vfs_iface_info->iface = &vfs_iface;
|
||
system->supports_vfs = true;
|
||
}
|
||
else
|
||
{
|
||
RARCH_WARN("[Environ]: GET_VFS_INTERFACE. Core requested version V%d which is higher than what we support (V%d).\n",
|
||
vfs_iface_info->required_interface_version, supported_vfs_version);
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_LED_INTERFACE:
|
||
{
|
||
struct retro_led_interface *ledintf =
|
||
(struct retro_led_interface *)data;
|
||
if (ledintf)
|
||
ledintf->set_led_state = led_driver_set_led;
|
||
RARCH_LOG("[Environ]: GET_LED_INTERFACE.\n");
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE:
|
||
{
|
||
int result = 0;
|
||
video_driver_state_t
|
||
*video_st = video_state_get_ptr();
|
||
audio_driver_state_t
|
||
*audio_st = audio_state_get_ptr();
|
||
if ( !audio_st->suspended &&
|
||
audio_st->active)
|
||
result |= 2;
|
||
if ( video_st->active
|
||
&& !(video_st->current_video->frame == video_null.frame))
|
||
result |= 1;
|
||
#ifdef HAVE_RUNAHEAD
|
||
if (audio_st->hard_disable)
|
||
result |= 8;
|
||
#endif
|
||
#ifdef HAVE_NETWORKING
|
||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_REPLAYING, NULL))
|
||
result &= ~(1|2);
|
||
#endif
|
||
#if defined(HAVE_RUNAHEAD) || defined(HAVE_NETWORKING)
|
||
if (runloop_st->request_fast_savestate)
|
||
result |= 4;
|
||
#endif
|
||
if (data)
|
||
{
|
||
int* result_p = (int*)data;
|
||
*result_p = result;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT:
|
||
{
|
||
int result = RETRO_SAVESTATE_CONTEXT_NORMAL;
|
||
#if defined(HAVE_RUNAHEAD) || defined(HAVE_NETWORKING)
|
||
if (runloop_st->request_fast_savestate)
|
||
{
|
||
#ifdef HAVE_RUNAHEAD
|
||
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
||
settings_t
|
||
*settings = config_get_ptr();
|
||
result = (settings->bools.run_ahead_secondary_instance
|
||
&& runloop_st->runahead_secondary_core_available
|
||
&& secondary_core_ensure_exists(settings) ? RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_BINARY : RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE);
|
||
#else
|
||
result = RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE;
|
||
#endif
|
||
#endif
|
||
#ifdef HAVE_NETWORKING
|
||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||
result = RETRO_SAVESTATE_CONTEXT_ROLLBACK_NETPLAY;
|
||
#endif
|
||
}
|
||
#endif
|
||
if (data)
|
||
{
|
||
int* result_p = (int*)data;
|
||
*result_p = result;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_MIDI_INTERFACE:
|
||
{
|
||
struct retro_midi_interface *midi_interface =
|
||
(struct retro_midi_interface *)data;
|
||
|
||
if (midi_interface)
|
||
{
|
||
midi_interface->input_enabled = midi_driver_input_enabled;
|
||
midi_interface->output_enabled = midi_driver_output_enabled;
|
||
midi_interface->read = midi_driver_read;
|
||
midi_interface->write = midi_driver_write;
|
||
midi_interface->flush = midi_driver_flush;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_FASTFORWARDING:
|
||
*(bool *)data = runloop_st->fastmotion;
|
||
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();
|
||
struct retro_throttle_state *throttle_state =
|
||
(struct retro_throttle_state *)data;
|
||
audio_driver_state_t *audio_st =
|
||
audio_state_get_ptr();
|
||
|
||
bool menu_opened = false;
|
||
bool core_paused = runloop_st->paused;
|
||
bool no_audio = (audio_st->suspended || !audio_st->active);
|
||
float core_fps = (float)video_st->av_info.timing.fps;
|
||
|
||
#ifdef HAVE_REWIND
|
||
if (runloop_st->rewind_st.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()->alive;
|
||
if (menu_opened)
|
||
#ifdef HAVE_NETWORKING
|
||
core_paused = settings->bools.menu_pause_libretro &&
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
|
||
#else
|
||
core_paused = settings->bools.menu_pause_libretro;
|
||
#endif
|
||
|
||
#endif
|
||
|
||
if (core_paused)
|
||
{
|
||
throttle_state->mode = RETRO_THROTTLE_FRAME_STEPPING;
|
||
throttle_state->rate = 0.0f;
|
||
break; /* ignore vsync */
|
||
}
|
||
|
||
/* Base mode and rate. */
|
||
throttle_state->mode = RETRO_THROTTLE_NONE;
|
||
throttle_state->rate = core_fps;
|
||
|
||
if (runloop_st->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->slowmotion && !no_audio)
|
||
{
|
||
throttle_state->mode = RETRO_THROTTLE_SLOW_MOTION;
|
||
throttle_state->rate /= (settings->floats.slowmotion_ratio > 0.0f ?
|
||
settings->floats.slowmotion_ratio : 1.0f);
|
||
}
|
||
|
||
/* VSync overrides the mode if the rate is limited by the display. */
|
||
if (menu_opened || /* Menu currently always runs with vsync on. */
|
||
(settings->bools.video_vsync && !runloop_st->force_nonblock
|
||
&& !input_state_get_ptr()->nonblocking_flag))
|
||
{
|
||
float refresh_rate = video_driver_get_refresh_rate();
|
||
if (refresh_rate == 0.0f)
|
||
refresh_rate = settings->floats.video_refresh_rate;
|
||
if (refresh_rate < throttle_state->rate || !throttle_state->rate)
|
||
{
|
||
/* Keep the mode as fast forward even if vsync limits it. */
|
||
if (refresh_rate < core_fps)
|
||
throttle_state->mode = RETRO_THROTTLE_VSYNC;
|
||
throttle_state->rate = refresh_rate;
|
||
}
|
||
}
|
||
|
||
/* Special behavior while audio output is not available. */
|
||
if (no_audio && throttle_state->mode != RETRO_THROTTLE_FAST_FORWARD
|
||
&& throttle_state->mode != RETRO_THROTTLE_VSYNC)
|
||
{
|
||
/* Keep base if frame limiter matching the core is active. */
|
||
retro_time_t core_limit = (core_fps
|
||
? (retro_time_t)(1000000.0f / core_fps)
|
||
: (retro_time_t)0);
|
||
retro_time_t frame_limit = runloop_st->frame_limit_minimum_time;
|
||
if (abs((int)(core_limit - frame_limit)) > 10)
|
||
{
|
||
throttle_state->mode = RETRO_THROTTLE_UNBLOCKED;
|
||
throttle_state->rate = 0.0f;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS:
|
||
/* Just falldown, the function will return true */
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION:
|
||
RARCH_LOG("[Environ]: GET_CORE_OPTIONS_VERSION.\n");
|
||
/* Current API version is 2 */
|
||
*(unsigned *)data = 2;
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE:
|
||
{
|
||
/* Try to use the polled refresh rate first. */
|
||
float target_refresh_rate = video_driver_get_refresh_rate();
|
||
|
||
/* If the above function failed [possibly because it is not
|
||
* implemented], use the refresh rate set in the config instead. */
|
||
if (target_refresh_rate == 0.0f)
|
||
{
|
||
if (settings)
|
||
target_refresh_rate = settings->floats.video_refresh_rate;
|
||
}
|
||
|
||
*(float *)data = target_refresh_rate;
|
||
break;
|
||
}
|
||
|
||
case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS:
|
||
*(unsigned *)data = settings->uints.input_max_users;
|
||
break;
|
||
|
||
/* Private environment callbacks.
|
||
*
|
||
* Should all be properly addressed in version 2.
|
||
* */
|
||
|
||
case RETRO_ENVIRONMENT_POLL_TYPE_OVERRIDE:
|
||
{
|
||
const unsigned *poll_type_data = (const unsigned*)data;
|
||
|
||
if (poll_type_data)
|
||
runloop_st->core_poll_type_override = (enum poll_type_override_t)*poll_type_data;
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB:
|
||
*(retro_environment_t *)data = runloop_clear_all_thread_waits;
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_SAVE_STATE_IN_BACKGROUND:
|
||
{
|
||
bool state = *(const bool*)data;
|
||
RARCH_LOG("[Environ]: SET_SAVE_STATE_IN_BACKGROUND: %s.\n", state ? "yes" : "no");
|
||
|
||
set_save_state_in_background(state);
|
||
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE:
|
||
{
|
||
const struct retro_system_content_info_override *overrides =
|
||
(const struct retro_system_content_info_override *)data;
|
||
|
||
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE.\n");
|
||
|
||
/* Passing NULL always results in 'success' - this
|
||
* allows cores to test for frontend support of
|
||
* the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and
|
||
* RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks */
|
||
if (!overrides)
|
||
return true;
|
||
|
||
return content_file_override_set(overrides);
|
||
}
|
||
break;
|
||
|
||
case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT:
|
||
{
|
||
content_state_t *p_content =
|
||
content_state_get_ptr();
|
||
const struct retro_game_info_ext **game_info_ext =
|
||
(const struct retro_game_info_ext **)data;
|
||
|
||
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_GET_GAME_INFO_EXT.\n");
|
||
|
||
if (!game_info_ext)
|
||
return false;
|
||
|
||
if (p_content &&
|
||
p_content->content_list &&
|
||
p_content->content_list->game_info_ext)
|
||
*game_info_ext = p_content->content_list->game_info_ext;
|
||
else
|
||
{
|
||
RARCH_ERR("[Environ]: Failed to retrieve extended game info.\n");
|
||
*game_info_ext = NULL;
|
||
return false;
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
RARCH_LOG("[Environ]: UNSUPPORTED (#%u).\n", cmd);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool libretro_get_system_info(
|
||
const char *path,
|
||
struct retro_system_info *info,
|
||
bool *load_no_content)
|
||
{
|
||
struct retro_system_info dummy_info;
|
||
#ifdef HAVE_DYNAMIC
|
||
dylib_t lib;
|
||
#endif
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
if (string_ends_with_size(path,
|
||
"builtin", strlen(path), STRLEN_CONST("builtin")))
|
||
return false;
|
||
|
||
dummy_info.library_name = NULL;
|
||
dummy_info.library_version = NULL;
|
||
dummy_info.valid_extensions = NULL;
|
||
dummy_info.need_fullpath = false;
|
||
dummy_info.block_extract = false;
|
||
|
||
#ifdef HAVE_DYNAMIC
|
||
if (!(lib = libretro_get_system_info_lib(
|
||
path, &dummy_info, load_no_content)))
|
||
{
|
||
RARCH_ERR("%s: \"%s\"\n",
|
||
msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE),
|
||
path);
|
||
RARCH_ERR("Error(s): %s\n", dylib_error());
|
||
return false;
|
||
}
|
||
#else
|
||
if (load_no_content)
|
||
{
|
||
runloop_st->load_no_content_hook = load_no_content;
|
||
|
||
/* load_no_content gets set in this callback. */
|
||
retro_set_environment(runloop_environ_cb_get_system_info);
|
||
|
||
/* It's possible that we just set get_system_info callback
|
||
* to the currently running core.
|
||
*
|
||
* Make sure we reset it to the actual environment callback.
|
||
* Ignore any environment callbacks here in case we're running
|
||
* on the non-current core. */
|
||
runloop_st->ignore_environment_cb = true;
|
||
retro_set_environment(runloop_environment_cb);
|
||
runloop_st->ignore_environment_cb = false;
|
||
}
|
||
|
||
retro_get_system_info(&dummy_info);
|
||
#endif
|
||
|
||
memcpy(info, &dummy_info, sizeof(*info));
|
||
|
||
runloop_st->current_library_name[0] = '\0';
|
||
runloop_st->current_library_version[0] = '\0';
|
||
runloop_st->current_valid_extensions[0] = '\0';
|
||
|
||
if (!string_is_empty(dummy_info.library_name))
|
||
strlcpy(runloop_st->current_library_name,
|
||
dummy_info.library_name,
|
||
sizeof(runloop_st->current_library_name));
|
||
if (!string_is_empty(dummy_info.library_version))
|
||
strlcpy(runloop_st->current_library_version,
|
||
dummy_info.library_version,
|
||
sizeof(runloop_st->current_library_version));
|
||
|
||
if (dummy_info.valid_extensions)
|
||
strlcpy(runloop_st->current_valid_extensions,
|
||
dummy_info.valid_extensions,
|
||
sizeof(runloop_st->current_valid_extensions));
|
||
|
||
info->library_name = runloop_st->current_library_name;
|
||
info->library_version = runloop_st->current_library_version;
|
||
info->valid_extensions = runloop_st->current_valid_extensions;
|
||
|
||
#ifdef HAVE_DYNAMIC
|
||
dylib_close(lib);
|
||
#endif
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* load_symbols:
|
||
* @type : Type of core to be loaded.
|
||
* If CORE_TYPE_DUMMY, will
|
||
* load dummy symbols.
|
||
*
|
||
* Setup libretro callback symbols. Returns true on success,
|
||
* or false if symbols could not be loaded.
|
||
**/
|
||
static bool init_libretro_symbols_custom(
|
||
runloop_state_t *runloop_st,
|
||
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;
|
||
#endif
|
||
|
||
switch (type)
|
||
{
|
||
case CORE_TYPE_PLAIN:
|
||
{
|
||
#ifdef HAVE_DYNAMIC
|
||
#ifdef HAVE_RUNAHEAD
|
||
dylib_t *lib_handle_p = (dylib_t*)_lib_handle_p;
|
||
if (!lib_path || !lib_handle_p)
|
||
#endif
|
||
{
|
||
const char *path = path_get(RARCH_PATH_CORE);
|
||
|
||
if (string_is_empty(path))
|
||
{
|
||
RARCH_ERR("[Core]: Frontend is built for dynamic libretro cores, but "
|
||
"path is not set. Cannot continue.\n");
|
||
retroarch_fail(1, "init_libretro_symbols()");
|
||
}
|
||
|
||
RARCH_LOG("[Core]: Loading dynamic libretro core from: \"%s\"\n",
|
||
path);
|
||
|
||
if (!(runloop_st->lib_handle = load_dynamic_core(
|
||
path,
|
||
path_get_ptr(RARCH_PATH_CORE),
|
||
path_get_realsize(RARCH_PATH_CORE)
|
||
)))
|
||
{
|
||
RARCH_ERR("%s: \"%s\"\nError(s): %s\n",
|
||
msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE),
|
||
path, dylib_error());
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE),
|
||
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 */
|
||
retro_assert(lib_path != NULL && lib_handle_p != NULL);
|
||
lib_handle_local = dylib_load(lib_path);
|
||
|
||
if (!lib_handle_local)
|
||
return false;
|
||
*lib_handle_p = lib_handle_local;
|
||
}
|
||
#endif
|
||
#endif
|
||
|
||
CORE_SYMBOLS(SYMBOL);
|
||
}
|
||
break;
|
||
case CORE_TYPE_DUMMY:
|
||
CORE_SYMBOLS(SYMBOL_DUMMY);
|
||
break;
|
||
case CORE_TYPE_FFMPEG:
|
||
#ifdef HAVE_FFMPEG
|
||
CORE_SYMBOLS(SYMBOL_FFMPEG);
|
||
#endif
|
||
break;
|
||
case CORE_TYPE_MPV:
|
||
#ifdef HAVE_MPV
|
||
CORE_SYMBOLS(SYMBOL_MPV);
|
||
#endif
|
||
break;
|
||
case CORE_TYPE_IMAGEVIEWER:
|
||
#ifdef HAVE_IMAGEVIEWER
|
||
CORE_SYMBOLS(SYMBOL_IMAGEVIEWER);
|
||
#endif
|
||
break;
|
||
case CORE_TYPE_NETRETROPAD:
|
||
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
|
||
CORE_SYMBOLS(SYMBOL_NETRETROPAD);
|
||
#endif
|
||
break;
|
||
case CORE_TYPE_VIDEO_PROCESSOR:
|
||
#if defined(HAVE_VIDEOPROCESSOR)
|
||
CORE_SYMBOLS(SYMBOL_VIDEOPROCESSOR);
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* init_libretro_symbols:
|
||
* @type : Type of core to be loaded.
|
||
* If CORE_TYPE_DUMMY, will
|
||
* load dummy symbols.
|
||
*
|
||
* Initializes libretro symbols and
|
||
* setups environment callback functions. Returns true on success,
|
||
* or false if symbols could not be loaded.
|
||
**/
|
||
static bool init_libretro_symbols(
|
||
runloop_state_t *runloop_st,
|
||
enum rarch_core_type type,
|
||
struct retro_core_t *current_core)
|
||
{
|
||
/* Load symbols */
|
||
if (!init_libretro_symbols_custom(runloop_st,
|
||
type, 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
|
||
return true;
|
||
}
|
||
|
||
bool libretro_get_shared_context(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
return runloop_st->core_set_shared_context;
|
||
}
|
||
|
||
void runloop_system_info_free(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
rarch_system_info_t *sys_info = &runloop_st->system;
|
||
|
||
if (sys_info->subsystem.data)
|
||
free(sys_info->subsystem.data);
|
||
if (sys_info->ports.data)
|
||
free(sys_info->ports.data);
|
||
if (sys_info->mmaps.descriptors)
|
||
free((void *)sys_info->mmaps.descriptors);
|
||
|
||
sys_info->subsystem.data = NULL;
|
||
sys_info->subsystem.size = 0;
|
||
|
||
sys_info->ports.data = NULL;
|
||
sys_info->ports.size = 0;
|
||
|
||
sys_info->mmaps.descriptors = NULL;
|
||
sys_info->mmaps.num_descriptors = 0;
|
||
|
||
sys_info->info.library_name = NULL;
|
||
sys_info->info.library_version = NULL;
|
||
sys_info->info.valid_extensions = NULL;
|
||
sys_info->info.need_fullpath = false;
|
||
sys_info->info.block_extract = false;
|
||
|
||
runloop_st->key_event = NULL;
|
||
runloop_st->frontend_key_event = NULL;
|
||
|
||
memset(&runloop_st->system, 0, sizeof(rarch_system_info_t));
|
||
}
|
||
|
||
/**
|
||
* uninit_libretro_sym:
|
||
*
|
||
* Frees libretro core.
|
||
*
|
||
* Frees all core options,
|
||
* associated state, and
|
||
* unbind all libretro callback symbols.
|
||
**/
|
||
static void uninit_libretro_symbols(
|
||
struct retro_core_t *current_core)
|
||
{
|
||
runloop_state_t
|
||
*runloop_st = &runloop_state;
|
||
input_driver_state_t
|
||
*input_st = input_state_get_ptr();
|
||
audio_driver_state_t
|
||
*audio_st = audio_state_get_ptr();
|
||
camera_driver_state_t
|
||
*camera_st = camera_state_get_ptr();
|
||
location_driver_state_t
|
||
*location_st = location_state_get_ptr();
|
||
#ifdef HAVE_DYNAMIC
|
||
if (runloop_st->lib_handle)
|
||
dylib_close(runloop_st->lib_handle);
|
||
runloop_st->lib_handle = NULL;
|
||
#endif
|
||
|
||
memset(current_core, 0, sizeof(struct retro_core_t));
|
||
|
||
runloop_st->core_set_shared_context = false;
|
||
|
||
if (runloop_st->core_options)
|
||
{
|
||
runloop_deinit_core_options(
|
||
runloop_st->game_options_active,
|
||
path_get(RARCH_PATH_CORE_OPTIONS),
|
||
runloop_st->core_options);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
runloop_st->core_options = NULL;
|
||
}
|
||
runloop_system_info_free();
|
||
audio_st->callback.callback = NULL;
|
||
audio_st->callback.set_state = NULL;
|
||
runloop_frame_time_free();
|
||
runloop_audio_buffer_status_free();
|
||
input_game_focus_free();
|
||
runloop_fastmotion_override_free();
|
||
runloop_core_options_cb_free();
|
||
runloop_st->video_swap_interval_auto = 1;
|
||
camera_st->active = false;
|
||
location_st->active = false;
|
||
|
||
/* Core has finished utilising the input driver;
|
||
* reset 'analog input requested' flags */
|
||
memset(&input_st->analog_requested, 0,
|
||
sizeof(input_st->analog_requested));
|
||
|
||
/* Performance counters no longer valid. */
|
||
runloop_st->perf_ptr_libretro = 0;
|
||
memset(runloop_st->perf_counters_libretro, 0,
|
||
sizeof(runloop_st->perf_counters_libretro));
|
||
}
|
||
|
||
#if defined(HAVE_RUNAHEAD)
|
||
static int16_t input_state_get_last(unsigned port,
|
||
unsigned device, unsigned index, unsigned id)
|
||
{
|
||
int i;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
if (!runloop_st->input_state_list)
|
||
return 0;
|
||
|
||
/* find list item */
|
||
for (i = 0; i < runloop_st->input_state_list->size; i++)
|
||
{
|
||
input_list_element *element =
|
||
(input_list_element*)runloop_st->input_state_list->data[i];
|
||
|
||
if ( (element->port == port) &&
|
||
(element->device == device) &&
|
||
(element->index == index))
|
||
{
|
||
if (id < element->state_size)
|
||
return element->state[id];
|
||
return 0;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void free_retro_ctx_load_content_info(struct
|
||
retro_ctx_load_content_info *dest)
|
||
{
|
||
if (!dest)
|
||
return;
|
||
|
||
string_list_free((struct string_list*)dest->content);
|
||
if (dest->info)
|
||
free(dest->info);
|
||
|
||
dest->info = NULL;
|
||
dest->content = NULL;
|
||
}
|
||
|
||
static struct retro_game_info* clone_retro_game_info(const
|
||
struct retro_game_info *src)
|
||
{
|
||
struct retro_game_info *dest = (struct retro_game_info*)malloc(
|
||
sizeof(struct retro_game_info));
|
||
|
||
if (!dest)
|
||
return NULL;
|
||
|
||
/* content_file_init() guarantees that all
|
||
* elements of the source retro_game_info
|
||
* struct will persist for the lifetime of
|
||
* the core. This means we do not have to
|
||
* copy any data; pointer assignment is
|
||
* sufficient */
|
||
dest->path = src->path;
|
||
dest->data = src->data;
|
||
dest->size = src->size;
|
||
dest->meta = src->meta;
|
||
|
||
return dest;
|
||
}
|
||
|
||
static struct retro_ctx_load_content_info
|
||
*clone_retro_ctx_load_content_info(
|
||
const struct retro_ctx_load_content_info *src)
|
||
{
|
||
struct retro_ctx_load_content_info *dest = NULL;
|
||
if (!src || src->special)
|
||
return NULL; /* refuse to deal with the Special field */
|
||
|
||
dest = (struct retro_ctx_load_content_info*)
|
||
malloc(sizeof(*dest));
|
||
|
||
if (!dest)
|
||
return NULL;
|
||
|
||
dest->info = NULL;
|
||
dest->content = NULL;
|
||
dest->special = NULL;
|
||
|
||
if (src->info)
|
||
dest->info = clone_retro_game_info(src->info);
|
||
if (src->content)
|
||
dest->content = string_list_clone(src->content);
|
||
|
||
return dest;
|
||
}
|
||
|
||
static void set_load_content_info(
|
||
runloop_state_t *runloop_st,
|
||
const retro_ctx_load_content_info_t *ctx)
|
||
{
|
||
free_retro_ctx_load_content_info(runloop_st->load_content_info);
|
||
free(runloop_st->load_content_info);
|
||
runloop_st->load_content_info = clone_retro_ctx_load_content_info(ctx);
|
||
}
|
||
|
||
/* RUNAHEAD - SECONDARY CORE */
|
||
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
||
static void strcat_alloc(char **dst, const char *s)
|
||
{
|
||
size_t len1;
|
||
char *src = *dst;
|
||
|
||
if (!src)
|
||
{
|
||
if (s)
|
||
{
|
||
size_t len = strlen(s);
|
||
if (len != 0)
|
||
{
|
||
char *_dst= (char*)malloc(len + 1);
|
||
strcpy_literal(_dst, s);
|
||
src = _dst;
|
||
}
|
||
else
|
||
src = NULL;
|
||
}
|
||
else
|
||
src = (char*)calloc(1,1);
|
||
|
||
*dst = src;
|
||
return;
|
||
}
|
||
|
||
if (!s)
|
||
return;
|
||
|
||
len1 = strlen(src);
|
||
|
||
if (!(src = (char*)realloc(src, len1 + strlen(s) + 1)))
|
||
return;
|
||
|
||
*dst = src;
|
||
strcpy_literal(src + len1, s);
|
||
}
|
||
|
||
void runloop_secondary_core_destroy(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!runloop_st->secondary_lib_handle)
|
||
return;
|
||
|
||
/* unload game from core */
|
||
if (runloop_st->secondary_core.retro_unload_game)
|
||
runloop_st->secondary_core.retro_unload_game();
|
||
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
|
||
|
||
/* deinit */
|
||
if (runloop_st->secondary_core.retro_deinit)
|
||
runloop_st->secondary_core.retro_deinit();
|
||
memset(&runloop_st->secondary_core, 0, sizeof(struct retro_core_t));
|
||
|
||
dylib_close(runloop_st->secondary_lib_handle);
|
||
runloop_st->secondary_lib_handle = NULL;
|
||
filestream_delete(runloop_st->secondary_library_path);
|
||
if (runloop_st->secondary_library_path)
|
||
free(runloop_st->secondary_library_path);
|
||
runloop_st->secondary_library_path = NULL;
|
||
}
|
||
|
||
static char *get_tmpdir_alloc(const char *override_dir)
|
||
{
|
||
const char *src = NULL;
|
||
char *path = NULL;
|
||
#ifdef _WIN32
|
||
#ifdef LEGACY_WIN32
|
||
DWORD plen = GetTempPath(0, NULL) + 1;
|
||
|
||
if (!(path = (char*)malloc(plen * sizeof(char))))
|
||
return NULL;
|
||
|
||
path[plen - 1] = 0;
|
||
GetTempPath(plen, path);
|
||
#else
|
||
DWORD plen = GetTempPathW(0, NULL) + 1;
|
||
wchar_t *wide_str = (wchar_t*)malloc(plen * sizeof(wchar_t));
|
||
|
||
if (!wide_str)
|
||
return NULL;
|
||
|
||
wide_str[plen - 1] = 0;
|
||
GetTempPathW(plen, wide_str);
|
||
|
||
path = utf16_to_utf8_string_alloc(wide_str);
|
||
free(wide_str);
|
||
#endif
|
||
#else
|
||
#if defined ANDROID
|
||
src = override_dir;
|
||
#else
|
||
{
|
||
char *tmpdir = getenv("TMPDIR");
|
||
if (tmpdir)
|
||
src = tmpdir;
|
||
else
|
||
src = "/tmp";
|
||
}
|
||
#endif
|
||
if (src)
|
||
{
|
||
size_t len = strlen(src);
|
||
if (len != 0)
|
||
{
|
||
char *dst = (char*)malloc(len + 1);
|
||
strcpy_literal(dst, src);
|
||
path = dst;
|
||
}
|
||
}
|
||
else
|
||
path = (char*)calloc(1,1);
|
||
#endif
|
||
return path;
|
||
}
|
||
|
||
static bool write_file_with_random_name(char **temp_dll_path,
|
||
const char *tmp_path, const void* data, ssize_t dataSize)
|
||
{
|
||
int i, ext_len;
|
||
char number_buf[32];
|
||
bool okay = false;
|
||
const char *prefix = "tmp";
|
||
char *ext = NULL;
|
||
time_t time_value = time(NULL);
|
||
unsigned _number_value = (unsigned)time_value;
|
||
const char *src = path_get_extension(*temp_dll_path);
|
||
|
||
if (src)
|
||
{
|
||
size_t len = strlen(src);
|
||
if (len != 0)
|
||
{
|
||
char *dst = (char*)malloc(len + 1);
|
||
strcpy_literal(dst, src);
|
||
ext = dst;
|
||
}
|
||
}
|
||
else
|
||
ext = (char*)calloc(1,1);
|
||
|
||
ext_len = (int)strlen(ext);
|
||
|
||
if (ext_len > 0)
|
||
{
|
||
strcat_alloc(&ext, ".");
|
||
memmove(ext + 1, ext, ext_len);
|
||
ext[0] = '.';
|
||
ext_len++;
|
||
}
|
||
|
||
/* Try up to 30 'random' filenames before giving up */
|
||
for (i = 0; i < 30; i++)
|
||
{
|
||
int number_value = _number_value * 214013 + 2531011;
|
||
int number = (number_value >> 14) % 100000;
|
||
|
||
snprintf(number_buf, sizeof(number_buf), "%05d", number);
|
||
|
||
if (*temp_dll_path)
|
||
free(*temp_dll_path);
|
||
*temp_dll_path = NULL;
|
||
|
||
strcat_alloc(temp_dll_path, tmp_path);
|
||
strcat_alloc(temp_dll_path, PATH_DEFAULT_SLASH());
|
||
strcat_alloc(temp_dll_path, prefix);
|
||
strcat_alloc(temp_dll_path, number_buf);
|
||
strcat_alloc(temp_dll_path, ext);
|
||
|
||
if (filestream_write_file(*temp_dll_path, data, dataSize))
|
||
{
|
||
okay = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (ext)
|
||
free(ext);
|
||
ext = NULL;
|
||
return okay;
|
||
}
|
||
|
||
|
||
static char *copy_core_to_temp_file(
|
||
const char *core_path,
|
||
const char *dir_libretro)
|
||
{
|
||
char tmp_path[PATH_MAX_LENGTH];
|
||
bool failed = false;
|
||
char *tmpdir = NULL;
|
||
char *tmp_dll_path = NULL;
|
||
void *dll_file_data = NULL;
|
||
int64_t dll_file_size = 0;
|
||
const char *core_base_name = path_basename_nocompression(core_path);
|
||
|
||
if (strlen(core_base_name) == 0)
|
||
return NULL;
|
||
|
||
if (!(tmpdir = get_tmpdir_alloc(dir_libretro)))
|
||
return NULL;
|
||
|
||
fill_pathname_join(tmp_path,
|
||
tmpdir, "retroarch_temp",
|
||
sizeof(tmp_path));
|
||
|
||
if (!path_mkdir(tmp_path))
|
||
{
|
||
failed = true;
|
||
goto end;
|
||
}
|
||
|
||
if (!filestream_read_file(core_path, &dll_file_data, &dll_file_size))
|
||
{
|
||
failed = true;
|
||
goto end;
|
||
}
|
||
|
||
strcat_alloc(&tmp_dll_path, tmp_path);
|
||
strcat_alloc(&tmp_dll_path, PATH_DEFAULT_SLASH());
|
||
strcat_alloc(&tmp_dll_path, core_base_name);
|
||
|
||
if (!filestream_write_file(tmp_dll_path, dll_file_data, dll_file_size))
|
||
{
|
||
/* try other file names */
|
||
if (!write_file_with_random_name(&tmp_dll_path,
|
||
tmp_path, dll_file_data, dll_file_size))
|
||
failed = true;
|
||
}
|
||
|
||
end:
|
||
if (tmpdir)
|
||
free(tmpdir);
|
||
if (dll_file_data)
|
||
free(dll_file_data);
|
||
|
||
tmpdir = NULL;
|
||
dll_file_data = NULL;
|
||
|
||
if (!failed)
|
||
return tmp_dll_path;
|
||
|
||
if (tmp_dll_path)
|
||
free(tmp_dll_path);
|
||
|
||
tmp_dll_path = NULL;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static bool runloop_environment_secondary_core_hook(
|
||
unsigned cmd, void *data)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool result = runloop_environment_cb(cmd, data);
|
||
|
||
if (runloop_st->has_variable_update)
|
||
{
|
||
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
|
||
{
|
||
bool *bool_p = (bool*)data;
|
||
*bool_p = true;
|
||
runloop_st->has_variable_update = false;
|
||
return true;
|
||
}
|
||
else if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE)
|
||
runloop_st->has_variable_update = false;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static void runloop_clear_controller_port_map(void)
|
||
{
|
||
int port;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
for (port = 0; port < MAX_USERS; port++)
|
||
runloop_st->port_map[port] = -1;
|
||
}
|
||
|
||
static bool secondary_core_create(runloop_state_t *runloop_st,
|
||
settings_t *settings)
|
||
{
|
||
bool contentless = false;
|
||
bool is_inited = false;
|
||
const enum rarch_core_type
|
||
last_core_type = runloop_st->last_core_type;
|
||
rarch_system_info_t *info = &runloop_st->system;
|
||
unsigned num_active_users = settings->uints.input_max_users;
|
||
|
||
if ( last_core_type != CORE_TYPE_PLAIN ||
|
||
!runloop_st->load_content_info ||
|
||
runloop_st->load_content_info->special)
|
||
return false;
|
||
|
||
if (runloop_st->secondary_library_path)
|
||
free(runloop_st->secondary_library_path);
|
||
runloop_st->secondary_library_path = NULL;
|
||
runloop_st->secondary_library_path = copy_core_to_temp_file(
|
||
path_get(RARCH_PATH_CORE),
|
||
settings->paths.directory_libretro);
|
||
|
||
if (!runloop_st->secondary_library_path)
|
||
return false;
|
||
|
||
/* Load Core */
|
||
if (!init_libretro_symbols_custom(runloop_st,
|
||
CORE_TYPE_PLAIN, &runloop_st->secondary_core,
|
||
runloop_st->secondary_library_path,
|
||
&runloop_st->secondary_lib_handle))
|
||
return false;
|
||
|
||
runloop_st->secondary_core.symbols_inited = true;
|
||
runloop_st->secondary_core.retro_set_environment(
|
||
runloop_environment_secondary_core_hook);
|
||
#ifdef HAVE_RUNAHEAD
|
||
runloop_st->has_variable_update = true;
|
||
#endif
|
||
|
||
runloop_st->secondary_core.retro_init();
|
||
|
||
content_get_status(&contentless, &is_inited);
|
||
runloop_st->secondary_core.inited = is_inited;
|
||
|
||
/* Load Content */
|
||
/* disabled due to crashes */
|
||
if ( !runloop_st->load_content_info ||
|
||
runloop_st->load_content_info->special)
|
||
return false;
|
||
|
||
if ( (runloop_st->load_content_info->content->size > 0) &&
|
||
runloop_st->load_content_info->content->elems[0].data)
|
||
{
|
||
runloop_st->secondary_core.game_loaded =
|
||
runloop_st->secondary_core.retro_load_game(
|
||
runloop_st->load_content_info->info);
|
||
if (!runloop_st->secondary_core.game_loaded)
|
||
goto error;
|
||
}
|
||
else if (contentless)
|
||
{
|
||
runloop_st->secondary_core.game_loaded =
|
||
runloop_st->secondary_core.retro_load_game(NULL);
|
||
if (!runloop_st->secondary_core.game_loaded)
|
||
goto error;
|
||
}
|
||
else
|
||
runloop_st->secondary_core.game_loaded = false;
|
||
|
||
if (!runloop_st->secondary_core.inited)
|
||
goto error;
|
||
|
||
core_set_default_callbacks(&runloop_st->secondary_callbacks);
|
||
runloop_st->secondary_core.retro_set_video_refresh(
|
||
runloop_st->secondary_callbacks.frame_cb);
|
||
runloop_st->secondary_core.retro_set_audio_sample(
|
||
runloop_st->secondary_callbacks.sample_cb);
|
||
runloop_st->secondary_core.retro_set_audio_sample_batch(
|
||
runloop_st->secondary_callbacks.sample_batch_cb);
|
||
runloop_st->secondary_core.retro_set_input_state(
|
||
runloop_st->secondary_callbacks.state_cb);
|
||
runloop_st->secondary_core.retro_set_input_poll(
|
||
runloop_st->secondary_callbacks.poll_cb);
|
||
|
||
if (info)
|
||
{
|
||
int port;
|
||
for (port = 0; port < MAX_USERS; port++)
|
||
{
|
||
if (port < info->ports.size)
|
||
{
|
||
unsigned device = (port < num_active_users) ?
|
||
runloop_st->port_map[port] : RETRO_DEVICE_NONE;
|
||
|
||
runloop_st->secondary_core.retro_set_controller_port_device(
|
||
port, device);
|
||
}
|
||
}
|
||
}
|
||
|
||
runloop_clear_controller_port_map();
|
||
|
||
return true;
|
||
|
||
error:
|
||
runloop_secondary_core_destroy();
|
||
return false;
|
||
}
|
||
|
||
bool secondary_core_ensure_exists(settings_t *settings)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!runloop_st->secondary_lib_handle)
|
||
if (!secondary_core_create(runloop_st, settings))
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
#if defined(HAVE_RUNAHEAD) && defined(HAVE_DYNAMIC)
|
||
static bool secondary_core_deserialize(settings_t *settings,
|
||
const void *buffer, int size)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (secondary_core_ensure_exists(settings))
|
||
return runloop_st->secondary_core.retro_unserialize(buffer, size);
|
||
runloop_secondary_core_destroy();
|
||
return false;
|
||
}
|
||
#endif
|
||
|
||
static void remember_controller_port_device(long port, long device)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (port >= 0 && port < MAX_USERS)
|
||
runloop_st->port_map[port] = (int)device;
|
||
if ( runloop_st->secondary_lib_handle
|
||
&& runloop_st->secondary_core.retro_set_controller_port_device)
|
||
runloop_st->secondary_core.retro_set_controller_port_device((unsigned)port, (unsigned)device);
|
||
}
|
||
|
||
static void secondary_core_input_poll_null(void) { }
|
||
|
||
static bool secondary_core_run_use_last_input(void)
|
||
{
|
||
retro_input_poll_t old_poll_function;
|
||
retro_input_state_t old_input_function;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
if (!secondary_core_ensure_exists(config_get_ptr()))
|
||
{
|
||
runloop_secondary_core_destroy();
|
||
return false;
|
||
}
|
||
|
||
old_poll_function = runloop_st->secondary_callbacks.poll_cb;
|
||
old_input_function = runloop_st->secondary_callbacks.state_cb;
|
||
|
||
runloop_st->secondary_callbacks.poll_cb = secondary_core_input_poll_null;
|
||
runloop_st->secondary_callbacks.state_cb = input_state_get_last;
|
||
|
||
runloop_st->secondary_core.retro_set_input_poll(
|
||
runloop_st->secondary_callbacks.poll_cb);
|
||
runloop_st->secondary_core.retro_set_input_state(
|
||
runloop_st->secondary_callbacks.state_cb);
|
||
|
||
runloop_st->secondary_core.retro_run();
|
||
runloop_st->secondary_callbacks.poll_cb = old_poll_function;
|
||
runloop_st->secondary_callbacks.state_cb = old_input_function;
|
||
|
||
runloop_st->secondary_core.retro_set_input_poll(
|
||
runloop_st->secondary_callbacks.poll_cb);
|
||
runloop_st->secondary_core.retro_set_input_state(
|
||
runloop_st->secondary_callbacks.state_cb);
|
||
|
||
return true;
|
||
}
|
||
#else
|
||
void runloop_secondary_core_destroy(void) { }
|
||
static void remember_controller_port_device(long port, long device) { }
|
||
static void runloop_clear_controller_port_map(void) { }
|
||
#endif
|
||
|
||
static void mylist_resize(my_list *list,
|
||
int new_size, bool run_constructor)
|
||
{
|
||
int i;
|
||
int new_capacity;
|
||
int old_size;
|
||
void *element = NULL;
|
||
if (new_size < 0)
|
||
new_size = 0;
|
||
new_capacity = new_size;
|
||
old_size = list->size;
|
||
|
||
if (new_size == old_size)
|
||
return;
|
||
|
||
if (new_size > list->capacity)
|
||
{
|
||
if (new_capacity < list->capacity * 2)
|
||
new_capacity = list->capacity * 2;
|
||
|
||
/* try to realloc */
|
||
list->data = (void**)realloc(
|
||
(void*)list->data, new_capacity * sizeof(void*));
|
||
|
||
for (i = list->capacity; i < new_capacity; i++)
|
||
list->data[i] = NULL;
|
||
|
||
list->capacity = new_capacity;
|
||
}
|
||
|
||
if (new_size <= list->size)
|
||
{
|
||
for (i = new_size; i < list->size; i++)
|
||
{
|
||
element = list->data[i];
|
||
|
||
if (element)
|
||
{
|
||
list->destructor(element);
|
||
list->data[i] = NULL;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (i = list->size; i < new_size; i++)
|
||
{
|
||
list->data[i] = NULL;
|
||
if (run_constructor)
|
||
list->data[i] = list->constructor();
|
||
}
|
||
}
|
||
|
||
list->size = new_size;
|
||
}
|
||
|
||
static void *mylist_add_element(my_list *list)
|
||
{
|
||
int old_size = list->size;
|
||
if (list)
|
||
mylist_resize(list, old_size + 1, true);
|
||
return list->data[old_size];
|
||
}
|
||
|
||
static void mylist_destroy(my_list **list_p)
|
||
{
|
||
my_list *list = NULL;
|
||
if (!list_p)
|
||
return;
|
||
|
||
list = *list_p;
|
||
|
||
if (list)
|
||
{
|
||
mylist_resize(list, 0, false);
|
||
free(list->data);
|
||
free(list);
|
||
*list_p = NULL;
|
||
}
|
||
}
|
||
|
||
static void mylist_create(my_list **list_p, int initial_capacity,
|
||
constructor_t constructor, destructor_t destructor)
|
||
{
|
||
my_list *list = NULL;
|
||
|
||
if (!list_p)
|
||
return;
|
||
|
||
list = *list_p;
|
||
if (list)
|
||
mylist_destroy(list_p);
|
||
|
||
list = (my_list*)malloc(sizeof(my_list));
|
||
*list_p = list;
|
||
list->size = 0;
|
||
list->constructor = constructor;
|
||
list->destructor = destructor;
|
||
list->data = (void**)calloc(initial_capacity, sizeof(void*));
|
||
list->capacity = initial_capacity;
|
||
}
|
||
|
||
static void *input_list_element_constructor(void)
|
||
{
|
||
void *ptr = malloc(sizeof(input_list_element));
|
||
input_list_element *element = (input_list_element*)ptr;
|
||
|
||
element->port = 0;
|
||
element->device = 0;
|
||
element->index = 0;
|
||
element->state = (int16_t*)calloc(NAME_MAX_LENGTH,
|
||
sizeof(int16_t));
|
||
element->state_size = NAME_MAX_LENGTH;
|
||
|
||
return ptr;
|
||
}
|
||
|
||
static void input_list_element_realloc(
|
||
input_list_element *element,
|
||
unsigned int new_size)
|
||
{
|
||
if (new_size > element->state_size)
|
||
{
|
||
element->state = (int16_t*)realloc(element->state,
|
||
new_size * sizeof(int16_t));
|
||
memset(&element->state[element->state_size], 0,
|
||
(new_size - element->state_size) * sizeof(int16_t));
|
||
element->state_size = new_size;
|
||
}
|
||
}
|
||
|
||
static void input_list_element_expand(
|
||
input_list_element *element, unsigned int new_index)
|
||
{
|
||
unsigned int new_size = element->state_size;
|
||
if (new_size == 0)
|
||
new_size = 32;
|
||
while (new_index >= new_size)
|
||
new_size *= 2;
|
||
input_list_element_realloc(element, new_size);
|
||
}
|
||
|
||
static void input_list_element_destructor(void* element_ptr)
|
||
{
|
||
input_list_element *element = (input_list_element*)element_ptr;
|
||
if (!element)
|
||
return;
|
||
|
||
free(element->state);
|
||
free(element_ptr);
|
||
}
|
||
|
||
static void input_state_set_last(
|
||
runloop_state_t *runloop_st,
|
||
unsigned port, unsigned device,
|
||
unsigned index, unsigned id, int16_t value)
|
||
{
|
||
unsigned i;
|
||
input_list_element *element = NULL;
|
||
|
||
if (!runloop_st->input_state_list)
|
||
mylist_create(&runloop_st->input_state_list, 16,
|
||
input_list_element_constructor,
|
||
input_list_element_destructor);
|
||
|
||
/* Find list item */
|
||
for (i = 0; i < (unsigned)runloop_st->input_state_list->size; i++)
|
||
{
|
||
element = (input_list_element*)runloop_st->input_state_list->data[i];
|
||
if ( (element->port == port) &&
|
||
(element->device == device) &&
|
||
(element->index == index)
|
||
)
|
||
{
|
||
if (id >= element->state_size)
|
||
input_list_element_expand(element, id);
|
||
element->state[id] = value;
|
||
return;
|
||
}
|
||
}
|
||
|
||
element = NULL;
|
||
if (runloop_st->input_state_list)
|
||
element = (input_list_element*)
|
||
mylist_add_element(runloop_st->input_state_list);
|
||
if (element)
|
||
{
|
||
element->port = port;
|
||
element->device = device;
|
||
element->index = index;
|
||
if (id >= element->state_size)
|
||
input_list_element_expand(element, id);
|
||
element->state[id] = value;
|
||
}
|
||
}
|
||
|
||
static int16_t input_state_with_logging(unsigned port,
|
||
unsigned device, unsigned index, unsigned id)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
if (runloop_st->input_state_callback_original)
|
||
{
|
||
int16_t result =
|
||
runloop_st->input_state_callback_original(
|
||
port, device, index, id);
|
||
int16_t last_input =
|
||
input_state_get_last(port, device, index, id);
|
||
if (result != last_input)
|
||
runloop_st->input_is_dirty = true;
|
||
/*arbitrary limit of up to 65536 elements in state array*/
|
||
if (id < 65536)
|
||
input_state_set_last(runloop_st, port, device, index, id, result);
|
||
|
||
return result;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void reset_hook(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
runloop_st->input_is_dirty = true;
|
||
|
||
if (runloop_st->retro_reset_callback_original)
|
||
runloop_st->retro_reset_callback_original();
|
||
}
|
||
|
||
static bool unserialize_hook(const void *buf, size_t size)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
runloop_st->input_is_dirty = true;
|
||
|
||
if (runloop_st->retro_unserialize_callback_original)
|
||
return runloop_st->retro_unserialize_callback_original(buf, size);
|
||
return false;
|
||
}
|
||
|
||
static void add_input_state_hook(runloop_state_t *runloop_st)
|
||
{
|
||
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
|
||
|
||
if (!runloop_st->input_state_callback_original)
|
||
{
|
||
runloop_st->input_state_callback_original = cbs->state_cb;
|
||
cbs->state_cb = input_state_with_logging;
|
||
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
|
||
}
|
||
|
||
if (!runloop_st->retro_reset_callback_original)
|
||
{
|
||
runloop_st->retro_reset_callback_original
|
||
= runloop_st->current_core.retro_reset;
|
||
runloop_st->current_core.retro_reset = reset_hook;
|
||
}
|
||
|
||
if (!runloop_st->retro_unserialize_callback_original)
|
||
{
|
||
runloop_st->retro_unserialize_callback_original = runloop_st->current_core.retro_unserialize;
|
||
runloop_st->current_core.retro_unserialize = unserialize_hook;
|
||
}
|
||
}
|
||
|
||
static void remove_input_state_hook(runloop_state_t *runloop_st)
|
||
{
|
||
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
|
||
|
||
if (runloop_st->input_state_callback_original)
|
||
{
|
||
cbs->state_cb =
|
||
runloop_st->input_state_callback_original;
|
||
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
|
||
runloop_st->input_state_callback_original = NULL;
|
||
mylist_destroy(&runloop_st->input_state_list);
|
||
}
|
||
|
||
if (runloop_st->retro_reset_callback_original)
|
||
{
|
||
runloop_st->current_core.retro_reset =
|
||
runloop_st->retro_reset_callback_original;
|
||
runloop_st->retro_reset_callback_original = NULL;
|
||
}
|
||
|
||
if (runloop_st->retro_unserialize_callback_original)
|
||
{
|
||
runloop_st->current_core.retro_unserialize =
|
||
runloop_st->retro_unserialize_callback_original;
|
||
runloop_st->retro_unserialize_callback_original = NULL;
|
||
}
|
||
}
|
||
|
||
static void *runahead_save_state_alloc(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
retro_ctx_serialize_info_t *savestate = (retro_ctx_serialize_info_t*)
|
||
malloc(sizeof(retro_ctx_serialize_info_t));
|
||
|
||
if (!savestate)
|
||
return NULL;
|
||
|
||
savestate->data = NULL;
|
||
savestate->data_const = NULL;
|
||
savestate->size = 0;
|
||
|
||
if ( (runloop_st->runahead_save_state_size > 0)
|
||
&& runloop_st->runahead_save_state_size_known)
|
||
{
|
||
savestate->data = malloc(runloop_st->runahead_save_state_size);
|
||
savestate->data_const = savestate->data;
|
||
savestate->size = runloop_st->runahead_save_state_size;
|
||
}
|
||
|
||
return savestate;
|
||
}
|
||
|
||
static void runahead_save_state_free(void *data)
|
||
{
|
||
retro_ctx_serialize_info_t *savestate = (retro_ctx_serialize_info_t*)data;
|
||
if (!savestate)
|
||
return;
|
||
free(savestate->data);
|
||
free(savestate);
|
||
}
|
||
|
||
static void runahead_save_state_list_init(
|
||
runloop_state_t *runloop_st,
|
||
size_t save_state_size)
|
||
{
|
||
runloop_st->runahead_save_state_size = save_state_size;
|
||
runloop_st->runahead_save_state_size_known = true;
|
||
|
||
mylist_create(&runloop_st->runahead_save_state_list, 16,
|
||
runahead_save_state_alloc, runahead_save_state_free);
|
||
}
|
||
|
||
/* Hooks - Hooks to cleanup, and add dirty input hooks */
|
||
static void runahead_remove_hooks(runloop_state_t *runloop_st)
|
||
{
|
||
if (runloop_st->original_retro_deinit)
|
||
{
|
||
runloop_st->current_core.retro_deinit =
|
||
runloop_st->original_retro_deinit;
|
||
runloop_st->original_retro_deinit = NULL;
|
||
}
|
||
|
||
if (runloop_st->original_retro_unload)
|
||
{
|
||
runloop_st->current_core.retro_unload_game =
|
||
runloop_st->original_retro_unload;
|
||
runloop_st->original_retro_unload = NULL;
|
||
}
|
||
remove_input_state_hook(runloop_st);
|
||
}
|
||
|
||
static void runahead_destroy(runloop_state_t *runloop_st)
|
||
{
|
||
mylist_destroy(&runloop_st->runahead_save_state_list);
|
||
runahead_remove_hooks(runloop_st);
|
||
runloop_runahead_clear_variables(runloop_st);
|
||
}
|
||
|
||
static void unload_hook(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
runahead_remove_hooks(runloop_st);
|
||
runahead_destroy(runloop_st);
|
||
runloop_secondary_core_destroy();
|
||
if (runloop_st->current_core.retro_unload_game)
|
||
runloop_st->current_core.retro_unload_game();
|
||
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
|
||
}
|
||
|
||
static void runahead_deinit_hook(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
runahead_remove_hooks(runloop_st);
|
||
runahead_destroy(runloop_st);
|
||
runloop_secondary_core_destroy();
|
||
if (runloop_st->current_core.retro_deinit)
|
||
runloop_st->current_core.retro_deinit();
|
||
}
|
||
|
||
static void runahead_add_hooks(runloop_state_t *runloop_st)
|
||
{
|
||
if (!runloop_st->original_retro_deinit)
|
||
{
|
||
runloop_st->original_retro_deinit =
|
||
runloop_st->current_core.retro_deinit;
|
||
runloop_st->current_core.retro_deinit = runahead_deinit_hook;
|
||
}
|
||
|
||
if (!runloop_st->original_retro_unload)
|
||
{
|
||
runloop_st->original_retro_unload = runloop_st->current_core.retro_unload_game;
|
||
runloop_st->current_core.retro_unload_game = unload_hook;
|
||
}
|
||
add_input_state_hook(runloop_st);
|
||
}
|
||
|
||
/* Runahead Code */
|
||
|
||
static void runahead_error(runloop_state_t *runloop_st)
|
||
{
|
||
runloop_st->runahead_available = false;
|
||
mylist_destroy(&runloop_st->runahead_save_state_list);
|
||
runahead_remove_hooks(runloop_st);
|
||
runloop_st->runahead_save_state_size = 0;
|
||
runloop_st->runahead_save_state_size_known = true;
|
||
}
|
||
|
||
static bool runahead_create(runloop_state_t *runloop_st)
|
||
{
|
||
/* get savestate size and allocate buffer */
|
||
retro_ctx_size_info_t info;
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
|
||
runloop_st->request_fast_savestate = true;
|
||
core_serialize_size(&info);
|
||
runloop_st->request_fast_savestate = false;
|
||
|
||
runahead_save_state_list_init(runloop_st, info.size);
|
||
video_st->runahead_is_active = video_st->active;
|
||
|
||
if ( (runloop_st->runahead_save_state_size == 0) ||
|
||
!runloop_st->runahead_save_state_size_known)
|
||
{
|
||
runahead_error(runloop_st);
|
||
return false;
|
||
}
|
||
|
||
runahead_add_hooks(runloop_st);
|
||
runloop_st->runahead_force_input_dirty = true;
|
||
if (runloop_st->runahead_save_state_list)
|
||
mylist_resize(runloop_st->runahead_save_state_list, 1, true);
|
||
return true;
|
||
}
|
||
|
||
static bool runahead_save_state(runloop_state_t *runloop_st)
|
||
{
|
||
retro_ctx_serialize_info_t *serialize_info;
|
||
bool okay = false;
|
||
|
||
if (!runloop_st->runahead_save_state_list)
|
||
return false;
|
||
|
||
serialize_info =
|
||
(retro_ctx_serialize_info_t*)runloop_st->runahead_save_state_list->data[0];
|
||
|
||
runloop_st->request_fast_savestate = true;
|
||
okay = core_serialize(serialize_info);
|
||
runloop_st->request_fast_savestate = false;
|
||
|
||
if (okay)
|
||
return true;
|
||
|
||
runahead_error(runloop_st);
|
||
return false;
|
||
}
|
||
|
||
static bool runahead_load_state(runloop_state_t *runloop_st)
|
||
{
|
||
bool okay = false;
|
||
retro_ctx_serialize_info_t *serialize_info =
|
||
(retro_ctx_serialize_info_t*)
|
||
runloop_st->runahead_save_state_list->data[0];
|
||
bool last_dirty = runloop_st->input_is_dirty;
|
||
|
||
runloop_st->request_fast_savestate = true;
|
||
/* calling core_unserialize has side effects with
|
||
* netplay (it triggers transmitting your save state)
|
||
call retro_unserialize directly from the core instead */
|
||
okay = runloop_st->current_core.retro_unserialize(
|
||
serialize_info->data_const, serialize_info->size);
|
||
|
||
runloop_st->request_fast_savestate = false;
|
||
runloop_st->input_is_dirty = last_dirty;
|
||
|
||
if (!okay)
|
||
runahead_error(runloop_st);
|
||
|
||
return okay;
|
||
}
|
||
|
||
#if HAVE_DYNAMIC
|
||
static bool runahead_load_state_secondary(void)
|
||
{
|
||
bool okay = false;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
settings_t *settings = config_get_ptr();
|
||
retro_ctx_serialize_info_t *serialize_info =
|
||
(retro_ctx_serialize_info_t*)runloop_st->runahead_save_state_list->data[0];
|
||
|
||
runloop_st->request_fast_savestate = true;
|
||
okay =
|
||
secondary_core_deserialize(settings,
|
||
serialize_info->data_const, (int)serialize_info->size);
|
||
runloop_st->request_fast_savestate = false;
|
||
|
||
if (!okay)
|
||
{
|
||
runloop_st->runahead_secondary_core_available = false;
|
||
runahead_error(runloop_st);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
static void runahead_core_run_use_last_input(runloop_state_t *runloop_st)
|
||
{
|
||
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
|
||
retro_input_poll_t old_poll_function = cbs->poll_cb;
|
||
retro_input_state_t old_input_function = cbs->state_cb;
|
||
|
||
cbs->poll_cb = retro_input_poll_null;
|
||
cbs->state_cb = input_state_get_last;
|
||
|
||
runloop_st->current_core.retro_set_input_poll(cbs->poll_cb);
|
||
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
|
||
|
||
runloop_st->current_core.retro_run();
|
||
|
||
cbs->poll_cb = old_poll_function;
|
||
cbs->state_cb = old_input_function;
|
||
|
||
runloop_st->current_core.retro_set_input_poll(cbs->poll_cb);
|
||
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
|
||
}
|
||
|
||
static void do_runahead(
|
||
runloop_state_t *runloop_st,
|
||
int runahead_count,
|
||
bool runahead_hide_warnings,
|
||
bool use_secondary)
|
||
{
|
||
int frame_number = 0;
|
||
bool last_frame = false;
|
||
bool suspended_frame = false;
|
||
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
||
const bool have_dynamic = true;
|
||
#else
|
||
const bool have_dynamic = false;
|
||
#endif
|
||
video_driver_state_t
|
||
*video_st = video_state_get_ptr();
|
||
uint64_t frame_count = video_st->frame_count;
|
||
audio_driver_state_t
|
||
*audio_st = audio_state_get_ptr();
|
||
|
||
if (runahead_count <= 0 || !runloop_st->runahead_available)
|
||
goto force_input_dirty;
|
||
|
||
if (!runloop_st->runahead_save_state_size_known)
|
||
{
|
||
/* Disable runahead if current core reports
|
||
* that it has an insufficient savestate
|
||
* support level */
|
||
if (!core_info_current_supports_runahead())
|
||
{
|
||
runahead_error(runloop_st);
|
||
/* If core is incompatible with runahead,
|
||
* log a warning but do not spam OSD messages.
|
||
* Runahead menu entries are hidden when using
|
||
* incompatible cores, so there is no mechanism
|
||
* for users to respond to notifications. In
|
||
* addition, auto-disabling runahead is a feature,
|
||
* not a cause for 'concern'; OSD warnings should
|
||
* be reserved for when a core reports that it is
|
||
* runahead-compatible but subsequently fails in
|
||
* execution */
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_RUNAHEAD));
|
||
goto force_input_dirty;
|
||
}
|
||
|
||
if (!runahead_create(runloop_st))
|
||
{
|
||
if (!runahead_hide_warnings)
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_SAVESTATES), 0, 2 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_SAVESTATES));
|
||
goto force_input_dirty;
|
||
}
|
||
}
|
||
|
||
/* Check for GUI */
|
||
/* Hack: If we were in the GUI, force a resync. */
|
||
if (frame_count != runloop_st->runahead_last_frame_count + 1)
|
||
runloop_st->runahead_force_input_dirty = true;
|
||
|
||
runloop_st->runahead_last_frame_count = frame_count;
|
||
|
||
if ( !use_secondary
|
||
|| !have_dynamic
|
||
|| !runloop_st->runahead_secondary_core_available)
|
||
{
|
||
/* TODO: multiple savestates for higher performance
|
||
* when not using secondary core */
|
||
for (frame_number = 0; frame_number <= runahead_count; frame_number++)
|
||
{
|
||
last_frame = frame_number == runahead_count;
|
||
suspended_frame = !last_frame;
|
||
|
||
if (suspended_frame)
|
||
{
|
||
audio_st->suspended = true;
|
||
video_st->active = false;
|
||
}
|
||
|
||
if (frame_number == 0)
|
||
core_run();
|
||
else
|
||
runahead_core_run_use_last_input(runloop_st);
|
||
|
||
if (suspended_frame)
|
||
{
|
||
video_st->active = video_st->runahead_is_active;
|
||
audio_st->suspended = false;
|
||
}
|
||
|
||
if (frame_number == 0)
|
||
{
|
||
if (!runahead_save_state(runloop_st))
|
||
{
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE), 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE));
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (last_frame)
|
||
{
|
||
if (!runahead_load_state(runloop_st))
|
||
{
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE), 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
#if HAVE_DYNAMIC
|
||
if (!secondary_core_ensure_exists(config_get_ptr()))
|
||
{
|
||
runloop_secondary_core_destroy();
|
||
runloop_st->runahead_secondary_core_available = false;
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE), 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE));
|
||
goto force_input_dirty;
|
||
}
|
||
|
||
/* run main core with video suspended */
|
||
video_st->active = false;
|
||
core_run();
|
||
video_st->active = video_st->runahead_is_active;
|
||
|
||
if ( runloop_st->input_is_dirty
|
||
|| runloop_st->runahead_force_input_dirty)
|
||
{
|
||
runloop_st->input_is_dirty = false;
|
||
|
||
if (!runahead_save_state(runloop_st))
|
||
{
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE), 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE));
|
||
return;
|
||
}
|
||
|
||
if (!runahead_load_state_secondary())
|
||
{
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE), 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE));
|
||
return;
|
||
}
|
||
|
||
for (frame_number = 0; frame_number < runahead_count - 1; frame_number++)
|
||
{
|
||
video_st->active = false;
|
||
audio_st->suspended = true;
|
||
audio_st->hard_disable = true;
|
||
runloop_st->runahead_secondary_core_available =
|
||
secondary_core_run_use_last_input();
|
||
audio_st->hard_disable = false;
|
||
audio_st->suspended = false;
|
||
video_st->active = video_st->runahead_is_active;
|
||
}
|
||
}
|
||
audio_st->suspended = true;
|
||
audio_st->hard_disable = true;
|
||
runloop_st->runahead_secondary_core_available =
|
||
secondary_core_run_use_last_input();
|
||
audio_st->hard_disable = false;
|
||
audio_st->suspended = false;
|
||
#endif
|
||
}
|
||
runloop_st->runahead_force_input_dirty= false;
|
||
return;
|
||
|
||
force_input_dirty:
|
||
core_run();
|
||
runloop_st->runahead_force_input_dirty= true;
|
||
}
|
||
#endif
|
||
|
||
static retro_time_t runloop_core_runtime_tick(
|
||
runloop_state_t *runloop_st,
|
||
float slowmotion_ratio,
|
||
retro_time_t current_time)
|
||
{
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
retro_time_t frame_time =
|
||
(1.0 / video_st->av_info.timing.fps) * 1000000;
|
||
bool runloop_slowmotion = runloop_st->slowmotion;
|
||
bool runloop_fastmotion = runloop_st->fastmotion;
|
||
|
||
/* Account for slow motion */
|
||
if (runloop_slowmotion)
|
||
return (retro_time_t)((double)frame_time * slowmotion_ratio);
|
||
|
||
/* Account for fast forward */
|
||
if (runloop_fastmotion)
|
||
{
|
||
/* Doing it this way means we miss the first frame after
|
||
* turning fast forward on, but it saves the overhead of
|
||
* having to do:
|
||
* retro_time_t current_usec = cpu_features_get_time_usec();
|
||
* core_runtime_last = current_usec;
|
||
* every frame when fast forward is off. */
|
||
retro_time_t current_usec = current_time;
|
||
retro_time_t potential_frame_time = current_usec -
|
||
runloop_st->core_runtime_last;
|
||
runloop_st->core_runtime_last = current_usec;
|
||
|
||
if (potential_frame_time < frame_time)
|
||
return potential_frame_time;
|
||
}
|
||
|
||
return frame_time;
|
||
}
|
||
|
||
static bool core_unload_game(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
video_driver_free_hw_context();
|
||
|
||
video_driver_set_cached_frame_ptr(NULL);
|
||
|
||
if (runloop_st->current_core.game_loaded)
|
||
{
|
||
RARCH_LOG("[Core]: Unloading game..\n");
|
||
runloop_st->current_core.retro_unload_game();
|
||
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
|
||
runloop_st->current_core.game_loaded = false;
|
||
}
|
||
|
||
audio_driver_stop();
|
||
|
||
return true;
|
||
}
|
||
|
||
static void runloop_apply_fastmotion_override(runloop_state_t *runloop_st, settings_t *settings)
|
||
{
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
bool frame_time_counter_reset_after_fastforwarding = settings ?
|
||
settings->bools.frame_time_counter_reset_after_fastforwarding : false;
|
||
float fastforward_ratio_default = settings ?
|
||
settings->floats.fastforward_ratio : 0.0f;
|
||
float fastforward_ratio_last =
|
||
(runloop_st->fastmotion_override.current.fastforward &&
|
||
(runloop_st->fastmotion_override.current.ratio >= 0.0f)) ?
|
||
runloop_st->fastmotion_override.current.ratio :
|
||
fastforward_ratio_default;
|
||
float fastforward_ratio_current;
|
||
|
||
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->fastmotion !=
|
||
runloop_st->fastmotion_override.current.fastforward)
|
||
{
|
||
input_driver_state_t *input_st = input_state_get_ptr();
|
||
runloop_st->fastmotion =
|
||
runloop_st->fastmotion_override.current.fastforward;
|
||
|
||
if (input_st)
|
||
{
|
||
if (runloop_st->fastmotion)
|
||
input_st->nonblocking_flag = true;
|
||
else
|
||
input_st->nonblocking_flag = false;
|
||
}
|
||
|
||
if (!runloop_st->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->fastmotion &&
|
||
frame_time_counter_reset_after_fastforwarding)
|
||
video_st->frame_time_count = 0;
|
||
|
||
/* Ensure fast forward widget is disabled when
|
||
* toggling fast-forward off
|
||
* (required if RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE
|
||
* is called during core de-initialisation) */
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (dispwidget_get_ptr()->active && !runloop_st->fastmotion)
|
||
video_st->widgets_fast_forward = false;
|
||
#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_st->frame_limit_minimum_time =
|
||
runloop_set_frame_limit(&video_st->av_info,
|
||
fastforward_ratio_current);
|
||
}
|
||
|
||
|
||
void runloop_event_deinit_core(void)
|
||
{
|
||
video_driver_state_t
|
||
*video_st = video_state_get_ptr();
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
settings_t *settings = config_get_ptr();
|
||
|
||
core_unload_game();
|
||
|
||
video_driver_set_cached_frame_ptr(NULL);
|
||
|
||
if (runloop_st->current_core.inited)
|
||
{
|
||
RARCH_LOG("[Core]: Unloading core..\n");
|
||
runloop_st->current_core.retro_deinit();
|
||
}
|
||
|
||
/* retro_deinit() may call
|
||
* RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE
|
||
* (i.e. to ensure that fastforwarding is
|
||
* disabled on core close)
|
||
* > Check for any pending updates */
|
||
if (runloop_st->fastmotion_override.pending)
|
||
{
|
||
runloop_apply_fastmotion_override(runloop_st,
|
||
settings);
|
||
runloop_st->fastmotion_override.pending = false;
|
||
}
|
||
|
||
if ( runloop_st->remaps_core_active
|
||
|| runloop_st->remaps_content_dir_active
|
||
|| runloop_st->remaps_game_active
|
||
|| !string_is_empty(runloop_st->name.remapfile)
|
||
)
|
||
{
|
||
input_remapping_deinit(settings->bools.remap_save_on_exit);
|
||
input_remapping_set_defaults(true);
|
||
}
|
||
else
|
||
input_remapping_restore_global_config(true);
|
||
|
||
RARCH_LOG("[Core]: Unloading core symbols..\n");
|
||
uninit_libretro_symbols(&runloop_st->current_core);
|
||
runloop_st->current_core.symbols_inited = false;
|
||
|
||
/* 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;
|
||
|
||
driver_uninit(DRIVERS_CMD_ALL);
|
||
|
||
#ifdef HAVE_CONFIGFILE
|
||
if (runloop_st->overrides_active)
|
||
{
|
||
/* Reload the original config */
|
||
config_unload_override();
|
||
runloop_st->overrides_active = false;
|
||
}
|
||
#endif
|
||
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
|
||
runloop_st->runtime_shader_preset_path[0] = '\0';
|
||
#endif
|
||
}
|
||
|
||
static void runloop_path_init_savefile_internal(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
path_deinit_savefile();
|
||
path_init_savefile_new();
|
||
|
||
if (!runloop_path_init_subsystem())
|
||
path_init_savefile_rtc(runloop_st->name.savefile);
|
||
}
|
||
|
||
static bool event_init_content(
|
||
settings_t *settings,
|
||
input_driver_state_t *input_st)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool contentless = false;
|
||
bool is_inited = false;
|
||
#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;
|
||
|
||
content_get_status(&contentless, &is_inited);
|
||
|
||
runloop_st->use_sram = (current_core_type == CORE_TYPE_PLAIN);
|
||
|
||
/* No content to be loaded for dummy core,
|
||
* just successfully exit. */
|
||
if (current_core_type == CORE_TYPE_DUMMY)
|
||
return true;
|
||
|
||
content_set_subsystem_info();
|
||
|
||
content_get_status(&contentless, &is_inited);
|
||
|
||
/* If core is contentless, just initialise SRAM
|
||
* interface, otherwise fill all content-related
|
||
* paths */
|
||
if (contentless)
|
||
runloop_path_init_savefile_internal();
|
||
else
|
||
runloop_path_fill_names();
|
||
|
||
if (!content_init())
|
||
return false;
|
||
|
||
command_event_set_savestate_auto_index(settings);
|
||
|
||
if (!event_load_save_files(runloop_st->is_sram_load_disabled))
|
||
RARCH_LOG("[SRAM]: %s\n",
|
||
msg_hash_to_str(MSG_SKIPPING_SRAM_LOAD));
|
||
|
||
/*
|
||
Since the operations are asynchronous we can't
|
||
guarantee users will not use auto_load_state to cheat on
|
||
achievements so we forbid auto_load_state from happening
|
||
if cheevos_enable and cheevos_hardcode_mode_enable
|
||
are true.
|
||
*/
|
||
#ifdef HAVE_CHEEVOS
|
||
if (!cheevos_enable || !cheevos_hardcore_mode_enable)
|
||
#endif
|
||
{
|
||
if (runloop_st->entry_state_slot && !command_event_load_entry_state())
|
||
runloop_st->entry_state_slot = 0;
|
||
if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load)
|
||
command_event_load_auto_state();
|
||
}
|
||
|
||
#ifdef HAVE_BSV_MOVIE
|
||
bsv_movie_deinit(input_st);
|
||
if (bsv_movie_init(input_st))
|
||
{
|
||
/* Set granularity upon success */
|
||
configuration_set_uint(settings,
|
||
settings->uints.rewind_granularity, 1);
|
||
}
|
||
#endif
|
||
command_event(CMD_EVENT_NETPLAY_INIT, NULL);
|
||
|
||
return true;
|
||
}
|
||
|
||
static void runloop_runtime_log_init(runloop_state_t *runloop_st)
|
||
{
|
||
const char *content_path = path_get(RARCH_PATH_CONTENT);
|
||
const char *core_path = path_get(RARCH_PATH_CORE);
|
||
|
||
runloop_st->core_runtime_last = cpu_features_get_time_usec();
|
||
runloop_st->core_runtime_usec = 0;
|
||
|
||
/* Have to cache content and core path here, otherwise
|
||
* logging fails if new content is loaded without
|
||
* closing existing content
|
||
* i.e. RARCH_PATH_CONTENT and RARCH_PATH_CORE get
|
||
* updated when the new content is loaded, which
|
||
* happens *before* command_event_runtime_log_deinit
|
||
* -> using RARCH_PATH_CONTENT and RARCH_PATH_CORE
|
||
* directly in command_event_runtime_log_deinit
|
||
* can therefore lead to the runtime of the currently
|
||
* loaded content getting written to the *new*
|
||
* content's log file... */
|
||
memset(runloop_st->runtime_content_path,
|
||
0, sizeof(runloop_st->runtime_content_path));
|
||
memset(runloop_st->runtime_core_path,
|
||
0, sizeof(runloop_st->runtime_core_path));
|
||
|
||
if (!string_is_empty(content_path))
|
||
strlcpy(runloop_st->runtime_content_path,
|
||
content_path,
|
||
sizeof(runloop_st->runtime_content_path));
|
||
|
||
if (!string_is_empty(core_path))
|
||
strlcpy(runloop_st->runtime_core_path,
|
||
core_path,
|
||
sizeof(runloop_st->runtime_core_path));
|
||
}
|
||
|
||
float runloop_set_frame_limit(
|
||
const struct retro_system_av_info *av_info,
|
||
float fastforward_ratio)
|
||
{
|
||
if (fastforward_ratio < 1.0f)
|
||
return 0.0f;
|
||
return (retro_time_t)roundf(1000000.0f /
|
||
(av_info->timing.fps * fastforward_ratio));
|
||
}
|
||
|
||
float runloop_get_fastforward_ratio(
|
||
settings_t *settings,
|
||
struct retro_fastforwarding_override *fastmotion_override)
|
||
{
|
||
if ( fastmotion_override->fastforward
|
||
&& (fastmotion_override->ratio >= 0.0f))
|
||
return fastmotion_override->ratio;
|
||
return settings->floats.fastforward_ratio;
|
||
}
|
||
|
||
void runloop_set_video_swap_interval(
|
||
bool vrr_runloop_enable,
|
||
bool crt_switching_active,
|
||
unsigned swap_interval_config,
|
||
float audio_max_timing_skew,
|
||
float video_refresh_rate,
|
||
double input_fps)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
float core_hz = input_fps;
|
||
float timing_hz = crt_switching_active ?
|
||
input_fps : video_refresh_rate;
|
||
float swap_ratio;
|
||
unsigned swap_integer;
|
||
float timing_skew;
|
||
|
||
/* If automatic swap interval selection is
|
||
* disabled, just record user-set value */
|
||
if (swap_interval_config != 0)
|
||
{
|
||
runloop_st->video_swap_interval_auto =
|
||
swap_interval_config;
|
||
return;
|
||
}
|
||
|
||
/* > If VRR is enabled, swap interval is irrelevant,
|
||
* just set to 1
|
||
* > If core fps is higher than display refresh rate,
|
||
* set swap interval to 1
|
||
* > If core fps or display refresh rate are zero,
|
||
* set swap interval to 1 */
|
||
if (vrr_runloop_enable ||
|
||
(core_hz > timing_hz) ||
|
||
(core_hz <= 0.0f) ||
|
||
(timing_hz <= 0.0f))
|
||
{
|
||
runloop_st->video_swap_interval_auto = 1;
|
||
return;
|
||
}
|
||
|
||
/* Check whether display refresh rate is an integer
|
||
* multiple of core fps (within timing skew tolerance) */
|
||
swap_ratio = timing_hz / core_hz;
|
||
swap_integer = (unsigned)(swap_ratio + 0.5f);
|
||
|
||
/* > Sanity check: swap interval must be in the
|
||
* range [1,4] - if we are outside this, then
|
||
* bail... */
|
||
if ((swap_integer < 1) || (swap_integer > 4))
|
||
{
|
||
runloop_st->video_swap_interval_auto = 1;
|
||
return;
|
||
}
|
||
|
||
timing_skew = fabs(1.0f - core_hz / (timing_hz / (float)swap_integer));
|
||
|
||
runloop_st->video_swap_interval_auto =
|
||
(timing_skew <= audio_max_timing_skew) ?
|
||
swap_integer : 1;
|
||
}
|
||
|
||
unsigned runloop_get_video_swap_interval(
|
||
unsigned swap_interval_config)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
return (swap_interval_config == 0) ?
|
||
runloop_st->video_swap_interval_auto :
|
||
swap_interval_config;
|
||
}
|
||
|
||
unsigned int retroarch_get_rotation(void)
|
||
{
|
||
settings_t *settings = config_get_ptr();
|
||
unsigned video_rotation = settings->uints.video_rotation;
|
||
return video_rotation + runloop_state.system.rotation;
|
||
}
|
||
|
||
static void retro_run_null(void) { } /* Stub function callback impl. */
|
||
|
||
static bool core_verify_api_version(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
unsigned api_version = runloop_st->current_core.retro_api_version();
|
||
|
||
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
|
||
);
|
||
|
||
if (api_version != RETRO_API_VERSION)
|
||
{
|
||
RARCH_WARN("[Core]: %s\n", msg_hash_to_str(MSG_LIBRETRO_ABI_BREAK));
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static int16_t core_input_state_poll_late(unsigned port,
|
||
unsigned device, unsigned idx, unsigned id)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!runloop_st->current_core.input_polled)
|
||
input_driver_poll();
|
||
runloop_st->current_core.input_polled = true;
|
||
|
||
return input_driver_state_wrapper(port, device, idx, id);
|
||
}
|
||
|
||
static void core_input_state_poll_maybe(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
const enum poll_type_override_t
|
||
core_poll_type_override = runloop_st->core_poll_type_override;
|
||
unsigned new_poll_type = (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
|
||
? (core_poll_type_override - 1)
|
||
: runloop_st->current_core.poll_type;
|
||
if (new_poll_type == POLL_TYPE_NORMAL)
|
||
input_driver_poll();
|
||
}
|
||
|
||
|
||
static retro_input_state_t core_input_state_poll_return_cb(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
const enum poll_type_override_t
|
||
core_poll_type_override = runloop_st->core_poll_type_override;
|
||
unsigned new_poll_type = (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
|
||
? (core_poll_type_override - 1)
|
||
: runloop_st->current_core.poll_type;
|
||
if (new_poll_type == POLL_TYPE_LATE)
|
||
return core_input_state_poll_late;
|
||
return input_driver_state_wrapper;
|
||
}
|
||
|
||
|
||
/**
|
||
* core_init_libretro_cbs:
|
||
* @data : pointer to retro_callbacks object
|
||
*
|
||
* Initializes libretro callbacks, and binds the libretro callbacks
|
||
* to default callback functions.
|
||
**/
|
||
static bool core_init_libretro_cbs(struct retro_callbacks *cbs)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
retro_input_state_t state_cb = core_input_state_poll_return_cb();
|
||
|
||
runloop_st->current_core.retro_set_video_refresh(video_driver_frame);
|
||
runloop_st->current_core.retro_set_audio_sample(audio_driver_sample);
|
||
runloop_st->current_core.retro_set_audio_sample_batch(audio_driver_sample_batch);
|
||
runloop_st->current_core.retro_set_input_state(state_cb);
|
||
runloop_st->current_core.retro_set_input_poll(core_input_state_poll_maybe);
|
||
|
||
core_set_default_callbacks(cbs);
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
|
||
return true;
|
||
|
||
core_set_netplay_callbacks();
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
static bool core_load(unsigned poll_type_behavior)
|
||
{
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
runloop_st->current_core.poll_type = poll_type_behavior;
|
||
|
||
if (!core_verify_api_version())
|
||
return false;
|
||
if (!core_init_libretro_cbs(&runloop_st->retro_ctx))
|
||
return false;
|
||
|
||
runloop_st->current_core.retro_get_system_av_info(&video_st->av_info);
|
||
video_st->core_frame_time = 1000000 /
|
||
((video_st->av_info.timing.fps > 0.0) ?
|
||
video_st->av_info.timing.fps : 60.0);
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
bool runloop_event_init_core(
|
||
settings_t *settings,
|
||
void *input_data,
|
||
enum rarch_core_type type)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
input_driver_state_t *input_st = (input_driver_state_t*)input_data;
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
#ifdef HAVE_CONFIGFILE
|
||
bool auto_overrides_enable = settings->bools.auto_overrides_enable;
|
||
bool auto_remaps_enable = false;
|
||
const char *dir_input_remapping = NULL;
|
||
#endif
|
||
bool show_set_initial_disk_msg = false;
|
||
unsigned poll_type_behavior = 0;
|
||
float fastforward_ratio = 0.0f;
|
||
rarch_system_info_t *sys_info = &runloop_st->system;
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||
{
|
||
#ifdef HAVE_UPDATE_CORES
|
||
/* If netplay is enabled, update the core before initializing. */
|
||
const char *path_core = path_get(RARCH_PATH_CORE);
|
||
|
||
if (!string_is_empty(path_core) &&
|
||
!string_is_equal(path_core, "builtin"))
|
||
{
|
||
if (task_push_update_single_core(path_core,
|
||
settings->bools.core_updater_auto_backup,
|
||
settings->uints.core_updater_auto_backup_history_size,
|
||
settings->paths.directory_libretro,
|
||
settings->paths.directory_core_assets))
|
||
/* We must wait for the update to finish
|
||
before starting the core. */
|
||
task_queue_wait(NULL, NULL);
|
||
}
|
||
#endif
|
||
|
||
/* We need this in order for core_info_current_supports_netplay
|
||
to work correctly at init_netplay,
|
||
called later at event_init_content. */
|
||
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
|
||
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
|
||
}
|
||
#endif
|
||
|
||
if (!init_libretro_symbols(runloop_st,
|
||
type, &runloop_st->current_core))
|
||
return false;
|
||
if (!runloop_st->current_core.retro_run)
|
||
runloop_st->current_core.retro_run = retro_run_null;
|
||
runloop_st->current_core.symbols_inited = true;
|
||
runloop_st->current_core.retro_get_system_info(&sys_info->info);
|
||
|
||
if (!sys_info->info.library_name)
|
||
sys_info->info.library_name = msg_hash_to_str(MSG_UNKNOWN);
|
||
if (!sys_info->info.library_version)
|
||
sys_info->info.library_version = "v0";
|
||
|
||
strlcpy(
|
||
video_st->title_buf,
|
||
msg_hash_to_str(MSG_PROGRAM),
|
||
sizeof(video_st->title_buf));
|
||
strlcat(video_st->title_buf,
|
||
" ",
|
||
sizeof(video_st->title_buf));
|
||
strlcat(video_st->title_buf,
|
||
sys_info->info.library_name,
|
||
sizeof(video_st->title_buf));
|
||
strlcat(video_st->title_buf, " ",
|
||
sizeof(video_st->title_buf));
|
||
strlcat(video_st->title_buf,
|
||
sys_info->info.library_version,
|
||
sizeof(video_st->title_buf));
|
||
|
||
strlcpy(sys_info->valid_extensions,
|
||
sys_info->info.valid_extensions ?
|
||
sys_info->info.valid_extensions : DEFAULT_EXT,
|
||
sizeof(sys_info->valid_extensions));
|
||
|
||
#ifdef HAVE_CONFIGFILE
|
||
if (auto_overrides_enable)
|
||
runloop_st->overrides_active =
|
||
config_load_override(&runloop_st->system);
|
||
#endif
|
||
|
||
/* Cannot access these settings-related parameters
|
||
* until *after* config overrides have been loaded */
|
||
#ifdef HAVE_CONFIGFILE
|
||
auto_remaps_enable = settings->bools.auto_remaps_enable;
|
||
dir_input_remapping = settings->paths.directory_input_remapping;
|
||
#endif
|
||
show_set_initial_disk_msg = settings->bools.notification_show_set_initial_disk;
|
||
poll_type_behavior = settings->uints.input_poll_type_behavior;
|
||
fastforward_ratio = runloop_get_fastforward_ratio(
|
||
settings, &runloop_st->fastmotion_override.current);
|
||
|
||
#ifdef HAVE_CHEEVOS
|
||
/* assume the core supports achievements unless it tells us otherwise */
|
||
rcheevos_set_support_cheevos(true);
|
||
#endif
|
||
|
||
/* Load auto-shaders on the next occasion */
|
||
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
|
||
video_st->shader_presets_need_reload = true;
|
||
runloop_st->shader_delay_timer.timer_begin = false; /* not initialized */
|
||
runloop_st->shader_delay_timer.timer_end = false; /* not expired */
|
||
#endif
|
||
|
||
/* reset video format to libretro's default */
|
||
video_st->pix_fmt = RETRO_PIXEL_FORMAT_0RGB1555;
|
||
|
||
runloop_st->current_core.retro_set_environment(runloop_environment_cb);
|
||
|
||
/* Load any input remap files
|
||
* > Note that we always cache the current global
|
||
* input settings when initialising a core
|
||
* (regardless of whether remap files are loaded)
|
||
* so settings can be restored when the core is
|
||
* unloaded - i.e. core remapping options modified
|
||
* at runtime should not 'bleed through' into the
|
||
* master config file */
|
||
input_remapping_cache_global_config();
|
||
#ifdef HAVE_CONFIGFILE
|
||
if (auto_remaps_enable)
|
||
config_load_remap(dir_input_remapping, &runloop_st->system);
|
||
#endif
|
||
|
||
/* Per-core saves: reset redirection paths */
|
||
retroarch_path_set_redirect(settings);
|
||
|
||
video_driver_set_cached_frame_ptr(NULL);
|
||
|
||
runloop_st->current_core.retro_init();
|
||
runloop_st->current_core.inited = true;
|
||
|
||
/* Attempt to set initial disk index */
|
||
disk_control_set_initial_index(
|
||
&sys_info->disk_control,
|
||
path_get(RARCH_PATH_CONTENT),
|
||
runloop_st->savefile_dir);
|
||
|
||
if (!event_init_content(settings, input_st))
|
||
{
|
||
runloop_st->core_running = false;
|
||
return false;
|
||
}
|
||
|
||
/* Verify that initial disk index was set correctly */
|
||
disk_control_verify_initial_index(&sys_info->disk_control,
|
||
show_set_initial_disk_msg);
|
||
|
||
if (!core_load(poll_type_behavior))
|
||
return false;
|
||
|
||
runloop_st->frame_limit_minimum_time =
|
||
runloop_set_frame_limit(&video_st->av_info,
|
||
fastforward_ratio);
|
||
runloop_st->frame_limit_last_time = cpu_features_get_time_usec();
|
||
|
||
runloop_runtime_log_init(runloop_st);
|
||
return true;
|
||
}
|
||
|
||
#ifdef HAVE_RUNAHEAD
|
||
void runloop_runahead_clear_variables(runloop_state_t *runloop_st)
|
||
{
|
||
video_driver_state_t
|
||
*video_st = video_state_get_ptr();
|
||
runloop_st->runahead_save_state_size = 0;
|
||
runloop_st->runahead_save_state_size_known = false;
|
||
video_st->runahead_is_active = true;
|
||
runloop_st->runahead_available = true;
|
||
runloop_st->runahead_secondary_core_available = true;
|
||
runloop_st->runahead_force_input_dirty = true;
|
||
runloop_st->runahead_last_frame_count = 0;
|
||
}
|
||
#endif
|
||
|
||
|
||
void runloop_pause_checks(void)
|
||
{
|
||
#ifdef HAVE_PRESENCE
|
||
presence_userdata_t userdata;
|
||
#endif
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool is_paused = runloop_st->paused;
|
||
bool is_idle = runloop_st->idle;
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
bool widgets_active = dispwidget_get_ptr()->active;
|
||
if (widgets_active)
|
||
video_st->widgets_paused = is_paused;
|
||
#endif
|
||
|
||
if (is_paused)
|
||
{
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (!widgets_active)
|
||
#endif
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_PAUSED), 1,
|
||
1, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
|
||
|
||
if (!is_idle)
|
||
video_driver_cached_frame();
|
||
|
||
#ifdef HAVE_PRESENCE
|
||
userdata.status = PRESENCE_GAME_PAUSED;
|
||
command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
|
||
#endif
|
||
|
||
#ifndef HAVE_LAKKA_SWITCH
|
||
#ifdef HAVE_LAKKA
|
||
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
|
||
#endif
|
||
#endif /* #ifndef HAVE_LAKKA_SWITCH */
|
||
}
|
||
else
|
||
{
|
||
#ifndef HAVE_LAKKA_SWITCH
|
||
#ifdef HAVE_LAKKA
|
||
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
|
||
#endif
|
||
#endif /* #ifndef HAVE_LAKKA_SWITCH */
|
||
}
|
||
|
||
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
|
||
if (dispwidget_get_ptr()->ai_service_overlay_state == 1)
|
||
gfx_widgets_ai_service_overlay_unload();
|
||
#endif
|
||
}
|
||
|
||
void runloop_frame_time_free(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
memset(&runloop_st->frame_time, 0,
|
||
sizeof(struct retro_frame_time_callback));
|
||
runloop_st->frame_time_last = 0;
|
||
runloop_st->max_frames = 0;
|
||
}
|
||
|
||
void runloop_audio_buffer_status_free(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
memset(&runloop_st->audio_buffer_status, 0,
|
||
sizeof(struct retro_audio_buffer_status_callback));
|
||
runloop_st->audio_latency = 0;
|
||
}
|
||
|
||
void runloop_fastmotion_override_free(void)
|
||
{
|
||
runloop_state_t
|
||
*runloop_st = &runloop_state;
|
||
video_driver_state_t
|
||
*video_st = video_state_get_ptr();
|
||
settings_t *settings = config_get_ptr();
|
||
float fastforward_ratio = settings->floats.fastforward_ratio;
|
||
bool reset_frame_limit = runloop_st->fastmotion_override.current.fastforward &&
|
||
(runloop_st->fastmotion_override.current.ratio >= 0.0f) &&
|
||
(runloop_st->fastmotion_override.current.ratio != fastforward_ratio);
|
||
|
||
runloop_st->fastmotion_override.current.ratio = 0.0f;
|
||
runloop_st->fastmotion_override.current.fastforward = false;
|
||
runloop_st->fastmotion_override.current.notification = false;
|
||
runloop_st->fastmotion_override.current.inhibit_toggle = false;
|
||
|
||
runloop_st->fastmotion_override.next.ratio = 0.0f;
|
||
runloop_st->fastmotion_override.next.fastforward = false;
|
||
runloop_st->fastmotion_override.next.notification = false;
|
||
runloop_st->fastmotion_override.next.inhibit_toggle = false;
|
||
|
||
runloop_st->fastmotion_override.pending = false;
|
||
|
||
if (reset_frame_limit)
|
||
runloop_st->frame_limit_minimum_time =
|
||
runloop_set_frame_limit(&video_st->av_info, fastforward_ratio);
|
||
}
|
||
|
||
void runloop_core_options_cb_free(void)
|
||
{
|
||
runloop_state_t
|
||
*runloop_st = &runloop_state;
|
||
/* Only a single core options callback is used at present */
|
||
runloop_st->core_options_callback.update_display = NULL;
|
||
}
|
||
|
||
struct string_list *path_get_subsystem_list(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
return runloop_st->subsystem_fullpaths;
|
||
}
|
||
|
||
bool runloop_path_init_subsystem(void)
|
||
{
|
||
unsigned i, j;
|
||
const struct retro_subsystem_info *info = NULL;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
rarch_system_info_t *system = &runloop_st->system;
|
||
bool subsystem_path_empty = path_is_empty(RARCH_PATH_SUBSYSTEM);
|
||
const char *savefile_dir = runloop_st->savefile_dir;
|
||
|
||
if (!system || subsystem_path_empty)
|
||
return false;
|
||
|
||
/* For subsystems, we know exactly which RAM types are supported. */
|
||
/* We'll handle this error gracefully later. */
|
||
if ((info = libretro_find_subsystem_info(
|
||
system->subsystem.data,
|
||
system->subsystem.size,
|
||
path_get(RARCH_PATH_SUBSYSTEM))))
|
||
{
|
||
unsigned num_content = MIN(info->num_roms,
|
||
subsystem_path_empty ?
|
||
0 : (unsigned)runloop_st->subsystem_fullpaths->size);
|
||
|
||
for (i = 0; i < num_content; i++)
|
||
{
|
||
for (j = 0; j < info->roms[i].num_memory; j++)
|
||
{
|
||
char ext[32];
|
||
union string_list_elem_attr attr;
|
||
char savename[PATH_MAX_LENGTH];
|
||
char path[PATH_MAX_LENGTH];
|
||
const struct retro_subsystem_memory_info *mem =
|
||
(const struct retro_subsystem_memory_info*)
|
||
&info->roms[i].memory[j];
|
||
ext[0] = '.';
|
||
ext[1] = '\0';
|
||
strlcat(ext, mem->extension, sizeof(ext));
|
||
strlcpy(savename,
|
||
runloop_st->subsystem_fullpaths->elems[i].data,
|
||
sizeof(savename));
|
||
path_remove_extension(savename);
|
||
|
||
if (path_is_directory(savefile_dir))
|
||
{
|
||
/* Use SRAM dir */
|
||
/* Redirect content fullpath to save directory. */
|
||
strlcpy(path, savefile_dir, sizeof(path));
|
||
fill_pathname_dir(path, savename, ext, sizeof(path));
|
||
}
|
||
else
|
||
fill_pathname(path, savename, ext, sizeof(path));
|
||
|
||
RARCH_LOG("%s \"%s\".\n",
|
||
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
|
||
path);
|
||
|
||
attr.i = mem->type;
|
||
string_list_append((struct string_list*)savefile_ptr_get(),
|
||
path, attr);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Let other relevant paths be inferred
|
||
from the main SRAM location. */
|
||
if (!retroarch_override_setting_is_set(
|
||
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL))
|
||
{
|
||
strlcpy(runloop_st->name.savefile,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.savefile));
|
||
strlcat(runloop_st->name.savefile,
|
||
".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("%s \"%s\".\n",
|
||
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
|
||
runloop_st->name.savefile);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
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();
|
||
|
||
#ifdef HAVE_BSV_MOVIE
|
||
strlcpy(input_st->bsv_movie_state.movie_path,
|
||
runloop_st->name.savefile,
|
||
sizeof(input_st->bsv_movie_state.movie_path));
|
||
#endif
|
||
|
||
if (string_is_empty(runloop_st->runtime_content_path_basename))
|
||
return;
|
||
|
||
if (string_is_empty(runloop_st->name.ups))
|
||
{
|
||
strlcpy(runloop_st->name.ups,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.ups));
|
||
strlcat(runloop_st->name.ups,
|
||
".ups",
|
||
sizeof(runloop_st->name.ups));
|
||
}
|
||
|
||
if (string_is_empty(runloop_st->name.bps))
|
||
{
|
||
strlcpy(runloop_st->name.bps,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.bps));
|
||
strlcat(runloop_st->name.bps,
|
||
".bps",
|
||
sizeof(runloop_st->name.bps));
|
||
}
|
||
|
||
if (string_is_empty(runloop_st->name.ips))
|
||
{
|
||
strlcpy(runloop_st->name.ips,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.ips));
|
||
strlcat(runloop_st->name.ips,
|
||
".ips",
|
||
sizeof(runloop_st->name.ips));
|
||
}
|
||
}
|
||
|
||
|
||
void runloop_path_init_savefile(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool should_sram_be_used = runloop_st->use_sram
|
||
&& !runloop_st->is_sram_save_disabled;
|
||
|
||
if (!(runloop_st->use_sram = should_sram_be_used))
|
||
{
|
||
RARCH_LOG("[SRAM]: %s\n",
|
||
msg_hash_to_str(MSG_SRAM_WILL_NOT_BE_SAVED));
|
||
return;
|
||
}
|
||
|
||
command_event(CMD_EVENT_AUTOSAVE_INIT, NULL);
|
||
}
|
||
|
||
|
||
/* Creates folder and core options stub file for subsequent runs */
|
||
bool core_options_create_override(bool game_specific)
|
||
{
|
||
char options_path[PATH_MAX_LENGTH];
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
config_file_t *conf = NULL;
|
||
|
||
options_path[0] = '\0';
|
||
|
||
if (game_specific)
|
||
{
|
||
/* Get options file path (game-specific) */
|
||
if (!validate_game_options(
|
||
runloop_st->system.info.library_name,
|
||
options_path,
|
||
sizeof(options_path), true))
|
||
goto error;
|
||
}
|
||
else
|
||
{
|
||
/* Sanity check - cannot create a folder-specific
|
||
* override if a game-specific override is
|
||
* already active */
|
||
if (runloop_st->game_options_active)
|
||
goto error;
|
||
|
||
/* Get options file path (folder-specific) */
|
||
if (!validate_folder_options(
|
||
options_path,
|
||
sizeof(options_path), true))
|
||
goto error;
|
||
}
|
||
|
||
/* Open config file */
|
||
if (!(conf = config_file_new_from_path_to_string(options_path)))
|
||
if (!(conf = config_file_new_alloc()))
|
||
goto error;
|
||
|
||
/* Write config file */
|
||
core_option_manager_flush(runloop_st->core_options, conf);
|
||
|
||
if (!config_file_write(conf, options_path, true))
|
||
goto error;
|
||
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_CORE_OPTIONS_FILE_CREATED_SUCCESSFULLY),
|
||
1, 100, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
|
||
path_set(RARCH_PATH_CORE_OPTIONS, options_path);
|
||
runloop_st->game_options_active = game_specific;
|
||
runloop_st->folder_options_active = !game_specific;
|
||
|
||
config_file_free(conf);
|
||
return true;
|
||
|
||
error:
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_ERROR_SAVING_CORE_OPTIONS_FILE),
|
||
1, 100, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
|
||
if (conf)
|
||
config_file_free(conf);
|
||
|
||
return false;
|
||
}
|
||
|
||
bool core_options_remove_override(bool game_specific)
|
||
{
|
||
char new_options_path[PATH_MAX_LENGTH];
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
settings_t *settings = config_get_ptr();
|
||
core_option_manager_t *coreopts = runloop_st->core_options;
|
||
bool per_core_options = !settings->bools.global_core_options;
|
||
const char *path_core_options = settings->paths.path_core_options;
|
||
const char *current_options_path = NULL;
|
||
config_file_t *conf = NULL;
|
||
bool folder_options_active = false;
|
||
|
||
new_options_path[0] = '\0';
|
||
|
||
/* Sanity check 1 - if there are no core options
|
||
* or no overrides are active, there is nothing to do */
|
||
if ( !coreopts ||
|
||
( !runloop_st->game_options_active
|
||
&& !runloop_st->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->game_options_active)
|
||
goto error;
|
||
|
||
/* Get current options file path */
|
||
current_options_path = path_get(RARCH_PATH_CORE_OPTIONS);
|
||
if (string_is_empty(current_options_path))
|
||
goto error;
|
||
|
||
/* Remove current options file, if required */
|
||
if (path_is_valid(current_options_path))
|
||
filestream_delete(current_options_path);
|
||
|
||
/* Reload any existing 'parent' options file
|
||
* > If we have removed a game-specific config,
|
||
* check whether a folder-specific config
|
||
* exists */
|
||
if (game_specific &&
|
||
validate_folder_options(
|
||
new_options_path,
|
||
sizeof(new_options_path), false) &&
|
||
path_is_valid(new_options_path))
|
||
folder_options_active = true;
|
||
|
||
/* > If a folder-specific config does not exist,
|
||
* or we removed it, check whether we have a
|
||
* top-level config file */
|
||
if (!folder_options_active)
|
||
{
|
||
/* Try core-specific options, if enabled */
|
||
if (per_core_options)
|
||
{
|
||
const char *core_name = runloop_st->system.info.library_name;
|
||
per_core_options = validate_per_core_options(
|
||
new_options_path, sizeof(new_options_path), true,
|
||
core_name, core_name);
|
||
}
|
||
|
||
/* ...otherwise use global options */
|
||
if (!per_core_options)
|
||
{
|
||
if (!string_is_empty(path_core_options))
|
||
strlcpy(new_options_path,
|
||
path_core_options, sizeof(new_options_path));
|
||
else if (!path_is_empty(RARCH_PATH_CONFIG))
|
||
fill_pathname_resolve_relative(
|
||
new_options_path, path_get(RARCH_PATH_CONFIG),
|
||
FILE_PATH_CORE_OPTIONS_CONFIG, sizeof(new_options_path));
|
||
}
|
||
}
|
||
|
||
if (string_is_empty(new_options_path))
|
||
goto error;
|
||
|
||
/* > If we have a valid file, load it */
|
||
if (folder_options_active ||
|
||
path_is_valid(new_options_path))
|
||
{
|
||
size_t i, j;
|
||
|
||
if (!(conf = config_file_new_from_path_to_string(new_options_path)))
|
||
goto error;
|
||
|
||
for (i = 0; i < coreopts->size; i++)
|
||
{
|
||
struct config_entry_list *entry = NULL;
|
||
struct core_option *option = (struct core_option*)&coreopts->opts[i];
|
||
if (!option)
|
||
continue;
|
||
if (!(entry = config_get_entry(conf, option->key)))
|
||
continue;
|
||
if (string_is_empty(entry->value))
|
||
continue;
|
||
|
||
/* Set current config value from file entry */
|
||
for (j = 0; j < option->vals->size; j++)
|
||
{
|
||
if (string_is_equal(option->vals->elems[j].data, entry->value))
|
||
{
|
||
option->index = j;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
coreopts->updated = true;
|
||
|
||
#ifdef HAVE_CHEEVOS
|
||
rcheevos_validate_config_settings();
|
||
#endif
|
||
}
|
||
|
||
/* Update runloop status */
|
||
if (folder_options_active)
|
||
{
|
||
path_set(RARCH_PATH_CORE_OPTIONS, new_options_path);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = true;
|
||
}
|
||
else
|
||
{
|
||
path_clear(RARCH_PATH_CORE_OPTIONS);
|
||
runloop_st->game_options_active = false;
|
||
runloop_st->folder_options_active = false;
|
||
|
||
/* Update config file path/object stored in
|
||
* core option manager struct */
|
||
strlcpy(coreopts->conf_path, new_options_path,
|
||
sizeof(coreopts->conf_path));
|
||
|
||
if (conf)
|
||
{
|
||
config_file_free(coreopts->conf);
|
||
coreopts->conf = conf;
|
||
conf = NULL;
|
||
}
|
||
}
|
||
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_CORE_OPTIONS_FILE_REMOVED_SUCCESSFULLY),
|
||
1, 100, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
|
||
if (conf)
|
||
config_file_free(conf);
|
||
|
||
return true;
|
||
|
||
error:
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_ERROR_REMOVING_CORE_OPTIONS_FILE),
|
||
1, 100, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
|
||
if (conf)
|
||
config_file_free(conf);
|
||
|
||
return false;
|
||
}
|
||
|
||
void core_options_reset(void)
|
||
{
|
||
size_t i;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
core_option_manager_t *coreopts = runloop_st->core_options;
|
||
|
||
/* If there are no core options, there
|
||
* is nothing to do */
|
||
if (!coreopts || (coreopts->size < 1))
|
||
return;
|
||
|
||
for (i = 0; i < coreopts->size; i++)
|
||
coreopts->opts[i].index = coreopts->opts[i].default_index;
|
||
|
||
coreopts->updated = true;
|
||
|
||
#ifdef HAVE_CHEEVOS
|
||
rcheevos_validate_config_settings();
|
||
#endif
|
||
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_CORE_OPTIONS_RESET),
|
||
1, 100, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
|
||
void core_options_flush(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
core_option_manager_t *coreopts = runloop_st->core_options;
|
||
const char *path_core_options = path_get(RARCH_PATH_CORE_OPTIONS);
|
||
const char *core_options_file = NULL;
|
||
bool success = false;
|
||
char msg[256];
|
||
|
||
msg[0] = '\0';
|
||
|
||
/* If there are no core options, there
|
||
* is nothing to do */
|
||
if (!coreopts || (coreopts->size < 1))
|
||
return;
|
||
|
||
/* Check whether game/folder-specific options file
|
||
* is being used */
|
||
if (!string_is_empty(path_core_options))
|
||
{
|
||
config_file_t *conf_tmp = NULL;
|
||
bool path_valid = path_is_valid(path_core_options);
|
||
|
||
/* Attempt to load existing file */
|
||
if (path_valid)
|
||
conf_tmp = config_file_new_from_path_to_string(path_core_options);
|
||
|
||
/* Create new file if required */
|
||
if (!conf_tmp)
|
||
conf_tmp = config_file_new_alloc();
|
||
|
||
if (conf_tmp)
|
||
{
|
||
core_option_manager_flush(runloop_st->core_options, conf_tmp);
|
||
|
||
success = config_file_write(conf_tmp, path_core_options, true);
|
||
config_file_free(conf_tmp);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* We are using the 'default' core options file */
|
||
path_core_options = runloop_st->core_options->conf_path;
|
||
|
||
if (!string_is_empty(path_core_options))
|
||
{
|
||
core_option_manager_flush(
|
||
runloop_st->core_options,
|
||
runloop_st->core_options->conf);
|
||
|
||
/* We must *guarantee* that a file gets written
|
||
* to disk if any options differ from the current
|
||
* options file contents. Must therefore handle
|
||
* the case where the 'default' file does not
|
||
* exist (e.g. if it gets deleted manually while
|
||
* a core is running) */
|
||
if (!path_is_valid(path_core_options))
|
||
runloop_st->core_options->conf->modified = true;
|
||
|
||
success = config_file_write(runloop_st->core_options->conf,
|
||
path_core_options, true);
|
||
}
|
||
}
|
||
|
||
/* Get options file name for display purposes */
|
||
if (!string_is_empty(path_core_options))
|
||
core_options_file = path_basename_nocompression(path_core_options);
|
||
|
||
if (string_is_empty(core_options_file))
|
||
core_options_file = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN);
|
||
|
||
if (success)
|
||
{
|
||
/* Log result */
|
||
RARCH_LOG(
|
||
"[Core]: Saved core options to \"%s\".\n",
|
||
path_core_options ? path_core_options : "UNKNOWN");
|
||
|
||
snprintf(msg, sizeof(msg), "%s \"%s\"",
|
||
msg_hash_to_str(MSG_CORE_OPTIONS_FLUSHED),
|
||
core_options_file);
|
||
}
|
||
else
|
||
{
|
||
/* Log result */
|
||
RARCH_LOG(
|
||
"[Core]: Failed to save core options to \"%s\".\n",
|
||
path_core_options ? path_core_options : "UNKNOWN");
|
||
|
||
snprintf(msg, sizeof(msg), "%s \"%s\"",
|
||
msg_hash_to_str(MSG_CORE_OPTIONS_FLUSH_FAILED),
|
||
core_options_file);
|
||
}
|
||
|
||
runloop_msg_queue_push(
|
||
msg, 1, 100, true,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
|
||
void runloop_msg_queue_push(const char *msg,
|
||
unsigned prio, unsigned duration,
|
||
bool flush,
|
||
char *title,
|
||
enum message_queue_icon icon,
|
||
enum message_queue_category category)
|
||
{
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
bool widgets_active = dispwidget_get_ptr()->active;
|
||
#endif
|
||
#ifdef HAVE_ACCESSIBILITY
|
||
settings_t *settings = config_get_ptr();
|
||
bool accessibility_enable = settings->bools.accessibility_enable;
|
||
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
|
||
access_state_t *access_st = access_state_get_ptr();
|
||
#endif
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
|
||
#ifdef HAVE_ACCESSIBILITY
|
||
if (is_accessibility_enabled(
|
||
accessibility_enable,
|
||
access_st->enabled))
|
||
accessibility_speak_priority(
|
||
accessibility_enable,
|
||
accessibility_narrator_speech_speed,
|
||
(char*) msg, 0);
|
||
#endif
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (widgets_active)
|
||
{
|
||
gfx_widgets_msg_queue_push(
|
||
NULL,
|
||
msg,
|
||
roundf((float)duration / 60.0f * 1000.0f),
|
||
title,
|
||
icon,
|
||
category,
|
||
prio,
|
||
flush,
|
||
#ifdef HAVE_MENU
|
||
menu_state_get_ptr()->alive
|
||
#else
|
||
false
|
||
#endif
|
||
);
|
||
duration = duration * 60 / 1000;
|
||
}
|
||
else
|
||
#endif
|
||
{
|
||
if (flush)
|
||
msg_queue_clear(&runloop_st->msg_queue);
|
||
|
||
msg_queue_push(&runloop_st->msg_queue, msg,
|
||
prio, duration,
|
||
title, icon, category);
|
||
|
||
runloop_st->msg_queue_size = msg_queue_size(
|
||
&runloop_st->msg_queue);
|
||
}
|
||
|
||
ui_companion_driver_msg_queue_push(
|
||
msg, prio, duration, flush);
|
||
|
||
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
|
||
}
|
||
|
||
#ifdef HAVE_MENU
|
||
/* Display the libretro core's framebuffer onscreen. */
|
||
static bool display_menu_libretro(
|
||
runloop_state_t *runloop_st,
|
||
input_driver_state_t *input_st,
|
||
float slowmotion_ratio,
|
||
bool libretro_running,
|
||
retro_time_t current_time)
|
||
{
|
||
bool runloop_idle = runloop_st->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->block_libretro_input)
|
||
input_st->block_libretro_input = true;
|
||
|
||
core_run();
|
||
runloop_st->core_runtime_usec +=
|
||
runloop_core_runtime_tick(runloop_st, slowmotion_ratio, current_time);
|
||
input_st->block_libretro_input = false;
|
||
|
||
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 enum runloop_state_enum runloop_check_state(
|
||
bool error_on_init,
|
||
settings_t *settings,
|
||
retro_time_t current_time)
|
||
{
|
||
input_bits_t current_bits;
|
||
#ifdef HAVE_MENU
|
||
static input_bits_t last_input = {{0}};
|
||
#endif
|
||
uico_driver_state_t *uico_st = uico_state_get_ptr();
|
||
input_driver_state_t *input_st = input_state_get_ptr();
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
gfx_display_t *p_disp = disp_get_ptr();
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
static bool old_focus = true;
|
||
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
|
||
bool is_focused = false;
|
||
bool is_alive = false;
|
||
uint64_t frame_count = 0;
|
||
bool focused = true;
|
||
bool rarch_is_initialized = runloop_st->is_inited;
|
||
bool runloop_paused = runloop_st->paused;
|
||
bool pause_nonactive = settings->bools.pause_nonactive;
|
||
unsigned quit_gamepad_combo = settings->uints.input_quit_gamepad_combo;
|
||
#ifdef HAVE_MENU
|
||
struct menu_state *menu_st = menu_state_get_ptr();
|
||
menu_handle_t *menu = menu_st->driver_data;
|
||
unsigned menu_toggle_gamepad_combo = settings->uints.input_menu_toggle_gamepad_combo;
|
||
bool menu_driver_binding_state = menu_st->is_binding;
|
||
bool menu_is_alive = menu_st->alive;
|
||
bool display_kb = menu_input_dialog_get_display_kb();
|
||
#endif
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
bool widgets_active = dispwidget_get_ptr()->active;
|
||
#endif
|
||
#ifdef HAVE_CHEEVOS
|
||
bool cheevos_hardcore_active = false;
|
||
#endif
|
||
|
||
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
|
||
if (dispwidget_get_ptr()->ai_service_overlay_state == 3)
|
||
{
|
||
command_event(CMD_EVENT_PAUSE, NULL);
|
||
dispwidget_get_ptr()->ai_service_overlay_state = 1;
|
||
}
|
||
#endif
|
||
|
||
#ifdef HAVE_LIBNX
|
||
/* Should be called once per frame */
|
||
if (!appletMainLoop())
|
||
return RUNLOOP_STATE_QUIT;
|
||
#endif
|
||
|
||
#ifdef _3DS
|
||
/* Should be called once per frame */
|
||
if (!aptMainLoop())
|
||
return RUNLOOP_STATE_QUIT;
|
||
#endif
|
||
|
||
BIT256_CLEAR_ALL_PTR(¤t_bits);
|
||
|
||
input_st->block_libretro_input = false;
|
||
input_st->block_hotkey = false;
|
||
|
||
if (input_st->keyboard_mapping_blocked)
|
||
input_st->block_hotkey = true;
|
||
|
||
input_driver_collect_system_input(input_st, settings, ¤t_bits);
|
||
|
||
#ifdef HAVE_MENU
|
||
last_input = current_bits;
|
||
if (
|
||
((menu_toggle_gamepad_combo != INPUT_COMBO_NONE) &&
|
||
input_driver_button_combo(
|
||
menu_toggle_gamepad_combo,
|
||
current_time,
|
||
&last_input)))
|
||
BIT256_SET(current_bits, RARCH_MENU_TOGGLE);
|
||
|
||
if (menu_st->input_driver_flushing_input > 0)
|
||
{
|
||
bool input_active = bits_any_set(current_bits.data, ARRAY_SIZE(current_bits.data));
|
||
|
||
if (!input_active)
|
||
menu_st->input_driver_flushing_input = (menu_st->input_driver_flushing_input - 1);
|
||
|
||
if (input_active || (menu_st->input_driver_flushing_input > 0))
|
||
{
|
||
BIT256_CLEAR_ALL(current_bits);
|
||
if (runloop_paused)
|
||
BIT256_SET(current_bits, RARCH_PAUSE_TOGGLE);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
|
||
if (!VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st))
|
||
{
|
||
const ui_application_t *application = uico_st->drv
|
||
? uico_st->drv->application
|
||
: NULL;
|
||
if (application)
|
||
application->process_events();
|
||
}
|
||
|
||
frame_count = video_st->frame_count;
|
||
is_alive = video_st->current_video
|
||
? video_st->current_video->alive(video_st->data)
|
||
: true;
|
||
is_focused = VIDEO_HAS_FOCUS(video_st);
|
||
|
||
#ifdef HAVE_MENU
|
||
if (menu_driver_binding_state)
|
||
BIT256_CLEAR_ALL(current_bits);
|
||
#endif
|
||
|
||
/* Check fullscreen toggle */
|
||
{
|
||
bool fullscreen_toggled = !runloop_paused
|
||
#ifdef HAVE_MENU
|
||
|| menu_is_alive;
|
||
#else
|
||
;
|
||
#endif
|
||
HOTKEY_CHECK(RARCH_FULLSCREEN_TOGGLE_KEY, CMD_EVENT_FULLSCREEN_TOGGLE,
|
||
fullscreen_toggled, NULL);
|
||
}
|
||
|
||
/* Check mouse grab toggle */
|
||
HOTKEY_CHECK(RARCH_GRAB_MOUSE_TOGGLE, CMD_EVENT_GRAB_MOUSE_TOGGLE, true, NULL);
|
||
|
||
/* Automatic mouse grab on focus */
|
||
if (settings->bools.input_auto_mouse_grab &&
|
||
is_focused &&
|
||
is_focused != runloop_st->focused &&
|
||
!input_st->grab_mouse_state)
|
||
command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
|
||
runloop_st->focused = is_focused;
|
||
|
||
#ifdef HAVE_OVERLAY
|
||
if (settings->bools.input_overlay_enable)
|
||
{
|
||
static char prev_overlay_restore = false;
|
||
static unsigned last_width = 0;
|
||
static unsigned last_height = 0;
|
||
unsigned video_driver_width = video_st->width;
|
||
unsigned video_driver_height = video_st->height;
|
||
bool check_next_rotation = true;
|
||
bool input_overlay_hide_in_menu = settings->bools.input_overlay_hide_in_menu;
|
||
bool input_overlay_hide_when_gamepad_connected = settings->bools.input_overlay_hide_when_gamepad_connected;
|
||
bool input_overlay_auto_rotate = settings->bools.input_overlay_auto_rotate;
|
||
|
||
/* Check whether overlay should be hidden
|
||
* when a gamepad is connected */
|
||
if (input_overlay_hide_when_gamepad_connected)
|
||
{
|
||
static bool last_controller_connected = false;
|
||
bool controller_connected = (input_config_get_device_name(0) != NULL);
|
||
|
||
if (controller_connected != last_controller_connected)
|
||
{
|
||
if (controller_connected)
|
||
input_overlay_deinit();
|
||
else
|
||
input_overlay_init();
|
||
|
||
last_controller_connected = controller_connected;
|
||
}
|
||
}
|
||
|
||
/* Check next overlay */
|
||
HOTKEY_CHECK(RARCH_OVERLAY_NEXT, CMD_EVENT_OVERLAY_NEXT, true, &check_next_rotation);
|
||
|
||
/* Ensure overlay is restored after displaying osk */
|
||
if (input_st->keyboard_linefeed_enable)
|
||
prev_overlay_restore = true;
|
||
else if (prev_overlay_restore)
|
||
{
|
||
if (!input_overlay_hide_in_menu)
|
||
input_overlay_init();
|
||
prev_overlay_restore = false;
|
||
}
|
||
|
||
/* Check whether video aspect has changed */
|
||
if ((video_driver_width != last_width) ||
|
||
(video_driver_height != last_height))
|
||
{
|
||
/* Update scaling/offset factors */
|
||
command_event(CMD_EVENT_OVERLAY_SET_SCALE_FACTOR, NULL);
|
||
|
||
/* Check overlay rotation, if required */
|
||
if (input_overlay_auto_rotate)
|
||
input_overlay_auto_rotate_(
|
||
video_st->width,
|
||
video_st->height,
|
||
settings->bools.input_overlay_enable,
|
||
input_st->overlay_ptr);
|
||
|
||
last_width = video_driver_width;
|
||
last_height = video_driver_height;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/*
|
||
* If the Aspect Ratio is FULL then update the aspect ratio to the
|
||
* current video driver aspect ratio (The full window)
|
||
*
|
||
* TODO/FIXME
|
||
* Should possibly be refactored to have last width & driver width & height
|
||
* only be done once when we are using an overlay OR using aspect ratio
|
||
* full
|
||
*/
|
||
if (settings->uints.video_aspect_ratio_idx == ASPECT_RATIO_FULL)
|
||
{
|
||
static unsigned last_width = 0;
|
||
static unsigned last_height = 0;
|
||
unsigned video_driver_width = video_st->width;
|
||
unsigned video_driver_height = video_st->height;
|
||
|
||
/* Check whether video aspect has changed */
|
||
if ((video_driver_width != last_width) ||
|
||
(video_driver_height != last_height))
|
||
{
|
||
/* Update set aspect ratio so the full matches the current video width & height */
|
||
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
|
||
|
||
last_width = video_driver_width;
|
||
last_height = video_driver_height;
|
||
}
|
||
}
|
||
|
||
/* Check quit key */
|
||
{
|
||
bool trig_quit_key, quit_press_twice;
|
||
static bool quit_key = false;
|
||
static bool old_quit_key = false;
|
||
static bool runloop_exec = false;
|
||
quit_key = BIT256_GET(
|
||
current_bits, RARCH_QUIT_KEY);
|
||
trig_quit_key = quit_key && !old_quit_key;
|
||
/* Check for quit gamepad combo */
|
||
if (!trig_quit_key &&
|
||
((quit_gamepad_combo != INPUT_COMBO_NONE) &&
|
||
input_driver_button_combo(
|
||
quit_gamepad_combo,
|
||
current_time,
|
||
¤t_bits)))
|
||
trig_quit_key = true;
|
||
old_quit_key = quit_key;
|
||
quit_press_twice = settings->bools.quit_press_twice;
|
||
|
||
/* Check double press if enabled */
|
||
if (trig_quit_key && quit_press_twice)
|
||
{
|
||
static retro_time_t quit_key_time = 0;
|
||
retro_time_t cur_time = current_time;
|
||
trig_quit_key = (cur_time - quit_key_time < QUIT_DELAY_USEC);
|
||
quit_key_time = cur_time;
|
||
|
||
if (!trig_quit_key)
|
||
{
|
||
float target_hz = 0.0;
|
||
|
||
runloop_environment_cb(
|
||
RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE, &target_hz);
|
||
|
||
runloop_msg_queue_push(msg_hash_to_str(MSG_PRESS_AGAIN_TO_QUIT), 1,
|
||
QUIT_DELAY_USEC * target_hz / 1000000,
|
||
true, NULL,
|
||
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
}
|
||
|
||
if (RUNLOOP_TIME_TO_EXIT(trig_quit_key))
|
||
{
|
||
bool quit_runloop = false;
|
||
#ifdef HAVE_SCREENSHOTS
|
||
unsigned runloop_max_frames = runloop_st->max_frames;
|
||
|
||
if ((runloop_max_frames != 0)
|
||
&& (frame_count >= runloop_max_frames)
|
||
&& runloop_st->max_frames_screenshot)
|
||
{
|
||
const char *screenshot_path = NULL;
|
||
bool fullpath = false;
|
||
|
||
if (string_is_empty(runloop_st->max_frames_screenshot_path))
|
||
screenshot_path = path_get(RARCH_PATH_BASENAME);
|
||
else
|
||
{
|
||
fullpath = true;
|
||
screenshot_path = runloop_st->max_frames_screenshot_path;
|
||
}
|
||
|
||
RARCH_LOG("Taking a screenshot before exiting...\n");
|
||
|
||
/* Take a screenshot before we exit. */
|
||
if (!take_screenshot(settings->paths.directory_screenshot,
|
||
screenshot_path, false,
|
||
video_driver_cached_frame_has_valid_framebuffer(), fullpath, false))
|
||
{
|
||
RARCH_ERR("Could not take a screenshot before exiting.\n");
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (runloop_exec)
|
||
runloop_exec = false;
|
||
|
||
if (runloop_st->core_shutdown_initiated)
|
||
{
|
||
bool load_dummy_core = false;
|
||
|
||
runloop_st->core_shutdown_initiated = false;
|
||
|
||
/* 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->shutdown_initiated = false;
|
||
}
|
||
|
||
/* Unload current core, and load dummy if
|
||
* required */
|
||
if (!command_event(CMD_EVENT_UNLOAD_CORE, &load_dummy_core))
|
||
{
|
||
runloop_st->shutdown_initiated = true;
|
||
quit_runloop = true;
|
||
}
|
||
|
||
if (!load_dummy_core)
|
||
quit_runloop = true;
|
||
}
|
||
else
|
||
quit_runloop = true;
|
||
|
||
runloop_st->core_running = false;
|
||
|
||
if (quit_runloop)
|
||
{
|
||
old_quit_key = quit_key;
|
||
retroarch_main_quit();
|
||
return RUNLOOP_STATE_QUIT;
|
||
}
|
||
}
|
||
}
|
||
|
||
#if defined(HAVE_MENU) || defined(HAVE_GFX_WIDGETS)
|
||
gfx_animation_update(
|
||
current_time,
|
||
settings->bools.menu_timedate_enable,
|
||
settings->floats.menu_ticker_speed,
|
||
video_st->width,
|
||
video_st->height);
|
||
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (widgets_active)
|
||
{
|
||
bool rarch_force_fullscreen = video_st->force_fullscreen;
|
||
bool video_is_fullscreen = settings->bools.video_fullscreen ||
|
||
rarch_force_fullscreen;
|
||
|
||
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
|
||
gfx_widgets_iterate(
|
||
p_disp,
|
||
settings,
|
||
video_st->width,
|
||
video_st->height,
|
||
video_is_fullscreen,
|
||
settings->paths.directory_assets,
|
||
settings->paths.path_font,
|
||
VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st));
|
||
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
|
||
}
|
||
#endif
|
||
|
||
#ifdef HAVE_MENU
|
||
if (menu_is_alive)
|
||
{
|
||
enum menu_action action;
|
||
static input_bits_t old_input = {{0}};
|
||
static enum menu_action
|
||
old_action = MENU_ACTION_CANCEL;
|
||
struct menu_state *menu_st = menu_state_get_ptr();
|
||
bool focused = false;
|
||
input_bits_t trigger_input = current_bits;
|
||
unsigned screensaver_timeout = settings->uints.menu_screensaver_timeout;
|
||
|
||
/* Get current time */
|
||
menu_st->current_time_us = current_time;
|
||
|
||
cbs->poll_cb();
|
||
|
||
bits_clear_bits(trigger_input.data, old_input.data,
|
||
ARRAY_SIZE(trigger_input.data));
|
||
action = (enum menu_action)menu_event(
|
||
settings,
|
||
¤t_bits, &trigger_input, display_kb);
|
||
#ifdef HAVE_NETWORKING
|
||
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
|
||
focused = true;
|
||
else
|
||
#endif
|
||
{
|
||
if (pause_nonactive)
|
||
focused = is_focused && !uico_st->is_on_foreground;
|
||
else
|
||
focused = !uico_st->is_on_foreground;
|
||
}
|
||
|
||
if (action == old_action)
|
||
{
|
||
retro_time_t press_time = current_time;
|
||
|
||
if (action == MENU_ACTION_NOOP)
|
||
menu_st->noop_press_time = press_time - menu_st->noop_start_time;
|
||
else
|
||
menu_st->action_press_time = press_time - menu_st->action_start_time;
|
||
}
|
||
else
|
||
{
|
||
if (action == MENU_ACTION_NOOP)
|
||
{
|
||
menu_st->noop_start_time = current_time;
|
||
menu_st->noop_press_time = 0;
|
||
|
||
if (menu_st->prev_action == old_action)
|
||
menu_st->action_start_time = menu_st->prev_start_time;
|
||
else
|
||
menu_st->action_start_time = current_time;
|
||
}
|
||
else
|
||
{
|
||
if ( menu_st->prev_action == action &&
|
||
menu_st->noop_press_time < 200000) /* 250ms */
|
||
{
|
||
menu_st->action_start_time = menu_st->prev_start_time;
|
||
menu_st->action_press_time = current_time - menu_st->action_start_time;
|
||
}
|
||
else
|
||
{
|
||
menu_st->prev_start_time = current_time;
|
||
menu_st->prev_action = action;
|
||
menu_st->action_press_time = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Check whether menu screensaver should be enabled */
|
||
if ((screensaver_timeout > 0) &&
|
||
menu_st->screensaver_supported &&
|
||
!menu_st->screensaver_active &&
|
||
((menu_st->current_time_us - menu_st->input_last_time_us) >
|
||
((retro_time_t)screensaver_timeout * 1000000)))
|
||
{
|
||
menu_ctx_environment_t menu_environ;
|
||
menu_environ.type = MENU_ENVIRON_ENABLE_SCREENSAVER;
|
||
menu_environ.data = NULL;
|
||
menu_st->screensaver_active = true;
|
||
menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
|
||
}
|
||
|
||
/* Iterate the menu driver for one frame. */
|
||
|
||
/* If the user had requested that the Quick Menu
|
||
* be spawned during the previous frame, do this now
|
||
* and exit the function to go to the next frame. */
|
||
if (menu_st->pending_quick_menu)
|
||
{
|
||
menu_ctx_list_t list_info;
|
||
|
||
/* We are going to push a new menu; ensure
|
||
* that the current one is cached for animation
|
||
* purposes */
|
||
list_info.type = MENU_LIST_PLAIN;
|
||
list_info.action = 0;
|
||
menu_driver_list_cache(&list_info);
|
||
|
||
p_disp->msg_force = true;
|
||
|
||
generic_action_ok_displaylist_push("", NULL,
|
||
"", 0, 0, 0, ACTION_OK_DL_CONTENT_SETTINGS);
|
||
|
||
menu_st->selection_ptr = 0;
|
||
menu_st->pending_quick_menu = false;
|
||
}
|
||
else if (!menu_driver_iterate(
|
||
menu_st,
|
||
p_disp,
|
||
anim_get_ptr(),
|
||
settings,
|
||
action, current_time))
|
||
{
|
||
if (error_on_init)
|
||
{
|
||
content_ctx_info_t content_info = {0};
|
||
task_push_start_dummy_core(&content_info);
|
||
}
|
||
else
|
||
retroarch_menu_running_finished(false);
|
||
}
|
||
|
||
if (focused || !runloop_st->idle)
|
||
{
|
||
bool runloop_is_inited = runloop_st->is_inited;
|
||
#ifdef HAVE_NETWORKING
|
||
bool menu_pause_libretro = settings->bools.menu_pause_libretro &&
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
|
||
#else
|
||
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
|
||
#endif
|
||
bool libretro_running = !menu_pause_libretro
|
||
&& runloop_is_inited
|
||
&& (runloop_st->current_core_type != CORE_TYPE_DUMMY);
|
||
|
||
if (menu)
|
||
{
|
||
if (BIT64_GET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER)
|
||
!= BIT64_GET(menu->state, MENU_STATE_RENDER_MESSAGEBOX))
|
||
BIT64_SET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER);
|
||
|
||
if (BIT64_GET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER))
|
||
p_disp->framebuf_dirty = true;
|
||
|
||
if (BIT64_GET(menu->state, MENU_STATE_RENDER_MESSAGEBOX)
|
||
&& !string_is_empty(menu->menu_state_msg))
|
||
{
|
||
if (menu->driver_ctx->render_messagebox)
|
||
menu->driver_ctx->render_messagebox(
|
||
menu->userdata,
|
||
menu->menu_state_msg);
|
||
|
||
if (uico_st->is_on_foreground)
|
||
{
|
||
if ( uico_st->drv
|
||
&& uico_st->drv->render_messagebox)
|
||
uico_st->drv->render_messagebox(menu->menu_state_msg);
|
||
}
|
||
}
|
||
|
||
if (BIT64_GET(menu->state, MENU_STATE_BLIT))
|
||
{
|
||
if (menu->driver_ctx->render)
|
||
menu->driver_ctx->render(
|
||
menu->userdata,
|
||
video_st->width,
|
||
video_st->height,
|
||
runloop_st->idle);
|
||
}
|
||
|
||
if (menu_st->alive && !runloop_st->idle)
|
||
if (display_menu_libretro(runloop_st, input_st,
|
||
settings->floats.slowmotion_ratio,
|
||
libretro_running, current_time))
|
||
video_driver_cached_frame();
|
||
|
||
if (menu->driver_ctx->set_texture)
|
||
menu->driver_ctx->set_texture(menu->userdata);
|
||
|
||
menu->state = 0;
|
||
}
|
||
|
||
if (settings->bools.audio_enable_menu &&
|
||
!libretro_running)
|
||
audio_driver_menu_sample();
|
||
}
|
||
|
||
old_input = current_bits;
|
||
old_action = action;
|
||
|
||
if (!focused || runloop_st->idle)
|
||
return RUNLOOP_STATE_POLLED_AND_SLEEP;
|
||
}
|
||
else
|
||
#endif
|
||
#endif
|
||
{
|
||
if (runloop_st->idle)
|
||
{
|
||
cbs->poll_cb();
|
||
return RUNLOOP_STATE_POLLED_AND_SLEEP;
|
||
}
|
||
}
|
||
|
||
/* Check game focus toggle */
|
||
{
|
||
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 if we have pressed the UI companion toggle button */
|
||
HOTKEY_CHECK(RARCH_UI_COMPANION_TOGGLE, CMD_EVENT_UI_COMPANION_TOGGLE, true, NULL);
|
||
/* Check close content key */
|
||
HOTKEY_CHECK(RARCH_CLOSE_CONTENT_KEY, CMD_EVENT_CLOSE_CONTENT, true, NULL);
|
||
|
||
#ifdef HAVE_MENU
|
||
/* Check if we have pressed the menu toggle button */
|
||
{
|
||
static bool old_pressed = false;
|
||
char *menu_driver = settings->arrays.menu_driver;
|
||
bool pressed = BIT256_GET(
|
||
current_bits, RARCH_MENU_TOGGLE) &&
|
||
!string_is_equal(menu_driver, "null");
|
||
bool core_type_is_dummy = runloop_st->current_core_type == CORE_TYPE_DUMMY;
|
||
|
||
if (menu_st->kb_key_state[RETROK_F1] == 1)
|
||
{
|
||
if (menu_st->alive)
|
||
{
|
||
if (rarch_is_initialized && !core_type_is_dummy)
|
||
{
|
||
retroarch_menu_running_finished(false);
|
||
menu_st->kb_key_state[RETROK_F1] =
|
||
((menu_st->kb_key_state[RETROK_F1] & 1) << 1) | false;
|
||
}
|
||
}
|
||
}
|
||
else if ((!menu_st->kb_key_state[RETROK_F1] &&
|
||
(pressed && !old_pressed)) ||
|
||
core_type_is_dummy)
|
||
{
|
||
if (menu_st->alive)
|
||
{
|
||
if (rarch_is_initialized && !core_type_is_dummy)
|
||
retroarch_menu_running_finished(false);
|
||
}
|
||
else
|
||
retroarch_menu_running();
|
||
}
|
||
else
|
||
menu_st->kb_key_state[RETROK_F1] =
|
||
((menu_st->kb_key_state[RETROK_F1] & 1) << 1) | false;
|
||
|
||
old_pressed = pressed;
|
||
}
|
||
|
||
/* Check if we have pressed the FPS toggle button */
|
||
HOTKEY_CHECK(RARCH_FPS_TOGGLE, CMD_EVENT_FPS_TOGGLE, true, NULL);
|
||
|
||
HOTKEY_CHECK(RARCH_STATISTICS_TOGGLE, CMD_EVENT_STATISTICS_TOGGLE, true, NULL);
|
||
|
||
/* Check if we have pressed the netplay host toggle button */
|
||
HOTKEY_CHECK(RARCH_NETPLAY_HOST_TOGGLE, CMD_EVENT_NETPLAY_HOST_TOGGLE, true, NULL);
|
||
|
||
if (menu_st->alive)
|
||
{
|
||
float fastforward_ratio = runloop_get_fastforward_ratio(settings,
|
||
&runloop_st->fastmotion_override.current);
|
||
|
||
if (!settings->bools.menu_throttle_framerate && !fastforward_ratio)
|
||
return RUNLOOP_STATE_MENU_ITERATE;
|
||
|
||
return RUNLOOP_STATE_END;
|
||
}
|
||
#endif
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
|
||
#endif
|
||
if (pause_nonactive)
|
||
focused = is_focused;
|
||
|
||
#ifdef HAVE_SCREENSHOTS
|
||
/* Check if we have pressed the screenshot toggle button */
|
||
HOTKEY_CHECK(RARCH_SCREENSHOT, CMD_EVENT_TAKE_SCREENSHOT, true, NULL);
|
||
#endif
|
||
|
||
/* Check if we have pressed the audio mute toggle button */
|
||
HOTKEY_CHECK(RARCH_MUTE, CMD_EVENT_AUDIO_MUTE_TOGGLE, true, NULL);
|
||
|
||
/* Check if we have pressed the OSK toggle button */
|
||
HOTKEY_CHECK(RARCH_OSK, CMD_EVENT_OSK_TOGGLE, true, NULL);
|
||
|
||
/* Check if we have pressed the recording toggle button */
|
||
HOTKEY_CHECK(RARCH_RECORDING_TOGGLE, CMD_EVENT_RECORDING_TOGGLE, true, NULL);
|
||
|
||
/* Check if we have pressed the streaming toggle button */
|
||
HOTKEY_CHECK(RARCH_STREAMING_TOGGLE, CMD_EVENT_STREAMING_TOGGLE, true, NULL);
|
||
|
||
/* Check if we have pressed the Run-Ahead toggle button */
|
||
HOTKEY_CHECK(RARCH_RUNAHEAD_TOGGLE, CMD_EVENT_RUNAHEAD_TOGGLE, true, NULL);
|
||
|
||
/* Check if we have pressed the AI Service toggle button */
|
||
HOTKEY_CHECK(RARCH_AI_SERVICE, CMD_EVENT_AI_SERVICE_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;
|
||
}
|
||
}
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
/* Check Netplay */
|
||
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
|
||
|
||
/* Check if we have pressed the pause button */
|
||
{
|
||
static bool old_frameadvance = false;
|
||
static bool old_pause_pressed = false;
|
||
bool frameadvance_pressed = false;
|
||
bool trig_frameadvance = false;
|
||
bool pause_pressed = BIT256_GET(current_bits, RARCH_PAUSE_TOGGLE);
|
||
#ifdef HAVE_CHEEVOS
|
||
/* make sure not to evaluate this before calling menu_driver_iterate
|
||
* as that may change its value */
|
||
cheevos_hardcore_active = rcheevos_hardcore_active();
|
||
|
||
if (cheevos_hardcore_active)
|
||
{
|
||
static int unpaused_frames = 0;
|
||
|
||
/* Frame advance is not allowed when achievement hardcore is active */
|
||
if (!runloop_st->paused)
|
||
{
|
||
/* Limit pause to approximately three times per second (depending on core framerate) */
|
||
if (unpaused_frames < 20)
|
||
{
|
||
++unpaused_frames;
|
||
pause_pressed = false;
|
||
}
|
||
}
|
||
else
|
||
unpaused_frames = 0;
|
||
}
|
||
else
|
||
#endif
|
||
{
|
||
frameadvance_pressed = BIT256_GET(current_bits, RARCH_FRAMEADVANCE);
|
||
trig_frameadvance = frameadvance_pressed && !old_frameadvance;
|
||
|
||
/* FRAMEADVANCE will set us into pause mode. */
|
||
pause_pressed |= !runloop_st->paused
|
||
&& trig_frameadvance;
|
||
}
|
||
|
||
/* Check if libretro pause key was pressed. If so, pause or
|
||
* unpause the libretro core. */
|
||
|
||
if (focused)
|
||
{
|
||
if (pause_pressed && !old_pause_pressed)
|
||
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);
|
||
|
||
old_focus = focused;
|
||
old_pause_pressed = pause_pressed;
|
||
old_frameadvance = frameadvance_pressed;
|
||
|
||
if (runloop_st->paused)
|
||
{
|
||
bool toggle = !runloop_st->idle ? true : false;
|
||
|
||
HOTKEY_CHECK(RARCH_FULLSCREEN_TOGGLE_KEY,
|
||
CMD_EVENT_FULLSCREEN_TOGGLE, true, &toggle);
|
||
|
||
/* Check if it's not oneshot */
|
||
#ifdef HAVE_REWIND
|
||
if (!(trig_frameadvance || BIT256_GET(current_bits, RARCH_REWIND)))
|
||
#else
|
||
if (!trig_frameadvance)
|
||
#endif
|
||
focused = false;
|
||
}
|
||
}
|
||
|
||
#ifdef HAVE_ACCESSIBILITY
|
||
#ifdef HAVE_TRANSLATE
|
||
/* Copy over the retropad state to a buffer for the translate service
|
||
to send off if it's run. */
|
||
if (settings->bools.ai_service_enable)
|
||
{
|
||
input_st->ai_gamepad_state[0] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_B);
|
||
input_st->ai_gamepad_state[1] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_Y);
|
||
input_st->ai_gamepad_state[2] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_SELECT);
|
||
input_st->ai_gamepad_state[3] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_START);
|
||
|
||
input_st->ai_gamepad_state[4] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_UP);
|
||
input_st->ai_gamepad_state[5] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_DOWN);
|
||
input_st->ai_gamepad_state[6] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_LEFT);
|
||
input_st->ai_gamepad_state[7] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_RIGHT);
|
||
|
||
input_st->ai_gamepad_state[8] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_A);
|
||
input_st->ai_gamepad_state[9] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_X);
|
||
input_st->ai_gamepad_state[10] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L);
|
||
input_st->ai_gamepad_state[11] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R);
|
||
|
||
input_st->ai_gamepad_state[12] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L2);
|
||
input_st->ai_gamepad_state[13] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R2);
|
||
input_st->ai_gamepad_state[14] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L3);
|
||
input_st->ai_gamepad_state[15] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R3);
|
||
}
|
||
#endif
|
||
#endif
|
||
|
||
if (!focused)
|
||
{
|
||
cbs->poll_cb();
|
||
return RUNLOOP_STATE_POLLED_AND_SLEEP;
|
||
}
|
||
|
||
/* Apply any pending fastmotion override
|
||
* parameters */
|
||
if (runloop_st->fastmotion_override.pending)
|
||
{
|
||
runloop_apply_fastmotion_override(runloop_st, settings);
|
||
runloop_st->fastmotion_override.pending = false;
|
||
}
|
||
|
||
/* Check if we have pressed the fast forward button */
|
||
/* 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;
|
||
|
||
if (check2)
|
||
{
|
||
if (input_st->nonblocking_flag)
|
||
{
|
||
input_st->nonblocking_flag = false;
|
||
runloop_st->fastmotion = false;
|
||
runloop_st->fastforward_after_frames = 1;
|
||
}
|
||
else
|
||
{
|
||
input_st->nonblocking_flag = true;
|
||
runloop_st->fastmotion = true;
|
||
}
|
||
|
||
driver_set_nonblock_state();
|
||
|
||
/* Reset frame time counter when toggling
|
||
* fast-forward off, if required */
|
||
if (!runloop_st->fastmotion &&
|
||
settings->bools.frame_time_counter_reset_after_fastforwarding)
|
||
video_st->frame_time_count = 0;
|
||
}
|
||
|
||
old_button_state = new_button_state;
|
||
old_hold_button_state = new_hold_button_state;
|
||
}
|
||
|
||
/* Display fast-forward notification, unless
|
||
* disabled via override */
|
||
if (!runloop_st->fastmotion_override.current.fastforward ||
|
||
runloop_st->fastmotion_override.current.notification)
|
||
{
|
||
/* > Use widgets, if enabled */
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (widgets_active)
|
||
video_st->widgets_fast_forward =
|
||
settings->bools.notification_show_fast_forward ?
|
||
runloop_st->fastmotion : false;
|
||
else
|
||
#endif
|
||
{
|
||
/* > If widgets are disabled, display fast-forward
|
||
* status via OSD text for 1 frame every frame */
|
||
if (runloop_st->fastmotion &&
|
||
settings->bools.notification_show_fast_forward)
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_FAST_FORWARD), 1, 1, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
}
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
else
|
||
video_st->widgets_fast_forward = false;
|
||
#endif
|
||
|
||
/* Check if we have pressed any of the state slot buttons */
|
||
{
|
||
static bool old_should_slot_increase = false;
|
||
static bool old_should_slot_decrease = false;
|
||
bool should_slot_increase = BIT256_GET(
|
||
current_bits, RARCH_STATE_SLOT_PLUS);
|
||
bool should_slot_decrease = BIT256_GET(
|
||
current_bits, RARCH_STATE_SLOT_MINUS);
|
||
bool check1 = true;
|
||
bool check2 = should_slot_increase && !old_should_slot_increase;
|
||
int addition = 1;
|
||
int state_slot = settings->ints.state_slot;
|
||
|
||
if (!check2)
|
||
{
|
||
check2 = should_slot_decrease && !old_should_slot_decrease;
|
||
check1 = state_slot > 0;
|
||
addition = -1;
|
||
}
|
||
|
||
/* Checks if the state increase/decrease keys have been pressed
|
||
* for this frame. */
|
||
if (check2)
|
||
{
|
||
char msg[128];
|
||
int cur_state_slot = state_slot;
|
||
if (check1)
|
||
configuration_set_int(settings, settings->ints.state_slot,
|
||
cur_state_slot + addition);
|
||
msg[0] = '\0';
|
||
snprintf(msg, sizeof(msg), "%s: %d",
|
||
msg_hash_to_str(MSG_STATE_SLOT),
|
||
settings->ints.state_slot);
|
||
runloop_msg_queue_push(msg, 2, 180, true, NULL,
|
||
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
RARCH_LOG("[State]: %s\n", msg);
|
||
}
|
||
|
||
old_should_slot_increase = should_slot_increase;
|
||
old_should_slot_decrease = should_slot_decrease;
|
||
}
|
||
|
||
/* Check if we have pressed any of the savestate buttons */
|
||
HOTKEY_CHECK(RARCH_SAVE_STATE_KEY, CMD_EVENT_SAVE_STATE, true, NULL);
|
||
HOTKEY_CHECK(RARCH_LOAD_STATE_KEY, CMD_EVENT_LOAD_STATE, true, NULL);
|
||
|
||
#ifdef HAVE_CHEEVOS
|
||
if (!cheevos_hardcore_active)
|
||
#endif
|
||
{
|
||
/* Check if rewind toggle was being held. */
|
||
{
|
||
#ifdef HAVE_REWIND
|
||
char s[128];
|
||
bool rewinding = false;
|
||
unsigned t = 0;
|
||
|
||
s[0] = '\0';
|
||
|
||
rewinding = state_manager_check_rewind(
|
||
&runloop_st->rewind_st,
|
||
&runloop_st->current_core,
|
||
BIT256_GET(current_bits, RARCH_REWIND),
|
||
settings->uints.rewind_granularity,
|
||
runloop_st->paused,
|
||
s, sizeof(s), &t);
|
||
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
if (widgets_active)
|
||
video_st->widgets_rewinding = rewinding;
|
||
else
|
||
#endif
|
||
{
|
||
if (rewinding)
|
||
runloop_msg_queue_push(s, 0, t, true, NULL,
|
||
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
{
|
||
/* Checks if slowmotion toggle/hold was being pressed and/or held. */
|
||
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);
|
||
|
||
if (new_slowmotion_button_state && !old_slowmotion_button_state)
|
||
runloop_st->slowmotion = !runloop_st->slowmotion;
|
||
else if (old_slowmotion_hold_button_state != new_slowmotion_hold_button_state)
|
||
runloop_st->slowmotion = new_slowmotion_hold_button_state;
|
||
|
||
if (runloop_st->slowmotion)
|
||
{
|
||
if (settings->uints.video_black_frame_insertion)
|
||
if (!runloop_st->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->frame_is_reversed)
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_SLOW_MOTION_REWIND), 1, 1, false, NULL,
|
||
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
else
|
||
#endif
|
||
runloop_msg_queue_push(
|
||
msg_hash_to_str(MSG_SLOW_MOTION), 1, 1, false,
|
||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
}
|
||
|
||
old_slowmotion_button_state = new_slowmotion_button_state;
|
||
old_slowmotion_hold_button_state = new_slowmotion_hold_button_state;
|
||
}
|
||
}
|
||
|
||
HOTKEY_CHECK(RARCH_VRR_RUNLOOP_TOGGLE, CMD_EVENT_VRR_RUNLOOP_TOGGLE, true, NULL);
|
||
|
||
/* Check movie record toggle */
|
||
HOTKEY_CHECK(RARCH_BSV_RECORD_TOGGLE, CMD_EVENT_BSV_RECORDING_TOGGLE, true, NULL);
|
||
|
||
/* Check shader prev/next */
|
||
HOTKEY_CHECK(RARCH_SHADER_NEXT, CMD_EVENT_SHADER_NEXT, true, NULL);
|
||
HOTKEY_CHECK(RARCH_SHADER_PREV, CMD_EVENT_SHADER_PREV, true, NULL);
|
||
|
||
/* Check if we have pressed any of the disk buttons */
|
||
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 if we have pressed the reset button */
|
||
HOTKEY_CHECK(RARCH_RESET, CMD_EVENT_RESET, true, NULL);
|
||
|
||
/* Check cheats */
|
||
HOTKEY_CHECK3(
|
||
RARCH_CHEAT_INDEX_PLUS, CMD_EVENT_CHEAT_INDEX_PLUS,
|
||
RARCH_CHEAT_INDEX_MINUS, CMD_EVENT_CHEAT_INDEX_MINUS,
|
||
RARCH_CHEAT_TOGGLE, CMD_EVENT_CHEAT_TOGGLE);
|
||
|
||
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
|
||
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 = retroarch_get_shader_preset();
|
||
enum rarch_shader_type type = video_shader_parse_type(preset);
|
||
apply_shader(settings, type, preset, false);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#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.
|
||
**/
|
||
int runloop_iterate(void)
|
||
{
|
||
int i;
|
||
enum analog_dpad_mode dpad_mode[MAX_USERS];
|
||
input_driver_state_t *input_st = input_state_get_ptr();
|
||
audio_driver_state_t *audio_st = audio_state_get_ptr();
|
||
video_driver_state_t *video_st = video_state_get_ptr();
|
||
recording_state_t *recording_st = recording_state_get_ptr();
|
||
camera_driver_state_t *camera_st = camera_state_get_ptr();
|
||
#if defined(HAVE_COCOATOUCH)
|
||
uico_driver_state_t *uico_st = uico_state_get_ptr();
|
||
#endif
|
||
settings_t *settings = config_get_ptr();
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
unsigned video_frame_delay = settings->uints.video_frame_delay;
|
||
unsigned video_frame_delay_effective = video_st->frame_delay_effective;
|
||
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
|
||
unsigned max_users = settings->uints.input_max_users;
|
||
retro_time_t current_time = cpu_features_get_time_usec();
|
||
#ifdef HAVE_MENU
|
||
#ifdef HAVE_NETWORKING
|
||
bool menu_pause_libretro = settings->bools.menu_pause_libretro &&
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
|
||
#else
|
||
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
|
||
#endif
|
||
bool core_paused = runloop_st->paused || (menu_pause_libretro && menu_state_get_ptr()->alive);
|
||
#else
|
||
bool core_paused = runloop_st->paused;
|
||
#endif
|
||
float slowmotion_ratio = settings->floats.slowmotion_ratio;
|
||
#ifdef HAVE_CHEEVOS
|
||
bool cheevos_enable = settings->bools.cheevos_enable;
|
||
#endif
|
||
bool audio_sync = settings->bools.audio_sync;
|
||
#ifdef HAVE_DISCORD
|
||
discord_state_t *discord_st = discord_state_get_ptr();
|
||
|
||
if (discord_st->inited)
|
||
{
|
||
Discord_RunCallbacks();
|
||
#ifdef DISCORD_DISABLE_IO_THREAD
|
||
Discord_UpdateConnection();
|
||
#endif
|
||
}
|
||
#endif
|
||
|
||
if (runloop_st->frame_time.callback)
|
||
{
|
||
/* Updates frame timing if frame timing callback is in use by the core.
|
||
* Limits frame time if fast forward ratio throttle is enabled. */
|
||
retro_usec_t runloop_last_frame_time = runloop_st->frame_time_last;
|
||
retro_time_t current = current_time;
|
||
bool is_locked_fps = (runloop_st->paused
|
||
|| input_st->nonblocking_flag)
|
||
| !!recording_st->data;
|
||
retro_time_t delta = (!runloop_last_frame_time || is_locked_fps)
|
||
? runloop_st->frame_time.reference
|
||
: (current - runloop_last_frame_time);
|
||
|
||
if (is_locked_fps)
|
||
runloop_st->frame_time_last = 0;
|
||
else
|
||
{
|
||
runloop_st->frame_time_last = current;
|
||
|
||
if (runloop_st->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->paused ||
|
||
!audio_st->active ||
|
||
!audio_st->output_samples_buf) &&
|
||
audio_st->current_audio->write_avail &&
|
||
audio_st->context_audio_data &&
|
||
audio_st->buffer_size)
|
||
{
|
||
size_t audio_buf_avail;
|
||
|
||
if ((audio_buf_avail = audio_st->current_audio->write_avail(
|
||
audio_st->context_audio_data)) > audio_st->buffer_size)
|
||
audio_buf_avail = audio_st->buffer_size;
|
||
|
||
audio_buf_occupancy = (unsigned)(100 - (audio_buf_avail * 100) /
|
||
audio_st->buffer_size);
|
||
|
||
/* Elsewhere, we standardise on a 'low water mark'
|
||
* of 25% of the total audio buffer size - use
|
||
* the same metric here (can be made more sophisticated
|
||
* if required - i.e. determine buffer occupancy in
|
||
* terms of usec, and weigh this against the expected
|
||
* frame time) */
|
||
audio_buf_underrun = audio_buf_occupancy < 25;
|
||
|
||
audio_buf_active = true;
|
||
}
|
||
|
||
if (!core_paused)
|
||
runloop_st->audio_buffer_status.callback(
|
||
audio_buf_active, audio_buf_occupancy, audio_buf_underrun);
|
||
}
|
||
|
||
switch ((enum runloop_state_enum)runloop_check_state(
|
||
global_get_ptr()->error_on_init,
|
||
settings, current_time))
|
||
{
|
||
case RUNLOOP_STATE_QUIT:
|
||
runloop_st->frame_limit_last_time = 0.0;
|
||
runloop_st->core_running = false;
|
||
command_event(CMD_EVENT_QUIT, NULL);
|
||
return -1;
|
||
case RUNLOOP_STATE_POLLED_AND_SLEEP:
|
||
#ifdef HAVE_NETWORKING
|
||
/* FIXME: This is an ugly way to tell Netplay this... */
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
|
||
#endif
|
||
#if defined(HAVE_COCOATOUCH)
|
||
if (!uico_st->is_on_foreground)
|
||
#endif
|
||
retro_sleep(10);
|
||
return 1;
|
||
case RUNLOOP_STATE_END:
|
||
#ifdef HAVE_NETWORKING
|
||
#ifdef HAVE_MENU
|
||
/* FIXME: This is an ugly way to tell Netplay this... */
|
||
if (menu_pause_libretro &&
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)
|
||
)
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
|
||
#endif
|
||
#endif
|
||
goto end;
|
||
case RUNLOOP_STATE_MENU_ITERATE:
|
||
#ifdef HAVE_NETWORKING
|
||
/* FIXME: This is an ugly way to tell Netplay this... */
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
|
||
#endif
|
||
return 0;
|
||
case RUNLOOP_STATE_ITERATE:
|
||
runloop_st->core_running = true;
|
||
break;
|
||
}
|
||
|
||
#ifdef HAVE_THREADS
|
||
if (runloop_st->autosave)
|
||
autosave_lock();
|
||
#endif
|
||
|
||
#ifdef HAVE_BSV_MOVIE
|
||
/* Used for rewinding while playback/record. */
|
||
if (input_st->bsv_movie_state_handle)
|
||
input_st->bsv_movie_state_handle->frame_pos[input_st->bsv_movie_state_handle->frame_ptr]
|
||
= intfstream_tell(input_st->bsv_movie_state_handle->file);
|
||
#endif
|
||
|
||
if ( camera_st->cb.caps
|
||
&& camera_st->driver
|
||
&& camera_st->driver->poll
|
||
&& camera_st->data)
|
||
camera_st->driver->poll(camera_st->data,
|
||
camera_st->cb.frame_raw_framebuffer,
|
||
camera_st->cb.frame_opengl_texture);
|
||
|
||
/* Update binds for analog dpad modes. */
|
||
for (i = 0; i < (int)max_users; i++)
|
||
{
|
||
dpad_mode[i] = (enum analog_dpad_mode)
|
||
settings->uints.input_analog_dpad_mode[i];
|
||
|
||
switch (dpad_mode[i])
|
||
{
|
||
case ANALOG_DPAD_LSTICK:
|
||
case ANALOG_DPAD_RSTICK:
|
||
{
|
||
unsigned mapped_port = settings->uints.input_remap_ports[i];
|
||
if (input_st->analog_requested[mapped_port])
|
||
dpad_mode[i] = ANALOG_DPAD_NONE;
|
||
}
|
||
break;
|
||
case ANALOG_DPAD_LSTICK_FORCED:
|
||
dpad_mode[i] = ANALOG_DPAD_LSTICK;
|
||
break;
|
||
case ANALOG_DPAD_RSTICK_FORCED:
|
||
dpad_mode[i] = ANALOG_DPAD_RSTICK;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Push analog to D-Pad mappings to binds. */
|
||
if (dpad_mode[i] != ANALOG_DPAD_NONE)
|
||
{
|
||
unsigned k;
|
||
unsigned joy_idx = settings->uints.input_joypad_index[i];
|
||
struct retro_keybind *general_binds = input_config_binds[joy_idx];
|
||
struct retro_keybind *auto_binds = input_autoconf_binds[joy_idx];
|
||
unsigned x_plus = RARCH_ANALOG_RIGHT_X_PLUS;
|
||
unsigned y_plus = RARCH_ANALOG_RIGHT_Y_PLUS;
|
||
unsigned x_minus = RARCH_ANALOG_RIGHT_X_MINUS;
|
||
unsigned y_minus = RARCH_ANALOG_RIGHT_Y_MINUS;
|
||
|
||
if (dpad_mode[i] == ANALOG_DPAD_LSTICK)
|
||
{
|
||
x_plus = RARCH_ANALOG_LEFT_X_PLUS;
|
||
y_plus = RARCH_ANALOG_LEFT_Y_PLUS;
|
||
x_minus = RARCH_ANALOG_LEFT_X_MINUS;
|
||
y_minus = RARCH_ANALOG_LEFT_Y_MINUS;
|
||
}
|
||
|
||
for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
|
||
{
|
||
(auto_binds)[k].orig_joyaxis = (auto_binds)[k].joyaxis;
|
||
(general_binds)[k].orig_joyaxis = (general_binds)[k].joyaxis;
|
||
}
|
||
|
||
if (!INHERIT_JOYAXIS(auto_binds))
|
||
{
|
||
unsigned j = x_plus + 3;
|
||
/* Inherit joyaxis from analogs. */
|
||
for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
|
||
(auto_binds)[k].joyaxis = (auto_binds)[j--].joyaxis;
|
||
}
|
||
|
||
if (!INHERIT_JOYAXIS(general_binds))
|
||
{
|
||
unsigned j = x_plus + 3;
|
||
/* Inherit joyaxis from analogs. */
|
||
for (k = RETRO_DEVICE_ID_JOYPAD_UP; k <= RETRO_DEVICE_ID_JOYPAD_RIGHT; k++)
|
||
(general_binds)[k].joyaxis = (general_binds)[j--].joyaxis;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!input_st->nonblocking_flag)
|
||
{
|
||
if (settings->bools.video_frame_delay_auto)
|
||
{
|
||
static bool slowmotion_prev = false;
|
||
static unsigned skip_frames = 0;
|
||
float refresh_rate = settings->floats.video_refresh_rate;
|
||
unsigned video_swap_interval = runloop_get_video_swap_interval(
|
||
settings->uints.video_swap_interval);
|
||
unsigned video_bfi = settings->uints.video_black_frame_insertion;
|
||
unsigned frame_time_interval = 8;
|
||
bool frame_time_update =
|
||
/* Skip some starting frames for stabilization */
|
||
video_st->frame_count > frame_time_interval &&
|
||
video_st->frame_count % frame_time_interval == 0;
|
||
|
||
/* A few frames need to get ignored after slowmotion is disabled */
|
||
if (!runloop_st->slowmotion && slowmotion_prev)
|
||
skip_frames = frame_time_interval * 2;
|
||
|
||
if (skip_frames)
|
||
skip_frames--;
|
||
|
||
slowmotion_prev = runloop_st->slowmotion;
|
||
/* Always skip when slowmotion is active */
|
||
if (slowmotion_prev)
|
||
skip_frames = 1;
|
||
|
||
if (skip_frames)
|
||
frame_time_update = false;
|
||
|
||
/* Black frame insertion + swap interval multiplier */
|
||
refresh_rate = (refresh_rate / (video_bfi + 1.0f) / video_swap_interval);
|
||
|
||
/* Set target moderately as half frame time with 0 delay */
|
||
if (video_frame_delay == 0)
|
||
video_frame_delay = 1 / refresh_rate * 1000 / 2;
|
||
|
||
if (video_st->frame_delay_target != video_frame_delay)
|
||
{
|
||
video_st->frame_delay_target = video_frame_delay_effective = video_frame_delay;
|
||
RARCH_LOG("[Video]: Frame delay reset to %d ms.\n", video_frame_delay);
|
||
}
|
||
|
||
if (video_frame_delay_effective > 0 && frame_time_update)
|
||
{
|
||
video_frame_delay_auto_t vfda = {0};
|
||
vfda.frame_time_interval = frame_time_interval;
|
||
vfda.refresh_rate = refresh_rate;
|
||
|
||
video_frame_delay_auto(video_st, &vfda);
|
||
if (vfda.decrease > 0)
|
||
{
|
||
video_frame_delay_effective -= vfda.decrease;
|
||
RARCH_LOG("[Video]: Frame delay decrease by %d ms to %d ms due to frame time: %d > %d.\n",
|
||
vfda.decrease, video_frame_delay_effective, vfda.time, vfda.target);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
video_st->frame_delay_target = video_frame_delay_effective = video_frame_delay;
|
||
|
||
video_st->frame_delay_effective = video_frame_delay_effective;
|
||
|
||
if (video_frame_delay_effective > 0)
|
||
retro_sleep(video_frame_delay_effective);
|
||
}
|
||
|
||
{
|
||
#ifdef HAVE_RUNAHEAD
|
||
bool run_ahead_enabled = settings->bools.run_ahead_enabled;
|
||
unsigned run_ahead_num_frames = settings->uints.run_ahead_frames;
|
||
bool run_ahead_hide_warnings = settings->bools.run_ahead_hide_warnings;
|
||
bool run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance;
|
||
/* Run Ahead Feature replaces the call to core_run in this loop */
|
||
bool want_runahead = run_ahead_enabled &&
|
||
(run_ahead_num_frames > 0) && runloop_st->runahead_available;
|
||
#ifdef HAVE_NETWORKING
|
||
want_runahead = want_runahead && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
|
||
#endif
|
||
|
||
if (want_runahead)
|
||
do_runahead(
|
||
runloop_st,
|
||
run_ahead_num_frames,
|
||
run_ahead_hide_warnings,
|
||
run_ahead_secondary_instance);
|
||
else
|
||
#endif
|
||
core_run();
|
||
}
|
||
|
||
/* Increment runtime tick counter after each call to
|
||
* core_run() or run_ahead() */
|
||
runloop_st->core_runtime_usec += runloop_core_runtime_tick(
|
||
runloop_st,
|
||
slowmotion_ratio,
|
||
current_time);
|
||
|
||
#ifdef HAVE_CHEEVOS
|
||
if (cheevos_enable)
|
||
rcheevos_test();
|
||
#endif
|
||
#ifdef HAVE_CHEATS
|
||
cheat_manager_apply_retro_cheats();
|
||
#endif
|
||
#ifdef HAVE_PRESENCE
|
||
presence_update(PRESENCE_GAME);
|
||
#endif
|
||
|
||
/* Restores analog D-pad binds temporarily overridden. */
|
||
for (i = 0; i < max_users; i++)
|
||
{
|
||
if (dpad_mode[i] != ANALOG_DPAD_NONE)
|
||
{
|
||
int j;
|
||
unsigned joy_idx = settings->uints.input_joypad_index[i];
|
||
struct retro_keybind *general_binds = input_config_binds[joy_idx];
|
||
struct retro_keybind *auto_binds = input_autoconf_binds[joy_idx];
|
||
|
||
for (j = RETRO_DEVICE_ID_JOYPAD_UP; j <= RETRO_DEVICE_ID_JOYPAD_RIGHT; j++)
|
||
{
|
||
(auto_binds)[j].joyaxis = (auto_binds)[j].orig_joyaxis;
|
||
(general_binds)[j].joyaxis = (general_binds)[j].orig_joyaxis;
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifdef HAVE_BSV_MOVIE
|
||
if (input_st->bsv_movie_state_handle)
|
||
{
|
||
input_st->bsv_movie_state_handle->frame_ptr =
|
||
(input_st->bsv_movie_state_handle->frame_ptr + 1)
|
||
& input_st->bsv_movie_state_handle->frame_mask;
|
||
|
||
input_st->bsv_movie_state_handle->first_rewind =
|
||
!input_st->bsv_movie_state_handle->did_rewind;
|
||
input_st->bsv_movie_state_handle->did_rewind = false;
|
||
}
|
||
#endif
|
||
|
||
#ifdef HAVE_THREADS
|
||
if (runloop_st->autosave)
|
||
autosave_unlock();
|
||
#endif
|
||
|
||
end:
|
||
if (vrr_runloop_enable)
|
||
{
|
||
/* Sync on video only, block audio later. */
|
||
if (runloop_st->fastforward_after_frames && audio_sync)
|
||
{
|
||
if (runloop_st->fastforward_after_frames == 1)
|
||
{
|
||
/* Nonblocking audio */
|
||
if (audio_st->active &&
|
||
audio_st->context_audio_data)
|
||
audio_st->current_audio->set_nonblock_state(
|
||
audio_st->context_audio_data, true);
|
||
audio_st->chunk_size =
|
||
audio_st->chunk_nonblock_size;
|
||
}
|
||
|
||
runloop_st->fastforward_after_frames++;
|
||
|
||
if (runloop_st->fastforward_after_frames == 6)
|
||
{
|
||
/* Blocking audio */
|
||
if (audio_st->active &&
|
||
audio_st->context_audio_data)
|
||
audio_st->current_audio->set_nonblock_state(
|
||
audio_st->context_audio_data,
|
||
audio_sync ? false : true);
|
||
|
||
audio_st->chunk_size = audio_st->chunk_block_size;
|
||
runloop_st->fastforward_after_frames = 0;
|
||
}
|
||
}
|
||
|
||
if (runloop_st->fastmotion)
|
||
runloop_st->frame_limit_minimum_time =
|
||
runloop_set_frame_limit(&video_st->av_info,
|
||
runloop_get_fastforward_ratio(settings,
|
||
&runloop_st->fastmotion_override.current));
|
||
else
|
||
runloop_st->frame_limit_minimum_time =
|
||
runloop_set_frame_limit(&video_st->av_info,
|
||
1.0f);
|
||
}
|
||
|
||
/* if there's a fast forward limit, inject sleeps to keep from going too fast. */
|
||
if (runloop_st->frame_limit_minimum_time)
|
||
{
|
||
const retro_time_t end_frame_time = cpu_features_get_time_usec();
|
||
const retro_time_t to_sleep_ms = (
|
||
( runloop_st->frame_limit_last_time
|
||
+ runloop_st->frame_limit_minimum_time)
|
||
- end_frame_time) / 1000;
|
||
|
||
if (to_sleep_ms > 0)
|
||
{
|
||
unsigned sleep_ms = (unsigned)to_sleep_ms;
|
||
|
||
/* Combat jitter a bit. */
|
||
runloop_st->frame_limit_last_time +=
|
||
runloop_st->frame_limit_minimum_time;
|
||
|
||
if (sleep_ms > 0)
|
||
{
|
||
#if defined(HAVE_COCOATOUCH)
|
||
if (!uico_state_get_ptr()->is_on_foreground)
|
||
#endif
|
||
retro_sleep(sleep_ms);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
runloop_st->frame_limit_last_time = end_frame_time;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void runloop_msg_queue_deinit(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
|
||
|
||
msg_queue_deinitialize(&runloop_st->msg_queue);
|
||
|
||
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
|
||
#ifdef HAVE_THREADS
|
||
slock_free(runloop_st->msg_queue_lock);
|
||
runloop_st->msg_queue_lock = NULL;
|
||
#endif
|
||
|
||
runloop_st->msg_queue_size = 0;
|
||
}
|
||
|
||
void runloop_msg_queue_init(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
runloop_msg_queue_deinit();
|
||
msg_queue_initialize(&runloop_st->msg_queue, 8);
|
||
|
||
#ifdef HAVE_THREADS
|
||
runloop_st->msg_queue_lock = slock_new();
|
||
#endif
|
||
}
|
||
|
||
void runloop_task_msg_queue_push(
|
||
retro_task_t *task, const char *msg,
|
||
unsigned prio, unsigned duration,
|
||
bool flush)
|
||
{
|
||
#if defined(HAVE_GFX_WIDGETS)
|
||
#ifdef HAVE_MENU
|
||
struct menu_state *menu_st = menu_state_get_ptr();
|
||
#endif
|
||
#ifdef HAVE_ACCESSIBILITY
|
||
access_state_t *access_st = access_state_get_ptr();
|
||
settings_t *settings = config_get_ptr();
|
||
bool accessibility_enable = settings->bools.accessibility_enable;
|
||
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
|
||
#endif
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
bool widgets_active = dispwidget_get_ptr()->active;
|
||
|
||
if (widgets_active && task->title && !task->mute)
|
||
{
|
||
RUNLOOP_MSG_QUEUE_LOCK(runloop_st);
|
||
ui_companion_driver_msg_queue_push(msg,
|
||
prio, task ? duration : duration * 60 / 1000, flush);
|
||
#ifdef HAVE_ACCESSIBILITY
|
||
if (is_accessibility_enabled(
|
||
accessibility_enable,
|
||
access_st->enabled))
|
||
accessibility_speak_priority(
|
||
accessibility_enable,
|
||
accessibility_narrator_speech_speed,
|
||
(char*)msg, 0);
|
||
#endif
|
||
gfx_widgets_msg_queue_push(
|
||
task,
|
||
msg,
|
||
duration,
|
||
NULL,
|
||
(enum message_queue_icon)MESSAGE_QUEUE_CATEGORY_INFO,
|
||
(enum message_queue_category)MESSAGE_QUEUE_ICON_DEFAULT,
|
||
prio,
|
||
flush,
|
||
#ifdef HAVE_MENU
|
||
menu_st->alive
|
||
#else
|
||
false
|
||
#endif
|
||
);
|
||
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st);
|
||
}
|
||
else
|
||
#endif
|
||
runloop_msg_queue_push(msg, prio, duration, flush, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||
}
|
||
|
||
bool retroarch_get_current_savestate_path(char *path, size_t len)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
settings_t *settings = config_get_ptr();
|
||
int state_slot = settings ? settings->ints.state_slot : 0;
|
||
const char *name_savestate = NULL;
|
||
|
||
if (!path)
|
||
return false;
|
||
|
||
name_savestate = runloop_st->name.savestate;
|
||
if (string_is_empty(name_savestate))
|
||
return false;
|
||
|
||
if (state_slot > 0)
|
||
snprintf(path, len, "%s%d", name_savestate, state_slot);
|
||
else if (state_slot < 0)
|
||
fill_pathname_join_delim(path, name_savestate, "auto", '.', len);
|
||
else
|
||
strlcpy(path, name_savestate, len);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool retroarch_get_entry_state_path(char *path, size_t len, unsigned slot)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
const char *name_savestate = NULL;
|
||
|
||
if (!path || !slot)
|
||
return false;
|
||
|
||
name_savestate = runloop_st->name.savestate;
|
||
if (string_is_empty(name_savestate))
|
||
return false;
|
||
|
||
snprintf(path, len, "%s%d%s", name_savestate, slot, ".entry");
|
||
|
||
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->has_set_core)
|
||
return;
|
||
|
||
if (explicitly_set)
|
||
{
|
||
runloop_st->has_set_core = true;
|
||
runloop_st->explicit_current_core_type = type;
|
||
}
|
||
runloop_st->current_core_type = type;
|
||
}
|
||
|
||
bool core_set_default_callbacks(void *data)
|
||
{
|
||
struct retro_callbacks *cbs = (struct retro_callbacks*)data;
|
||
retro_input_state_t state_cb = core_input_state_poll_return_cb();
|
||
|
||
cbs->frame_cb = video_driver_frame;
|
||
cbs->sample_cb = audio_driver_sample;
|
||
cbs->sample_batch_cb = audio_driver_sample_batch;
|
||
cbs->state_cb = state_cb;
|
||
cbs->poll_cb = input_driver_poll;
|
||
|
||
return true;
|
||
}
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
/**
|
||
* core_set_netplay_callbacks:
|
||
*
|
||
* Set the I/O callbacks to use netplay's interceding callback system. Should
|
||
* only be called while initializing netplay.
|
||
**/
|
||
bool core_set_netplay_callbacks(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
/* Force normal poll type for netplay. */
|
||
runloop_st->current_core.poll_type = POLL_TYPE_NORMAL;
|
||
|
||
/* And use netplay's interceding callbacks */
|
||
runloop_st->current_core.retro_set_video_refresh(video_frame_net);
|
||
runloop_st->current_core.retro_set_audio_sample(audio_sample_net);
|
||
runloop_st->current_core.retro_set_audio_sample_batch(audio_sample_batch_net);
|
||
runloop_st->current_core.retro_set_input_state(input_state_net);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* core_unset_netplay_callbacks
|
||
*
|
||
* Unset the I/O callbacks from having used netplay's interceding callback
|
||
* system. Should only be called while uninitializing netplay.
|
||
*/
|
||
bool core_unset_netplay_callbacks(void)
|
||
{
|
||
struct retro_callbacks cbs;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
if (!core_set_default_callbacks(&cbs))
|
||
return false;
|
||
|
||
runloop_st->current_core.retro_set_video_refresh(cbs.frame_cb);
|
||
runloop_st->current_core.retro_set_audio_sample(cbs.sample_cb);
|
||
runloop_st->current_core.retro_set_audio_sample_batch(cbs.sample_batch_cb);
|
||
runloop_st->current_core.retro_set_input_state(cbs.state_cb);
|
||
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
bool core_set_cheat(retro_ctx_cheat_info_t *info)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
|
||
settings_t *settings = config_get_ptr();
|
||
bool run_ahead_enabled = false;
|
||
unsigned run_ahead_frames = 0;
|
||
bool run_ahead_secondary_instance = false;
|
||
bool want_runahead = false;
|
||
|
||
if (settings)
|
||
{
|
||
run_ahead_enabled = settings->bools.run_ahead_enabled;
|
||
run_ahead_frames = settings->uints.run_ahead_frames;
|
||
run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance;
|
||
want_runahead = run_ahead_enabled &&
|
||
(run_ahead_frames > 0) && runloop_st->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->runahead_secondary_core_available
|
||
&& secondary_core_ensure_exists(settings)
|
||
&& runloop_st->secondary_core.retro_cheat_set)
|
||
runloop_st->secondary_core.retro_cheat_set(
|
||
info->index, info->enabled, info->code);
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
bool core_reset_cheat(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
|
||
settings_t *settings = config_get_ptr();
|
||
bool run_ahead_enabled = false;
|
||
unsigned run_ahead_frames = 0;
|
||
bool run_ahead_secondary_instance = false;
|
||
bool want_runahead = false;
|
||
|
||
if (settings)
|
||
{
|
||
run_ahead_enabled = settings->bools.run_ahead_enabled;
|
||
run_ahead_frames = settings->uints.run_ahead_frames;
|
||
run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance;
|
||
want_runahead = run_ahead_enabled &&
|
||
(run_ahead_frames > 0) && runloop_st->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->runahead_secondary_core_available
|
||
&& secondary_core_ensure_exists(settings)
|
||
&& runloop_st->secondary_core.retro_cheat_reset)
|
||
runloop_st->secondary_core.retro_cheat_reset();
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
bool core_set_poll_type(unsigned type)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
runloop_st->current_core.poll_type = type;
|
||
return true;
|
||
}
|
||
|
||
bool core_set_controller_port_device(retro_ctx_controller_info_t *pad)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
input_driver_state_t *input_st = input_state_get_ptr();
|
||
if (!pad)
|
||
return false;
|
||
|
||
/* We are potentially 'connecting' a entirely different
|
||
* type of virtual input device, which may or may not
|
||
* support analog inputs. We therefore have to reset
|
||
* the 'analog input requested' flag for this port - but
|
||
* since port mapping is arbitrary/mutable, it is easiest
|
||
* to simply reset the flags for all ports.
|
||
* Correct values will be registered at the next call
|
||
* of 'input_state()' */
|
||
memset(&input_st->analog_requested, 0,
|
||
sizeof(input_st->analog_requested));
|
||
|
||
#ifdef HAVE_RUNAHEAD
|
||
remember_controller_port_device(pad->port, pad->device);
|
||
#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 contentless = false;
|
||
bool is_inited = false;
|
||
bool game_loaded = false;
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
video_driver_set_cached_frame_ptr(NULL);
|
||
|
||
#ifdef HAVE_RUNAHEAD
|
||
set_load_content_info(runloop_st, load_info);
|
||
runloop_clear_controller_port_map();
|
||
#endif
|
||
|
||
content_get_status(&contentless, &is_inited);
|
||
set_save_state_in_background(false);
|
||
|
||
if (load_info && load_info->special)
|
||
game_loaded = runloop_st->current_core.retro_load_game_special(
|
||
load_info->special->id, load_info->info, load_info->content->size);
|
||
else if (load_info && !string_is_empty(load_info->content->elems[0].data))
|
||
game_loaded = runloop_st->current_core.retro_load_game(load_info->info);
|
||
else if (contentless)
|
||
game_loaded = runloop_st->current_core.retro_load_game(NULL);
|
||
|
||
runloop_st->current_core.game_loaded = 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 */
|
||
if (game_loaded)
|
||
input_remapping_enable_global_config_restore();
|
||
|
||
return game_loaded;
|
||
}
|
||
|
||
bool core_get_system_info(struct retro_system_info *system)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!system)
|
||
return false;
|
||
runloop_st->current_core.retro_get_system_info(system);
|
||
return true;
|
||
}
|
||
|
||
bool core_unserialize(retro_ctx_serialize_info_t *info)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!info || !runloop_st->current_core.retro_unserialize(info->data_const, info->size))
|
||
return false;
|
||
|
||
#if HAVE_NETWORKING
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, info);
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
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_size(retro_ctx_size_info_t *info)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!info)
|
||
return false;
|
||
info->size = runloop_st->current_core.retro_serialize_size();
|
||
return true;
|
||
}
|
||
|
||
uint64_t core_serialization_quirks(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
return runloop_st->current_core.serialization_quirks_v;
|
||
}
|
||
|
||
bool core_reset(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
|
||
video_driver_set_cached_frame_ptr(NULL);
|
||
|
||
runloop_st->current_core.retro_reset();
|
||
return true;
|
||
}
|
||
|
||
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;
|
||
unsigned new_poll_type = (core_poll_type_override != POLL_TYPE_OVERRIDE_DONTCARE)
|
||
? (core_poll_type_override - 1)
|
||
: current_core->poll_type;
|
||
bool early_polling = new_poll_type == POLL_TYPE_EARLY;
|
||
bool late_polling = new_poll_type == POLL_TYPE_LATE;
|
||
#ifdef HAVE_NETWORKING
|
||
bool netplay_preframe = netplay_driver_ctl(
|
||
RARCH_NETPLAY_CTL_PRE_FRAME, NULL);
|
||
|
||
if (!netplay_preframe)
|
||
{
|
||
/* Paused due to netplay. We must poll and display something so that a
|
||
* netplay peer pausing doesn't just hang. */
|
||
input_driver_poll();
|
||
video_driver_cached_frame();
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
if (early_polling)
|
||
input_driver_poll();
|
||
else if (late_polling)
|
||
current_core->input_polled = false;
|
||
|
||
current_core->retro_run();
|
||
|
||
if (late_polling && !current_core->input_polled)
|
||
input_driver_poll();
|
||
|
||
#ifdef HAVE_NETWORKING
|
||
netplay_driver_ctl(RARCH_NETPLAY_CTL_POST_FRAME, NULL);
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
bool core_has_set_input_descriptor(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
return runloop_st->current_core.has_set_input_descriptors;
|
||
}
|
||
|
||
char* crt_switch_core_name(void)
|
||
{
|
||
return (char*)runloop_state.system.info.library_name;
|
||
}
|
||
|
||
void runloop_path_set_basename(const char *path)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
char *dst = NULL;
|
||
|
||
path_set(RARCH_PATH_CONTENT, path);
|
||
path_set(RARCH_PATH_BASENAME, path);
|
||
|
||
#ifdef HAVE_COMPRESSION
|
||
/* Removing extension is a bit tricky for compressed files.
|
||
* Basename means:
|
||
* /file/to/path/game.extension should be:
|
||
* /file/to/path/game
|
||
*
|
||
* Two things to consider here are: /file/to/path/ is expected
|
||
* to be a directory and "game" is a single file. This is used for
|
||
* states and srm default paths.
|
||
*
|
||
* For compressed files we have:
|
||
*
|
||
* /file/to/path/comp.7z#game.extension and
|
||
* /file/to/path/comp.7z#folder/game.extension
|
||
*
|
||
* The choice I take here is:
|
||
* /file/to/path/game as basename. We might end up in a writable
|
||
* directory then and the name of srm and states are meaningful.
|
||
*
|
||
*/
|
||
path_basedir_wrapper(runloop_st->runtime_content_path_basename);
|
||
if (!string_is_empty(runloop_st->runtime_content_path_basename))
|
||
fill_pathname_dir(runloop_st->runtime_content_path_basename, path, "", sizeof(runloop_st->runtime_content_path_basename));
|
||
#endif
|
||
|
||
if ((dst = strrchr(runloop_st->runtime_content_path_basename, '.')))
|
||
*dst = '\0';
|
||
}
|
||
|
||
void runloop_path_set_names(void)
|
||
{
|
||
runloop_state_t *runloop_st = &runloop_state;
|
||
if (!retroarch_override_setting_is_set(
|
||
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL))
|
||
{
|
||
strlcpy(runloop_st->name.savefile,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.savefile));
|
||
strlcat(runloop_st->name.savefile,
|
||
".srm",
|
||
sizeof(runloop_st->name.savefile));
|
||
}
|
||
|
||
if (!retroarch_override_setting_is_set(
|
||
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL))
|
||
{
|
||
strlcpy(runloop_st->name.savestate,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.savestate));
|
||
strlcat(runloop_st->name.savestate,
|
||
".state",
|
||
sizeof(runloop_st->name.savestate));
|
||
}
|
||
|
||
#ifdef HAVE_CHEATS
|
||
if (!string_is_empty(runloop_st->runtime_content_path_basename))
|
||
{
|
||
strlcpy(runloop_st->name.cheatfile,
|
||
runloop_st->runtime_content_path_basename,
|
||
sizeof(runloop_st->name.cheatfile));
|
||
strlcat(runloop_st->name.cheatfile,
|
||
".cht",
|
||
sizeof(runloop_st->name.cheatfile));
|
||
}
|
||
#endif
|
||
}
|