1
0
mirror of https://github.com/libretro/RetroArch synced 2024-07-08 12:15:49 +00:00
RetroArch/retroarch.c
2021-09-26 23:07:51 +02:00

30963 lines
1008 KiB
C
Raw Blame History

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2021 - Daniel De Matteis
* Copyright (C) 2012-2015 - Michael Lelli
* Copyright (C) 2014-2017 - Jean-Andr<64> Santoni
* Copyright (C) 2016-2019 - Brad Parker
* Copyright (C) 2016-2019 - Andr<64>s Su<53>rez (input mapper/Discord 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 <setjmp.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 <gfx/scaler/pixconv.h>
#include <gfx/scaler/scaler.h>
#include <gfx/video_frame.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 <audio/conversion/float_to_s16.h>
#include <audio/conversion/s16_to_float.h>
#ifdef HAVE_AUDIOMIXER
#include <audio/audio_mixer.h>
#endif
#ifdef HAVE_DSP_FILTER
#include <audio/dsp_filter.h>
#endif
#include <compat/posix_string.h>
#include <streams/file_stream.h>
#include <streams/interface_stream.h>
#include <file/file_path.h>
#include <retro_assert.h>
#include <retro_miscellaneous.h>
#include <queues/message_queue.h>
#include <queues/task_queue.h>
#include <lists/dir_list.h>
#ifdef HAVE_NETWORKING
#include <net/net_http.h>
#endif
#ifdef WIIU
#include <wiiu/os/energy.h>
#endif
#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_DISCORD
#include <discord_rpc.h>
#include "deps/discord-rpc/include/discord_rpc.h"
#include "network/discord.h"
#endif
#include "config.def.h"
#include "config.def.keybinds.h"
#include "runtime_file.h"
#include "runloop.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 "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_TRANSLATE
#include <encodings/base64.h>
#include <formats/rbmp.h>
#include <formats/rpng.h>
#include <formats/rjson.h>
#include "translation_defines.h"
#endif
#ifdef HAVE_DISCORD
#include "network/discord.h"
#endif
#ifdef HAVE_NETWORKING
#include "network/netplay/netplay.h"
#include "network/netplay/netplay_private.h"
#include "network/netplay/netplay_discovery.h"
#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_type.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
#include "bluetooth/bluetooth_driver.h"
#include "wifi/wifi_driver.h"
#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
#ifdef HAVE_AUDIOMIXER
#include "tasks/task_audio_mixer.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"
#ifdef HAVE_ACCESSIBILITY
#include "accessibility.h"
#endif
#ifdef HAVE_THREADS
#include "audio/audio_thread_wrapper.h"
#endif
#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
#include "SDL.h"
#endif
/* RetroArch global state / macros */
#include "retroarch_data.h"
/* Forward declarations */
#include "retroarch_fwd_decls.h"
#ifdef HAVE_LAKKA
#include "lakka.h"
#endif
static runloop_core_status_msg_t runloop_core_status_msg =
{
0,
0.0f,
"",
false
};
static runloop_state_t runloop_state;
/* GLOBAL POINTER GETTERS */
#ifdef HAVE_REWIND
bool state_manager_frame_is_reversed(void)
{
struct rarch_state *p_rarch = &rarch_st;
struct state_manager_rewind_state
*rewind_st = &p_rarch->rewind_st;
return rewind_st->frame_is_reversed;
}
#endif
content_state_t *content_state_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return &p_rarch->content_st;
}
/* Get the current subsystem rom id */
unsigned content_get_subsystem_rom_id(void)
{
struct rarch_state *p_rarch = &rarch_st;
content_state_t *p_content = &p_rarch->content_st;
return p_content->pending_subsystem_rom_id;
}
/* Get the current subsystem */
int content_get_subsystem(void)
{
struct rarch_state *p_rarch = &rarch_st;
content_state_t *p_content = &p_rarch->content_st;
return p_content->pending_subsystem_id;
}
int input_event_get_osk_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->osk_ptr;
}
char **input_event_get_osk_grid(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->osk_grid;
}
gfx_thumbnail_state_t *gfx_thumb_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return &p_rarch->gfx_thumb_state;
}
struct retro_hw_render_callback *video_driver_get_hw_context(void)
{
struct rarch_state *p_rarch = &rarch_st;
return VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
}
struct retro_system_av_info *video_viewport_get_system_av_info(void)
{
struct rarch_state *p_rarch = &rarch_st;
return &p_rarch->video_driver_av_info;
}
gfx_display_t *disp_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return &p_rarch->dispgfx;
}
#ifdef HAVE_GFX_WIDGETS
void *dispwidget_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return &p_rarch->dispwidget_st;
}
#endif
settings_t *config_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->configuration_settings;
}
global_t *global_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return &p_rarch->g_extern;
}
#ifdef HAVE_THREADS
/**
* video_thread_get_ptr:
* @drv : Found driver.
*
* Gets the underlying video driver associated with the
* threaded video wrapper. Sets @drv to the found
* video driver.
*
* Returns: Video driver data of the video driver associated
* with the threaded wrapper (if successful). If not successful,
* NULL.
**/
static void *video_thread_get_ptr(struct rarch_state *p_rarch)
{
const thread_video_t *thr = (const thread_video_t*)p_rarch->video_driver_data;
if (thr)
return thr->driver_data;
return NULL;
}
#endif
/**
* video_driver_get_ptr:
*
* Use this if you need the real video driver
* and driver data pointers.
*
* Returns: video driver's userdata.
**/
void *video_driver_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return VIDEO_DRIVER_GET_PTR_INTERNAL(p_rarch);
}
void *video_driver_get_data(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_data;
}
/* DRIVERS */
int driver_find_index(const char *label, const char *drv)
{
unsigned i;
char str[256];
str[0] = '\0';
for (i = 0;
find_driver_nonempty(label, i, str, sizeof(str)) != NULL; i++)
{
if (string_is_empty(str))
break;
if (string_is_equal_noncase(drv, str))
return i;
}
return -1;
}
/**
* driver_find_last:
* @label : string of driver type to be found.
* @s : identifier of driver to be found.
* @len : size of @s.
*
* Find last driver in driver array.
**/
static void driver_find_last(const char *label, char *s, size_t len)
{
unsigned i;
for (i = 0;
find_driver_nonempty(label, i, s, len) != NULL; i++) { }
if (i)
i = i - 1;
else
i = 0;
find_driver_nonempty(label, i, s, len);
}
/**
* driver_find_prev:
* @label : string of driver type to be found.
* @s : identifier of driver to be found.
* @len : size of @s.
*
* Find previous driver in driver array.
**/
static bool driver_find_prev(const char *label, char *s, size_t len)
{
int i = driver_find_index(label, s);
if (i > 0)
{
find_driver_nonempty(label, i - 1, s, len);
return true;
}
RARCH_WARN(
"Couldn't find any previous driver (current one: \"%s\").\n", s);
return false;
}
/**
* driver_find_next:
* @label : string of driver type to be found.
* @s : identifier of driver to be found.
* @len : size of @s.
*
* Find next driver in driver array.
**/
static bool driver_find_next(const char *label, char *s, size_t len)
{
int i = driver_find_index(label, s);
if (i >= 0 && string_is_not_equal(s, "null"))
{
find_driver_nonempty(label, i + 1, s, len);
return true;
}
RARCH_WARN("%s (current one: \"%s\").\n",
msg_hash_to_str(MSG_COULD_NOT_FIND_ANY_NEXT_DRIVER),
s);
return false;
}
void input_keyboard_mapping_bits(unsigned mode, unsigned key)
{
struct rarch_state *p_rarch = &rarch_st;
switch (mode)
{
case 0:
BIT512_CLEAR_PTR(&p_rarch->keyboard_mapping_bits, key);
break;
case 1:
BIT512_SET_PTR(&p_rarch->keyboard_mapping_bits, key);
break;
default:
break;
}
}
#ifdef HAVE_MENU
static bool menu_input_key_bind_custom_bind_keyboard_cb(
void *data, unsigned code)
{
uint64_t current_usec;
struct rarch_state *p_rarch = &rarch_st;
struct menu_state *menu_st = menu_state_get_ptr();
settings_t *settings = p_rarch->configuration_settings;
struct menu_bind_state *binds = &menu_st->input_binds;
uint64_t input_bind_hold_us = settings->uints.input_bind_hold * 1000000;
uint64_t input_bind_timeout_us = settings->uints.input_bind_timeout * 1000000;
/* Clear old mapping bit */
BIT512_CLEAR_PTR(&p_rarch->keyboard_mapping_bits, binds->buffer.key);
/* store key in bind */
binds->buffer.key = (enum retro_key)code;
/* Store new mapping bit */
BIT512_SET_PTR(&p_rarch->keyboard_mapping_bits, binds->buffer.key);
/* write out the bind */
*(binds->output) = binds->buffer;
/* next bind */
binds->begin++;
binds->output++;
binds->buffer =* (binds->output);
current_usec = cpu_features_get_time_usec();
RARCH_TIMER_BEGIN_NEW_TIME_USEC(
binds->timer_hold,
current_usec,
input_bind_hold_us);
RARCH_TIMER_BEGIN_NEW_TIME_USEC(
binds->timer_timeout,
current_usec, input_bind_timeout_us);
return (binds->begin <= binds->last);
}
bool menu_input_key_bind_set_mode(
enum menu_input_binds_ctl_state state, void *data)
{
uint64_t current_usec;
unsigned index_offset;
rarch_setting_t *setting = (rarch_setting_t*)data;
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t
*input_driver_st = &p_rarch->input_driver_state;
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
const input_device_driver_t
*joypad = input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_driver_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
menu_input_t *menu_input = &menu_st->input_state;
settings_t *settings = p_rarch->configuration_settings;
struct menu_bind_state *binds = &menu_st->input_binds;
uint64_t input_bind_hold_us = settings->uints.input_bind_hold
* 1000000;
uint64_t input_bind_timeout_us = settings->uints.input_bind_timeout
* 1000000;
if (!setting || !menu)
return false;
if (menu_input_key_bind_set_mode_common(menu_st,
binds, state, setting, settings) == -1)
return false;
index_offset = setting->index_offset;
binds->port = settings->uints.input_joypad_index[
index_offset];
menu_input_key_bind_poll_bind_get_rested_axes(
joypad,
sec_joypad,
binds);
menu_input_key_bind_poll_bind_state(
&p_rarch->input_driver_state,
p_rarch->libretro_input_binds,
settings->floats.input_axis_threshold,
settings->uints.input_joypad_index[binds->port],
binds, false,
p_rarch->keyboard_mapping_blocked);
current_usec = cpu_features_get_time_usec();
RARCH_TIMER_BEGIN_NEW_TIME_USEC(
binds->timer_hold,
current_usec,
input_bind_hold_us);
RARCH_TIMER_BEGIN_NEW_TIME_USEC(
binds->timer_timeout,
current_usec,
input_bind_timeout_us);
p_rarch->keyboard_press_cb =
menu_input_key_bind_custom_bind_keyboard_cb;
p_rarch->keyboard_press_data = menu;
/* While waiting for input, we have to block all hotkeys. */
p_rarch->keyboard_mapping_blocked = true;
/* Upon triggering an input bind operation,
* pointer input must be inhibited - otherwise
* attempting to bind mouse buttons will cause
* spurious menu actions */
menu_input->select_inhibit = true;
menu_input->cancel_inhibit = true;
return true;
}
static bool menu_input_key_bind_iterate(
struct rarch_state *p_rarch,
settings_t *settings,
menu_input_ctx_bind_t *bind,
retro_time_t current_time)
{
bool timed_out = false;
struct menu_state *menu_st = menu_state_get_ptr();
struct menu_bind_state *_binds = &menu_st->input_binds;
menu_input_t *menu_input = &menu_st->input_state;
uint64_t input_bind_hold_us = settings->uints.input_bind_hold * 1000000;
uint64_t input_bind_timeout_us = settings->uints.input_bind_timeout * 1000000;
snprintf(bind->s, bind->len,
"[%s]\nPress keyboard, mouse or joypad\n(Timeout %d %s)",
input_config_bind_map_get_desc(
_binds->begin - MENU_SETTINGS_BIND_BEGIN),
RARCH_TIMER_GET_TIMEOUT(_binds->timer_timeout),
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SECONDS));
/* Tick main timers */
RARCH_TIMER_TICK(_binds->timer_timeout, current_time);
RARCH_TIMER_TICK(_binds->timer_hold, current_time);
if (RARCH_TIMER_HAS_EXPIRED(_binds->timer_timeout))
{
uint64_t current_usec = cpu_features_get_time_usec();
p_rarch->keyboard_mapping_blocked = false;
/*skip to next bind*/
_binds->begin++;
_binds->output++;
RARCH_TIMER_BEGIN_NEW_TIME_USEC(_binds->timer_hold,
current_usec,
input_bind_hold_us);
RARCH_TIMER_BEGIN_NEW_TIME_USEC(_binds->timer_timeout,
current_usec,
input_bind_timeout_us);
timed_out = true;
}
/* binds.begin is updated in keyboard_press callback. */
if (_binds->begin > _binds->last)
{
/* Avoid new binds triggering things right away. */
/* Inhibits input for 2 frames
* > Required, since input is ignored for 1 frame
* after certain events - e.g. closing the OSK */
menu_st->input_driver_flushing_input = 2;
/* We won't be getting any key events, so just cancel early. */
if (timed_out)
{
p_rarch->keyboard_press_cb = NULL;
p_rarch->keyboard_press_data = NULL;
p_rarch->keyboard_mapping_blocked = false;
}
return true;
}
{
bool complete = false;
struct menu_bind_state new_binds = *_binds;
p_rarch->keyboard_mapping_blocked = false;
menu_input_key_bind_poll_bind_state(
&p_rarch->input_driver_state,
p_rarch->libretro_input_binds,
settings->floats.input_axis_threshold,
settings->uints.input_joypad_index[new_binds.port],
&new_binds, timed_out,
p_rarch->keyboard_mapping_blocked);
#ifdef ANDROID
/* Keep resetting bind during the hold period,
* or we'll potentially bind joystick and mouse, etc.*/
new_binds.buffer = *(new_binds.output);
if (menu_input_key_bind_poll_find_hold(
settings->uints.input_max_users,
&new_binds, &new_binds.buffer))
{
uint64_t current_usec = cpu_features_get_time_usec();
/* Inhibit timeout*/
RARCH_TIMER_BEGIN_NEW_TIME_USEC(
new_binds.timer_timeout,
current_usec,
input_bind_timeout_us);
/* Run hold timer*/
RARCH_TIMER_TICK(new_binds.timer_hold, current_time);
snprintf(bind->s, bind->len,
"[%s]\npress keyboard, mouse or joypad\nand hold ...",
input_config_bind_map_get_desc(
_binds->begin - MENU_SETTINGS_BIND_BEGIN));
/* Hold complete? */
if (RARCH_TIMER_HAS_EXPIRED(new_binds.timer_hold))
complete = true;
}
else
{
uint64_t current_usec = cpu_features_get_time_usec();
/* Reset hold countdown*/
RARCH_TIMER_BEGIN_NEW_TIME_USEC(new_binds.timer_hold,
current_usec,
input_bind_hold_us);
}
#else
if ((new_binds.skip && !_binds->skip) ||
menu_input_key_bind_poll_find_trigger(
settings->uints.input_max_users,
_binds, &new_binds, &(new_binds.buffer)))
complete = true;
#endif
if (complete)
{
uint64_t current_usec = cpu_features_get_time_usec();
/* Update bind */
*(new_binds.output) = new_binds.buffer;
p_rarch->keyboard_mapping_blocked = false;
/* Avoid new binds triggering things right away. */
/* Inhibits input for 2 frames
* > Required, since input is ignored for 1 frame
* after certain events - e.g. closing the OSK */
menu_st->input_driver_flushing_input = 2;
new_binds.begin++;
if (new_binds.begin > new_binds.last)
{
p_rarch->keyboard_press_cb = NULL;
p_rarch->keyboard_press_data = NULL;
p_rarch->keyboard_mapping_blocked = false;
return true;
}
/*next bind*/
new_binds.output++;
new_binds.buffer = *(new_binds.output);
RARCH_TIMER_BEGIN_NEW_TIME_USEC(new_binds.timer_hold,
current_usec, input_bind_hold_us);
RARCH_TIMER_BEGIN_NEW_TIME_USEC(new_binds.timer_timeout,
current_usec, input_bind_timeout_us);
}
*(_binds) = new_binds;
}
/* Pointer input must be inhibited on each
* frame that the bind operation is active -
* otherwise attempting to bind mouse buttons
* will cause spurious menu actions */
menu_input->select_inhibit = true;
menu_input->cancel_inhibit = true;
/* Menu screensaver should be inhibited on each
* frame that the bind operation is active */
menu_st->input_last_time_us = menu_st->current_time_us;
return false;
}
/**
* menu_iterate:
* @input : input sample for this frame
* @old_input : input sample of the previous frame
* @trigger_input : difference' input sample - difference
* between 'input' and 'old_input'
*
* Runs RetroArch menu for one frame.
*
* Returns: 0 on success, -1 if we need to quit out of the loop.
**/
static int generic_menu_iterate(
struct rarch_state *p_rarch,
struct menu_state *menu_st,
gfx_display_t *p_disp,
gfx_animation_t *p_anim,
settings_t *settings,
menu_handle_t *menu,
void *userdata, enum menu_action action,
retro_time_t current_time)
{
#ifdef HAVE_ACCESSIBILITY
static enum action_iterate_type
last_iterate_type = ITERATE_TYPE_DEFAULT;
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
enum action_iterate_type iterate_type;
unsigned file_type = 0;
int ret = 0;
const char *label = NULL;
file_list_t *list = MENU_LIST_GET(menu_st->entries.list, 0);
if (list && list->size)
file_list_get_at_offset(list, list->size - 1, NULL, &label, &file_type, NULL);
menu->menu_state_msg[0] = '\0';
iterate_type = action_iterate_type(label);
p_rarch->menu_driver_is_binding = false;
if ( action != MENU_ACTION_NOOP
|| MENU_ENTRIES_NEEDS_REFRESH(menu_st)
|| GFX_DISPLAY_GET_UPDATE_PENDING(p_anim, p_disp))
{
BIT64_SET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER);
}
switch (iterate_type)
{
case ITERATE_TYPE_HELP:
ret = menu_dialog_iterate(
&menu_st->dialog_st, settings,
menu->menu_state_msg, sizeof(menu->menu_state_msg),
current_time);
#ifdef HAVE_ACCESSIBILITY
if ( (iterate_type != last_iterate_type)
&& is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
menu->menu_state_msg, 10);
#endif
BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX);
BIT64_SET(menu->state, MENU_STATE_POST_ITERATE);
{
bool pop_stack = false;
if ( ret == 1 ||
action == MENU_ACTION_OK ||
action == MENU_ACTION_CANCEL
)
pop_stack = true;
if (pop_stack)
BIT64_SET(menu->state, MENU_STATE_POP_STACK);
}
break;
case ITERATE_TYPE_BIND:
{
menu_input_ctx_bind_t bind;
p_rarch->menu_driver_is_binding = true;
bind.s = menu->menu_state_msg;
bind.len = sizeof(menu->menu_state_msg);
if (menu_input_key_bind_iterate(p_rarch,
settings,
&bind, current_time))
{
size_t selection = menu_st->selection_ptr;
menu_entries_pop_stack(&selection, 0, 0);
menu_st->selection_ptr = selection;
}
else
BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX);
}
break;
case ITERATE_TYPE_INFO:
{
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = menu_list ? MENU_LIST_GET_SELECTION(menu_list, (unsigned)0) : NULL;
size_t selection = menu_st->selection_ptr;
menu_file_list_cbs_t *cbs = selection_buf ?
(menu_file_list_cbs_t*)selection_buf->list[selection].actiondata
: NULL;
if (cbs && cbs->enum_idx != MSG_UNKNOWN)
{
/* Core updater/manager entries require special treatment */
switch (cbs->enum_idx)
{
#ifdef HAVE_NETWORKING
case MENU_ENUM_LABEL_CORE_UPDATER_ENTRY:
{
core_updater_list_t *core_list =
core_updater_list_get_cached();
const core_updater_list_entry_t *entry = NULL;
const char *path =
selection_buf->list[selection].path;
/* Search for specified core */
if (
core_list
&& path
&& core_updater_list_get_filename(core_list,
path, &entry)
&& !string_is_empty(entry->description)
)
strlcpy(menu->menu_state_msg, entry->description,
sizeof(menu->menu_state_msg));
else
strlcpy(menu->menu_state_msg,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE),
sizeof(menu->menu_state_msg));
ret = 0;
}
break;
#endif
case MENU_ENUM_LABEL_CORE_MANAGER_ENTRY:
{
core_info_t *core_info = NULL;
const char *path = selection_buf->list[selection].path;
/* Search for specified core */
if ( path
&& core_info_find(path, &core_info)
&& !string_is_empty(core_info->description))
strlcpy(menu->menu_state_msg,
core_info->description,
sizeof(menu->menu_state_msg));
else
strlcpy(menu->menu_state_msg,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE),
sizeof(menu->menu_state_msg));
ret = 0;
}
break;
default:
ret = msg_hash_get_help_enum(cbs->enum_idx,
menu->menu_state_msg, sizeof(menu->menu_state_msg));
break;
}
#ifdef HAVE_ACCESSIBILITY
if ( (iterate_type != last_iterate_type) &&
is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
{
if (string_is_equal(menu->menu_state_msg,
msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE)))
{
char current_sublabel[255];
get_current_menu_sublabel(
menu_st,
current_sublabel, sizeof(current_sublabel));
if (string_is_equal(current_sublabel, ""))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
menu->menu_state_msg, 10);
else
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
current_sublabel, 10);
}
else
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
menu->menu_state_msg, 10);
}
#endif
}
else
{
enum msg_hash_enums enum_idx = MSG_UNKNOWN;
size_t selection = menu_st->selection_ptr;
unsigned type = selection_buf->list[selection].type;
switch (type)
{
case FILE_TYPE_FONT:
case FILE_TYPE_VIDEO_FONT:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_FONT;
break;
case FILE_TYPE_RDB:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_RDB;
break;
case FILE_TYPE_OVERLAY:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_OVERLAY;
break;
#ifdef HAVE_VIDEO_LAYOUT
case FILE_TYPE_VIDEO_LAYOUT:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_VIDEO_LAYOUT;
break;
#endif
case FILE_TYPE_CHEAT:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_CHEAT;
break;
case FILE_TYPE_SHADER_PRESET:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_SHADER_PRESET;
break;
case FILE_TYPE_SHADER:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_SHADER;
break;
case FILE_TYPE_REMAP:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_REMAP;
break;
case FILE_TYPE_RECORD_CONFIG:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_RECORD_CONFIG;
break;
case FILE_TYPE_CURSOR:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_CURSOR;
break;
case FILE_TYPE_CONFIG:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_CONFIG;
break;
case FILE_TYPE_CARCHIVE:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_COMPRESSED_ARCHIVE;
break;
case FILE_TYPE_DIRECTORY:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
break;
case FILE_TYPE_VIDEOFILTER: /* TODO/FIXME */
case FILE_TYPE_AUDIOFILTER: /* TODO/FIXME */
case FILE_TYPE_SHADER_SLANG: /* TODO/FIXME */
case FILE_TYPE_SHADER_GLSL: /* TODO/FIXME */
case FILE_TYPE_SHADER_HLSL: /* TODO/FIXME */
case FILE_TYPE_SHADER_CG: /* TODO/FIXME */
case FILE_TYPE_SHADER_PRESET_GLSLP: /* TODO/FIXME */
case FILE_TYPE_SHADER_PRESET_HLSLP: /* TODO/FIXME */
case FILE_TYPE_SHADER_PRESET_CGP: /* TODO/FIXME */
case FILE_TYPE_SHADER_PRESET_SLANGP: /* TODO/FIXME */
case FILE_TYPE_PLAIN:
enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_PLAIN_FILE;
break;
default:
break;
}
if (enum_idx != MSG_UNKNOWN)
ret = msg_hash_get_help_enum(enum_idx,
menu->menu_state_msg, sizeof(menu->menu_state_msg));
else
{
strlcpy(menu->menu_state_msg,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE),
sizeof(menu->menu_state_msg));
ret = 0;
}
}
}
BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX);
BIT64_SET(menu->state, MENU_STATE_POST_ITERATE);
if (action == MENU_ACTION_OK || action == MENU_ACTION_CANCEL)
{
BIT64_SET(menu->state, MENU_STATE_POP_STACK);
}
break;
case ITERATE_TYPE_DEFAULT:
{
menu_entry_t entry;
menu_list_t *menu_list = menu_st->entries.list;
size_t selection = menu_st->selection_ptr;
size_t menu_list_size = menu_st->entries.list ? MENU_LIST_GET_SELECTION(menu_st->entries.list, 0)->size : 0;
/* FIXME: Crappy hack, needed for mouse controls
* to not be completely broken in case we press back.
*
* We need to fix this entire mess, mouse controls
* should not rely on a hack like this in order to work. */
selection = MAX(MIN(selection, (menu_list_size - 1)), 0);
MENU_ENTRY_INIT(entry);
/* NOTE: If menu_entry_action() is modified,
* will have to verify that these parameters
* remain unused... */
entry.rich_label_enabled = false;
entry.value_enabled = false;
entry.sublabel_enabled = false;
menu_entry_get(&entry, 0, selection, NULL, false);
ret = menu_entry_action(&entry,
selection, (enum menu_action)action);
if (ret)
return -1;
BIT64_SET(menu->state, MENU_STATE_POST_ITERATE);
/* Have to defer it so we let settings refresh. */
if (menu_st->dialog_st.pending_push)
{
const char *label;
menu_displaylist_info_t info;
menu_displaylist_info_init(&info);
info.list = menu_list ? MENU_LIST_GET(menu_list, (unsigned)0) : NULL;
info.enum_idx = MENU_ENUM_LABEL_HELP;
/* Set the label string, if it exists. */
label = msg_hash_to_str(MENU_ENUM_LABEL_HELP);
if (label)
info.label = strdup(label);
menu_displaylist_ctl(DISPLAYLIST_HELP, &info, settings);
}
}
break;
}
#ifdef HAVE_ACCESSIBILITY
if ((last_iterate_type == ITERATE_TYPE_HELP
|| last_iterate_type == ITERATE_TYPE_INFO)
&& last_iterate_type != iterate_type
&& is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
"Closed dialog.", 10);
last_iterate_type = iterate_type;
#endif
BIT64_SET(menu->state, MENU_STATE_BLIT);
if (BIT64_GET(menu->state, MENU_STATE_POP_STACK))
{
size_t selection = menu_st->selection_ptr;
size_t new_selection_ptr = selection;
menu_entries_pop_stack(&new_selection_ptr, 0, 0);
menu_st->selection_ptr = selection;
}
if (BIT64_GET(menu->state, MENU_STATE_POST_ITERATE))
{
menu_input_t *menu_input = &menu_st->input_state;
/* If pointer devices are disabled, just ensure mouse
* cursor is hidden */
if (menu_input->pointer.type == MENU_POINTER_DISABLED)
ret = 0;
else
ret = menu_input_post_iterate(p_rarch, p_disp, menu_st, action,
current_time);
menu_input_set_pointer_visibility(
&menu_st->input_pointer_hw_state,
menu_input, current_time);
}
if (ret)
return -1;
return 0;
}
int generic_menu_entry_action(
void *userdata, menu_entry_t *entry, size_t i, enum menu_action action)
{
int ret = 0;
struct rarch_state *p_rarch = &rarch_st;
struct menu_state *menu_st = menu_state_get_ptr();
const menu_ctx_driver_t
*menu_driver_ctx = menu_st->driver_ctx;
menu_handle_t *menu = menu_st->driver_data;
settings_t *settings = p_rarch->configuration_settings;
void *menu_userdata = menu_st->userdata;
bool wraparound_enable = settings->bools.menu_navigation_wraparound_enable;
size_t scroll_accel = menu_st->scroll.acceleration;
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = menu_list ? MENU_LIST_GET_SELECTION(menu_list, (unsigned)0) : NULL;
file_list_t *menu_stack = menu_list ? MENU_LIST_GET(menu_list, (unsigned)0) : NULL;
size_t selection_buf_size = selection_buf ? selection_buf->size : 0;
menu_file_list_cbs_t *cbs = selection_buf ?
(menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL;
#ifdef HAVE_ACCESSIBILITY
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
switch (action)
{
case MENU_ACTION_UP:
if (selection_buf_size > 0)
{
unsigned scroll_speed = (unsigned)((MAX(scroll_accel, 2) - 2) / 4 + 1);
if (!(menu_st->selection_ptr == 0 && !wraparound_enable))
{
size_t idx = 0;
if (menu_st->selection_ptr >= scroll_speed)
idx = menu_st->selection_ptr - scroll_speed;
else
{
idx = selection_buf_size - 1;
if (!wraparound_enable)
idx = 0;
}
menu_st->selection_ptr = idx;
menu_driver_navigation_set(true);
if (menu_driver_ctx->navigation_decrement)
menu_driver_ctx->navigation_decrement(menu_userdata);
}
}
break;
case MENU_ACTION_DOWN:
if (selection_buf_size > 0)
{
unsigned scroll_speed = (unsigned)((MAX(scroll_accel, 2) - 2) / 4 + 1);
if (!(menu_st->selection_ptr >= selection_buf_size - 1
&& !wraparound_enable))
{
if ((menu_st->selection_ptr + scroll_speed) < selection_buf_size)
{
size_t idx = menu_st->selection_ptr + scroll_speed;
menu_st->selection_ptr = idx;
menu_driver_navigation_set(true);
}
else
{
if (wraparound_enable)
{
bool pending_push = false;
menu_driver_ctl(MENU_NAVIGATION_CTL_CLEAR, &pending_push);
}
else
menu_driver_ctl(MENU_NAVIGATION_CTL_SET_LAST, NULL);
}
if (menu_driver_ctx->navigation_increment)
menu_driver_ctx->navigation_increment(menu_userdata);
}
}
break;
case MENU_ACTION_SCROLL_UP:
if (
menu_st->scroll.index_size
&& menu_st->selection_ptr != 0
)
{
size_t l = menu_st->scroll.index_size - 1;
while (l
&& menu_st->scroll.index_list[l - 1]
>= menu_st->selection_ptr)
l--;
if (l > 0)
menu_st->selection_ptr = menu_st->scroll.index_list[l - 1];
if (menu_driver_ctx->navigation_descend_alphabet)
menu_driver_ctx->navigation_descend_alphabet(
menu_userdata, &menu_st->selection_ptr);
}
break;
case MENU_ACTION_SCROLL_DOWN:
if (menu_st->scroll.index_size)
{
if (menu_st->selection_ptr == menu_st->scroll.index_list[menu_st->scroll.index_size - 1])
menu_st->selection_ptr = selection_buf_size - 1;
else
{
size_t l = 0;
while (l < menu_st->scroll.index_size - 1
&& menu_st->scroll.index_list[l + 1] <= menu_st->selection_ptr)
l++;
menu_st->selection_ptr = menu_st->scroll.index_list[l + 1];
if (menu_st->selection_ptr >= selection_buf_size)
menu_st->selection_ptr = selection_buf_size - 1;
}
if (menu_driver_ctx->navigation_ascend_alphabet)
menu_driver_ctx->navigation_ascend_alphabet(
menu_userdata, &menu_st->selection_ptr);
}
break;
case MENU_ACTION_CANCEL:
if (cbs && cbs->action_cancel)
ret = cbs->action_cancel(entry->path,
entry->label, entry->type, i);
break;
case MENU_ACTION_OK:
if (cbs && cbs->action_ok)
ret = cbs->action_ok(entry->path,
entry->label, entry->type, i, entry->entry_idx);
break;
case MENU_ACTION_START:
if (cbs && cbs->action_start)
ret = cbs->action_start(entry->path,
entry->label, entry->type, i, entry->entry_idx);
break;
case MENU_ACTION_LEFT:
if (cbs && cbs->action_left)
ret = cbs->action_left(entry->type, entry->label, false);
break;
case MENU_ACTION_RIGHT:
if (cbs && cbs->action_right)
ret = cbs->action_right(entry->type, entry->label, false);
break;
case MENU_ACTION_INFO:
if (cbs && cbs->action_info)
ret = cbs->action_info(entry->type, entry->label);
break;
case MENU_ACTION_SELECT:
if (cbs && cbs->action_select)
ret = cbs->action_select(entry->path,
entry->label, entry->type, i, entry->entry_idx);
break;
case MENU_ACTION_SEARCH:
menu_input_dialog_start_search();
break;
case MENU_ACTION_SCAN:
if (cbs && cbs->action_scan)
ret = cbs->action_scan(entry->path,
entry->label, entry->type, i);
break;
default:
break;
}
if (MENU_ENTRIES_NEEDS_REFRESH(menu_st))
{
bool refresh = false;
menu_driver_displaylist_push(
menu_st,
settings,
selection_buf,
menu_stack);
menu_entries_ctl(MENU_ENTRIES_CTL_UNSET_REFRESH, &refresh);
}
#ifdef HAVE_ACCESSIBILITY
if ( action != 0
&& is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled)
&& !menu_input_dialog_get_display_kb())
{
char current_label[128];
char current_value[128];
char title_name[255];
char speak_string[512];
speak_string[0] = '\0';
title_name [0] = '\0';
current_label[0] = '\0';
get_current_menu_value(menu_st,
current_value, sizeof(current_value));
switch (action)
{
case MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE:
menu_entries_get_title(title_name, sizeof(title_name));
break;
case MENU_ACTION_START:
/* if equal to '..' we break, else we fall-through */
if (string_is_equal(current_value, "..."))
break;
/* fall-through */
case MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE_LABEL:
case MENU_ACTION_OK:
case MENU_ACTION_LEFT:
case MENU_ACTION_RIGHT:
case MENU_ACTION_CANCEL:
menu_entries_get_title(title_name, sizeof(title_name));
get_current_menu_label(menu_st, current_label, sizeof(current_label));
break;
case MENU_ACTION_UP:
case MENU_ACTION_DOWN:
case MENU_ACTION_SCROLL_UP:
case MENU_ACTION_SCROLL_DOWN:
case MENU_ACTION_SELECT:
case MENU_ACTION_SEARCH:
case MENU_ACTION_ACCESSIBILITY_SPEAK_LABEL:
get_current_menu_label(menu_st, current_label, sizeof(current_label));
break;
case MENU_ACTION_SCAN:
case MENU_ACTION_INFO:
default:
break;
}
if (!string_is_empty(title_name))
{
if (!string_is_equal(current_value, "..."))
snprintf(speak_string, sizeof(speak_string),
"%s %s %s", title_name, current_label, current_value);
else
snprintf(speak_string, sizeof(speak_string),
"%s %s", title_name, current_label);
}
else
{
if (!string_is_equal(current_value, "..."))
snprintf(speak_string, sizeof(speak_string),
"%s %s", current_label, current_value);
else
strlcpy(speak_string, current_label, sizeof(speak_string));
}
if (!string_is_empty(speak_string))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
speak_string, 10);
}
#endif
if (menu_st->pending_close_content)
{
const char *content_path = path_get(RARCH_PATH_CONTENT);
const char *menu_flush_to = msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU);
/* Flush to playlist entry menu if launched via playlist */
if (menu &&
!string_is_empty(menu->deferred_path) &&
!string_is_empty(content_path) &&
string_is_equal(menu->deferred_path, content_path))
menu_flush_to = msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS);
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
menu_entries_flush_stack(menu_flush_to, 0);
menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL);
menu_st->selection_ptr = 0;
menu_st->pending_close_content = false;
}
return ret;
}
#ifdef HAVE_COMPRESSION
/* This function gets called at first startup on Android/iOS
* when we need to extract the APK contents/zip file. This
* file contains assets which then get extracted to the
* user's asset directories. */
void bundle_decompressed(retro_task_t *task,
void *task_data,
void *user_data, const char *err)
{
settings_t *settings = config_get_ptr();
decompress_task_data_t *dec = (decompress_task_data_t*)task_data;
if (err)
RARCH_ERR("%s", err);
if (dec)
{
if (!err)
command_event(CMD_EVENT_REINIT, NULL);
/* delete bundle? */
free(dec->source_file);
free(dec);
}
configuration_set_uint(settings,
settings->uints.bundle_assets_extract_last_version,
settings->uints.bundle_assets_extract_version_current);
configuration_set_bool(settings, settings->bools.bundle_finished, true);
command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
}
#endif
const char *menu_driver_ident(void)
{
struct rarch_state *p_rarch = &rarch_st;
struct menu_state *menu_st = menu_state_get_ptr();
if (p_rarch->menu_driver_alive)
if (menu_st->driver_ctx && menu_st->driver_ctx->ident)
return menu_st->driver_ctx->ident;
return NULL;
}
/* Iterate the menu driver for one frame. */
static bool menu_driver_iterate(
struct rarch_state *p_rarch,
struct menu_state *menu_st,
gfx_display_t *p_disp,
gfx_animation_t *p_anim,
settings_t *settings,
enum menu_action action,
retro_time_t current_time)
{
return (menu_st->driver_data &&
generic_menu_iterate(
p_rarch,
menu_st,
p_disp,
p_anim,
settings,
menu_st->driver_data,
menu_st->userdata, action,
current_time) != -1);
}
static bool menu_driver_init_internal(
gfx_display_t *p_disp,
struct rarch_state *p_rarch,
settings_t *settings,
bool video_is_threaded)
{
struct menu_state *menu_st = menu_state_get_ptr();
menu_ctx_environment_t menu_environ;
if (menu_st->driver_ctx)
{
const char *ident = menu_st->driver_ctx->ident;
/* ID must be set first, since it is required for
* the proper determination of pixel/dpi scaling
* parameters (and some menu drivers fetch the
* current pixel/dpi scale during 'menu_driver_ctx->init()') */
if (ident)
p_disp->menu_driver_id = menu_driver_set_id(ident);
else
p_disp->menu_driver_id = MENU_DRIVER_ID_UNKNOWN;
if (menu_st->driver_ctx->init)
{
menu_st->driver_data = (menu_handle_t*)
menu_st->driver_ctx->init(&menu_st->userdata,
video_is_threaded);
menu_st->driver_data->userdata = menu_st->userdata;
menu_st->driver_data->driver_ctx = menu_st->driver_ctx;
}
}
if (!menu_st->driver_data || !rarch_menu_init(
menu_st,
&menu_st->dialog_st,
menu_st->driver_ctx,
&menu_st->input_state,
&menu_st->input_pointer_hw_state,
settings))
return false;
gfx_display_init();
/* TODO/FIXME - can we get rid of this? Is this needed? */
configuration_set_string(settings,
settings->arrays.menu_driver, menu_st->driver_ctx->ident);
if (menu_st->driver_ctx->lists_init)
{
if (!menu_st->driver_ctx->lists_init(menu_st->driver_data))
return false;
}
else
generic_menu_init_list(menu_st, settings);
/* Initialise menu screensaver */
menu_environ.type = MENU_ENVIRON_DISABLE_SCREENSAVER;
menu_environ.data = NULL;
menu_st->input_last_time_us = cpu_features_get_time_usec();
menu_st->screensaver_active = false;
menu_st->screensaver_supported = menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
return true;
}
bool menu_driver_init(bool video_is_threaded)
{
struct rarch_state *p_rarch = &rarch_st;
gfx_display_t *p_disp = &p_rarch->dispgfx;
struct menu_state *menu_st = menu_state_get_ptr();
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
if ( menu_st->driver_data ||
menu_driver_init_internal(
&p_rarch->dispgfx,
p_rarch,
p_rarch->configuration_settings,
video_is_threaded))
{
if (menu_st->driver_ctx && menu_st->driver_ctx->context_reset)
{
menu_st->driver_ctx->context_reset(menu_st->userdata,
video_is_threaded);
return true;
}
}
/* If driver initialisation failed, must reset
* driver id to 'unknown' */
p_disp->menu_driver_id = MENU_DRIVER_ID_UNKNOWN;
return false;
}
bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
gfx_display_t *p_disp = &p_rarch->dispgfx;
struct menu_state *menu_st = menu_state_get_ptr();
switch (state)
{
case RARCH_MENU_CTL_SET_PENDING_QUICK_MENU:
menu_entries_flush_stack(NULL, MENU_SETTINGS);
menu_st->pending_quick_menu = true;
break;
case RARCH_MENU_CTL_SET_PREVENT_POPULATE:
menu_st->prevent_populate = true;
break;
case RARCH_MENU_CTL_UNSET_PREVENT_POPULATE:
menu_st->prevent_populate = false;
break;
case RARCH_MENU_CTL_IS_PREVENT_POPULATE:
return menu_st->prevent_populate;
case RARCH_MENU_CTL_DEINIT:
if ( menu_st->driver_ctx
&& menu_st->driver_ctx->context_destroy)
menu_st->driver_ctx->context_destroy(menu_st->userdata);
if (menu_st->data_own)
return true;
playlist_free_cached();
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
menu_shader_manager_free(p_rarch);
#endif
#ifdef HAVE_NETWORKING
core_updater_list_free_cached();
#endif
#if defined(HAVE_MENU) && defined(HAVE_LIBRETRODB)
menu_explore_free();
#endif
if (menu_st->driver_data)
{
unsigned i;
menu_st->scroll.acceleration = 0;
menu_st->selection_ptr = 0;
menu_st->scroll.index_size = 0;
for (i = 0; i < SCROLL_INDEX_SIZE; i++)
menu_st->scroll.index_list[i] = 0;
memset(&menu_st->input_state, 0, sizeof(menu_input_t));
memset(&menu_st->input_pointer_hw_state, 0, sizeof(menu_input_pointer_hw_state_t));
if ( menu_st->driver_ctx
&& menu_st->driver_ctx->free)
menu_st->driver_ctx->free(menu_st->userdata);
if (menu_st->userdata)
free(menu_st->userdata);
menu_st->userdata = NULL;
p_disp->menu_driver_id = MENU_DRIVER_ID_UNKNOWN;
#ifndef HAVE_DYNAMIC
if (frontend_driver_has_fork())
#endif
{
rarch_system_info_t *system = &runloop_state.system;
libretro_free_system_info(&system->info);
memset(&system->info, 0, sizeof(struct retro_system_info));
}
gfx_animation_deinit();
gfx_display_free();
menu_entries_settings_deinit(menu_st);
menu_entries_list_deinit(menu_st->driver_ctx, menu_st);
if (menu_st->driver_data->core_buf)
free(menu_st->driver_data->core_buf);
menu_st->driver_data->core_buf = NULL;
menu_st->entries_need_refresh = false;
menu_st->entries_nonblocking_refresh = false;
menu_st->entries.begin = 0;
command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
rarch_favorites_deinit();
menu_st->dialog_st.pending_push = false;
menu_st->dialog_st.current_id = 0;
menu_st->dialog_st.current_type = MENU_DIALOG_NONE;
free(menu_st->driver_data);
}
menu_st->driver_data = NULL;
break;
case RARCH_MENU_CTL_ENVIRONMENT:
{
menu_ctx_environment_t *menu_environ =
(menu_ctx_environment_t*)data;
if (menu_st->driver_ctx->environ_cb)
{
if (menu_st->driver_ctx->environ_cb(menu_environ->type,
menu_environ->data, menu_st->userdata) == 0)
return true;
}
}
return false;
case RARCH_MENU_CTL_POINTER_DOWN:
{
menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data;
if (!menu_st->driver_ctx || !menu_st->driver_ctx->pointer_down)
{
point->retcode = 0;
return false;
}
point->retcode = menu_st->driver_ctx->pointer_down(
menu_st->userdata,
point->x, point->y, point->ptr,
point->cbs, point->entry, point->action);
}
break;
case RARCH_MENU_CTL_POINTER_UP:
{
menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data;
if (!menu_st->driver_ctx || !menu_st->driver_ctx->pointer_up)
{
point->retcode = 0;
return false;
}
point->retcode = menu_st->driver_ctx->pointer_up(
menu_st->userdata,
point->x, point->y, point->ptr,
point->gesture,
point->cbs, point->entry, point->action);
}
break;
case RARCH_MENU_CTL_OSK_PTR_AT_POS:
{
unsigned width = 0;
unsigned height = 0;
menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data;
if (!menu_st->driver_ctx || !menu_st->driver_ctx->osk_ptr_at_pos)
{
point->retcode = 0;
return false;
}
video_driver_get_size(&width, &height);
point->retcode = menu_st->driver_ctx->osk_ptr_at_pos(
menu_st->userdata,
point->x, point->y, width, height);
}
break;
case RARCH_MENU_CTL_UPDATE_THUMBNAIL_PATH:
{
size_t selection = menu_st->selection_ptr;
if (!menu_st->driver_ctx || !menu_st->driver_ctx->update_thumbnail_path)
return false;
menu_st->driver_ctx->update_thumbnail_path(
menu_st->userdata, (unsigned)selection, 'L');
menu_st->driver_ctx->update_thumbnail_path(
menu_st->userdata, (unsigned)selection, 'R');
}
break;
case RARCH_MENU_CTL_UPDATE_THUMBNAIL_IMAGE:
{
if (!menu_st->driver_ctx || !menu_st->driver_ctx->update_thumbnail_image)
return false;
menu_st->driver_ctx->update_thumbnail_image(menu_st->userdata);
}
break;
case RARCH_MENU_CTL_REFRESH_THUMBNAIL_IMAGE:
{
unsigned *i = (unsigned*)data;
if (!i || !menu_st->driver_ctx ||
!menu_st->driver_ctx->refresh_thumbnail_image)
return false;
menu_st->driver_ctx->refresh_thumbnail_image(
menu_st->userdata, *i);
}
break;
case RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_PATH:
{
size_t selection = menu_st->selection_ptr;
if ( !menu_st->driver_ctx ||
!menu_st->driver_ctx->update_savestate_thumbnail_path)
return false;
menu_st->driver_ctx->update_savestate_thumbnail_path(
menu_st->userdata, (unsigned)selection);
}
break;
case RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_IMAGE:
if ( !menu_st->driver_ctx ||
!menu_st->driver_ctx->update_savestate_thumbnail_image)
return false;
menu_st->driver_ctx->update_savestate_thumbnail_image(
menu_st->userdata);
break;
case MENU_NAVIGATION_CTL_CLEAR:
{
bool *pending_push = (bool*)data;
/* Always set current selection to first entry */
menu_st->selection_ptr = 0;
/* menu_driver_navigation_set() will be called
* at the next 'push'.
* If a push is *not* pending, have to do it here
* instead */
if (!(*pending_push))
{
menu_driver_navigation_set(true);
if (menu_st->driver_ctx->navigation_clear)
menu_st->driver_ctx->navigation_clear(
menu_st->userdata, *pending_push);
}
}
break;
case MENU_NAVIGATION_CTL_SET_LAST:
{
size_t menu_list_size = menu_st->entries.list ? MENU_LIST_GET_SELECTION(menu_st->entries.list, 0)->size : 0;
size_t new_selection = menu_list_size - 1;
menu_st->selection_ptr = new_selection;
if (menu_st->driver_ctx->navigation_set_last)
menu_st->driver_ctx->navigation_set_last(menu_st->userdata);
}
break;
case MENU_NAVIGATION_CTL_GET_SCROLL_ACCEL:
{
size_t *sel = (size_t*)data;
if (!sel)
return false;
*sel = menu_st->scroll.acceleration;
}
break;
default:
case RARCH_MENU_CTL_NONE:
break;
}
return true;
}
#endif
/**
* find_driver_nonempty:
* @label : string of driver type to be found.
* @i : index of driver.
* @str : identifier name of the found driver
* gets written to this string.
* @len : size of @str.
*
* Find driver based on @label.
*
* Returns: NULL if no driver based on @label found, otherwise
* pointer to driver.
**/
static const void *find_driver_nonempty(
const char *label, int i,
char *s, size_t len)
{
if (string_is_equal(label, "camera_driver"))
{
if (camera_drivers[i])
{
const char *ident = camera_drivers[i]->ident;
strlcpy(s, ident, len);
return camera_drivers[i];
}
}
else if (string_is_equal(label, "location_driver"))
{
if (location_drivers[i])
{
const char *ident = location_drivers[i]->ident;
strlcpy(s, ident, len);
return location_drivers[i];
}
}
#ifdef HAVE_MENU
else if (string_is_equal(label, "menu_driver"))
{
if (menu_ctx_drivers[i])
{
const char *ident = menu_ctx_drivers[i]->ident;
strlcpy(s, ident, len);
return menu_ctx_drivers[i];
}
}
#endif
else if (string_is_equal(label, "input_driver"))
{
if (input_drivers[i])
{
const char *ident = input_drivers[i]->ident;
strlcpy(s, ident, len);
return input_drivers[i];
}
}
else if (string_is_equal(label, "input_joypad_driver"))
{
if (joypad_drivers[i])
{
const char *ident = joypad_drivers[i]->ident;
strlcpy(s, ident, len);
return joypad_drivers[i];
}
}
else if (string_is_equal(label, "video_driver"))
{
if (video_drivers[i])
{
const char *ident = video_drivers[i]->ident;
strlcpy(s, ident, len);
return video_drivers[i];
}
}
else if (string_is_equal(label, "audio_driver"))
{
if (audio_drivers[i])
{
const char *ident = audio_drivers[i]->ident;
strlcpy(s, ident, len);
return audio_drivers[i];
}
}
else if (string_is_equal(label, "record_driver"))
{
if (record_drivers[i])
{
const char *ident = record_drivers[i]->ident;
strlcpy(s, ident, len);
return record_drivers[i];
}
}
else if (string_is_equal(label, "midi_driver"))
{
if (midi_driver_find_handle(i))
{
const char *ident = midi_drivers[i]->ident;
strlcpy(s, ident, len);
return midi_drivers[i];
}
}
else if (string_is_equal(label, "audio_resampler_driver"))
{
if (audio_resampler_driver_find_handle(i))
{
const char *ident = audio_resampler_driver_find_ident(i);
strlcpy(s, ident, len);
return audio_resampler_driver_find_handle(i);
}
}
else if (string_is_equal(label, "bluetooth_driver"))
{
if (bluetooth_drivers[i])
{
const char *ident = bluetooth_drivers[i]->ident;
strlcpy(s, ident, len);
return bluetooth_drivers[i];
}
}
else if (string_is_equal(label, "wifi_driver"))
{
if (wifi_drivers[i])
{
const char *ident = wifi_drivers[i]->ident;
strlcpy(s, ident, len);
return wifi_drivers[i];
}
}
return NULL;
}
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
struct video_shader *menu_shader_get(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (video_shader_any_supported())
if (p_rarch)
return p_rarch->menu_driver_shader;
return NULL;
}
void menu_shader_manager_free(void *data)
{
struct rarch_state *p_rarch = (struct rarch_state*)data;
if (p_rarch->menu_driver_shader)
free(p_rarch->menu_driver_shader);
p_rarch->menu_driver_shader = NULL;
}
/**
* menu_shader_manager_init:
*
* Initializes shader manager.
**/
bool menu_shader_manager_init(void)
{
struct rarch_state *p_rarch = &rarch_st;
enum rarch_shader_type type = RARCH_SHADER_NONE;
bool ret = true;
bool is_preset = false;
const char *path_shader = NULL;
struct video_shader *menu_shader = NULL;
/* We get the shader preset directly from the video driver, so that
* we are in sync with it (it could fail loading an auto-shader)
* If we can't (e.g. get_current_shader is not implemented),
* we'll load retroarch_get_shader_preset() like always */
video_shader_ctx_t shader_info = {0};
video_shader_driver_get_current_shader(&shader_info);
if (shader_info.data)
/* Use the path of the originally loaded preset because it could
* have been a preset with a #reference in it to another preset */
path_shader = shader_info.data->loaded_preset_path;
else
path_shader = retroarch_get_shader_preset();
menu_shader_manager_free(p_rarch);
menu_shader = (struct video_shader*)
calloc(1, sizeof(*menu_shader));
if (!menu_shader)
{
ret = false;
goto end;
}
if (string_is_empty(path_shader))
goto end;
type = video_shader_get_type_from_ext(path_get_extension(path_shader),
&is_preset);
if (!video_shader_is_supported(type))
{
ret = false;
goto end;
}
if (is_preset)
{
if (!video_shader_load_preset_into_shader(path_shader, menu_shader))
{
ret = false;
goto end;
}
menu_shader->modified = false;
}
else
{
strlcpy(menu_shader->pass[0].source.path, path_shader,
sizeof(menu_shader->pass[0].source.path));
menu_shader->passes = 1;
}
end:
p_rarch->menu_driver_shader = menu_shader;
command_event(CMD_EVENT_SHADER_PRESET_LOADED, NULL);
return ret;
}
/**
* menu_shader_manager_set_preset:
* @shader : Shader handle.
* @type : Type of shader.
* @preset_path : Preset path to load from.
* @apply : Whether to apply the shader or just update shader information
*
* Sets shader preset.
**/
bool menu_shader_manager_set_preset(struct video_shader *shader,
enum rarch_shader_type type, const char *preset_path, bool apply)
{
bool refresh = false;
bool ret = false;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = config_get_ptr();
if (apply && !retroarch_apply_shader(p_rarch, settings,
type, preset_path, true))
goto clear;
if (string_is_empty(preset_path))
{
ret = true;
goto clear;
}
/* Load stored Preset into menu on success.
* Used when a preset is directly loaded.
* No point in updating when the Preset was
* created from the menu itself. */
if ( !shader ||
!(video_shader_load_preset_into_shader(preset_path, shader)))
goto end;
RARCH_LOG("Menu shader set to: %s.\n", preset_path);
ret = true;
end:
#ifdef HAVE_MENU
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
#endif
command_event(CMD_EVENT_SHADER_PRESET_LOADED, NULL);
return ret;
clear:
/* We don't want to disable shaders entirely here,
* just reset number of passes
* > Note: Disabling shaders at this point would in
* fact be dangerous, since it changes the number of
* entries in the shader options menu which can in
* turn lead to the menu selection pointer going out
* of bounds. This causes undefined behaviour/segfaults */
menu_shader_manager_clear_num_passes(shader);
command_event(CMD_EVENT_SHADER_PRESET_LOADED, NULL);
return ret;
}
#endif
#ifdef HAVE_DISCORD
bool discord_is_ready(void)
{
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
return discord_st->ready;
}
static char *discord_get_own_username(struct rarch_state *p_rarch)
{
discord_state_t *discord_st = &p_rarch->discord_st;
if (discord_st->ready)
return discord_st->user_name;
return NULL;
}
char *discord_get_own_avatar(void)
{
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
if (discord_st->ready)
return discord_st->user_avatar;
return NULL;
}
bool discord_avatar_is_ready(void)
{
return false;
}
void discord_avatar_set_ready(bool ready)
{
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
discord_st->avatar_ready = ready;
}
#ifdef HAVE_MENU
static bool discord_download_avatar(
discord_state_t *discord_st,
const char* user_id, const char* avatar_id)
{
static char url[PATH_MAX_LENGTH];
static char url_encoded[PATH_MAX_LENGTH];
static char full_path[PATH_MAX_LENGTH];
static char buf[PATH_MAX_LENGTH];
file_transfer_t *transf = NULL;
RARCH_LOG("[DISCORD]: User avatar ID: %s\n", user_id);
fill_pathname_application_special(buf,
sizeof(buf),
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS);
fill_pathname_join(full_path, buf, avatar_id, sizeof(full_path));
strlcpy(discord_st->user_avatar,
avatar_id, sizeof(discord_st->user_avatar));
if (path_is_valid(full_path))
return true;
if (string_is_empty(avatar_id))
return false;
snprintf(url, sizeof(url), "%s/%s/%s" FILE_PATH_PNG_EXTENSION, CDN_URL, user_id, avatar_id);
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
snprintf(buf, sizeof(buf), "%s" FILE_PATH_PNG_EXTENSION, avatar_id);
transf = (file_transfer_t*)malloc(sizeof(*transf));
transf->enum_idx = MENU_ENUM_LABEL_CB_DISCORD_AVATAR;
strlcpy(transf->path, buf, sizeof(transf->path));
transf->user_data = NULL;
RARCH_LOG("[DISCORD]: Downloading avatar from: %s\n", url_encoded);
task_push_http_transfer_file(url_encoded, true, NULL, cb_generic_download, transf);
return false;
}
#endif
static void handle_discord_ready(const DiscordUser* connectedUser)
{
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
strlcpy(discord_st->user_name,
connectedUser->username, sizeof(discord_st->user_name));
RARCH_LOG("[DISCORD]: Connected to user: %s#%s\n",
connectedUser->username,
connectedUser->discriminator);
#ifdef HAVE_MENU
discord_download_avatar(discord_st,
connectedUser->userId, connectedUser->avatar);
#endif
}
static void handle_discord_disconnected(int errcode, const char* message)
{
RARCH_LOG("[DISCORD]: Disconnected (%d: %s)\n", errcode, message);
}
static void handle_discord_error(int errcode, const char* message)
{
RARCH_LOG("[DISCORD]: Error (%d: %s)\n", errcode, message);
}
static void handle_discord_join_cb(retro_task_t *task,
void *task_data, void *user_data, const char *err)
{
char join_hostname[PATH_MAX_LENGTH];
struct netplay_room *room = NULL;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
if (!data || err || !data->data)
goto finish;
data->data = (char*)realloc(data->data, data->len + 1);
data->data[data->len] = '\0';
netplay_rooms_parse(data->data);
room = netplay_room_get(0);
if (room)
{
bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM;
const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address;
unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port;
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
deinit_netplay(p_rarch);
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
snprintf(join_hostname, sizeof(join_hostname), "%s|%d",
srv_address, srv_port);
RARCH_LOG("[DISCORD]: Joining lobby at: %s\n", join_hostname);
task_push_netplay_crc_scan(room->gamecrc,
room->gamename, join_hostname, room->corename, room->subsystem_name);
discord_st->connecting = true;
if (discord_st->ready)
discord_update(DISCORD_PRESENCE_NETPLAY_CLIENT);
}
finish:
if (err)
RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), err);
if (user_data)
free(user_data);
}
static void handle_discord_join(const char* secret)
{
char url[2048];
struct string_list *list = string_split(secret, "|");
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
strlcpy(discord_st->peer_party_id,
list->elems[0].data, sizeof(discord_st->peer_party_id));
snprintf(url, sizeof(url), FILE_PATH_LOBBY_LIBRETRO_URL "%s/",
discord_st->peer_party_id);
RARCH_LOG("[DISCORD]: Querying lobby id: %s at %s\n",
discord_st->peer_party_id, url);
task_push_http_transfer(url, true, NULL, handle_discord_join_cb, NULL);
}
static void handle_discord_spectate(const char* secret)
{
RARCH_LOG("[DISCORD]: Spectate (%s)\n", secret);
}
#ifdef HAVE_MENU
#if 0
static void handle_discord_join_response(void *ignore, const char *line)
{
/* TODO/FIXME: needs in-game widgets */
if (strstr(line, "yes"))
Discord_Respond(user_id, DISCORD_REPLY_YES);
#ifdef HAVE_MENU
menu_input_dialog_end();
retroarch_menu_running_finished(false);
#endif
}
#endif
#endif
static void handle_discord_join_request(const DiscordUser* request)
{
#ifdef HAVE_MENU
#if 0
char buf[PATH_MAX_LENGTH];
menu_input_ctx_line_t line;
#endif
struct rarch_state *p_rarch = &rarch_st;
RARCH_LOG("[DISCORD]: Join request from %s#%s - %s %s\n",
request->username,
request->discriminator,
request->userId,
request->avatar);
discord_download_avatar(&p_rarch->discord_st,
request->userId, request->avatar);
#if 0
/* TODO/FIXME: Needs in-game widgets */
retroarch_menu_running();
memset(&line, 0, sizeof(line));
snprintf(buf, sizeof(buf), "%s %s?",
msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username);
line.label = buf;
line.label_setting = "no_setting";
line.cb = handle_discord_join_response;
/* TODO/FIXME: needs in-game widgets
* TODO/FIXME: bespoke dialog, should show while in-game
* and have a hotkey to accept
* TODO/FIXME: show avatar of the user connecting
*/
if (!menu_input_dialog_start(&line))
return;
#endif
#endif
}
void discord_update(enum discord_presence presence)
{
struct rarch_state *p_rarch = &rarch_st;
discord_state_t *discord_st = &p_rarch->discord_st;
#ifdef HAVE_CHEEVOS
char cheevos_richpresence[256];
#endif
if (presence == discord_st->status)
return;
if (!discord_st->connecting
&&
( presence == DISCORD_PRESENCE_NONE
|| presence == DISCORD_PRESENCE_MENU))
{
memset(&discord_st->presence,
0, sizeof(discord_st->presence));
discord_st->peer_party_id[0] = '\0';
}
switch (presence)
{
case DISCORD_PRESENCE_MENU:
discord_st->presence.details = msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_DISCORD_IN_MENU);
discord_st->presence.largeImageKey = "base";
discord_st->presence.largeImageText = msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_NO_CORE);
discord_st->presence.instance = 0;
break;
case DISCORD_PRESENCE_GAME_PAUSED:
discord_st->presence.smallImageKey = "paused";
discord_st->presence.smallImageText = msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_DISCORD_STATUS_PAUSED);
discord_st->presence.details = msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_DISCORD_IN_GAME_PAUSED);
discord_st->pause_time = time(0);
discord_st->elapsed_time = difftime(discord_st->pause_time,
discord_st->start_time);
discord_st->presence.startTimestamp = discord_st->pause_time;
break;
case DISCORD_PRESENCE_GAME:
{
core_info_t *core_info = NULL;
core_info_get_current_core(&core_info);
if (core_info)
{
const char *system_id =
core_info->system_id
? core_info->system_id
: "core";
const char *label = NULL;
const struct playlist_entry *entry = NULL;
playlist_t *current_playlist = playlist_get_cached();
if (current_playlist)
{
playlist_get_index_by_path(
current_playlist,
path_get(RARCH_PATH_CONTENT),
&entry);
if (entry && !string_is_empty(entry->label))
label = entry->label;
}
if (!label)
label = path_basename(path_get(RARCH_PATH_BASENAME));
discord_st->presence.largeImageKey = system_id;
if (core_info->display_name)
discord_st->presence.largeImageText =
core_info->display_name;
discord_st->start_time = time(0);
if (discord_st->pause_time != 0)
discord_st->start_time = time(0) -
discord_st->elapsed_time;
discord_st->pause_time = 0;
discord_st->elapsed_time = 0;
discord_st->presence.smallImageKey = "playing";
discord_st->presence.smallImageText = msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_DISCORD_STATUS_PLAYING);
discord_st->presence.startTimestamp = discord_st->start_time;
#ifdef HAVE_CHEEVOS
if (rcheevos_get_richpresence(cheevos_richpresence, sizeof(cheevos_richpresence)) > 0)
discord_st->presence.details = cheevos_richpresence;
else
#endif
discord_st->presence.details = msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_DISCORD_IN_GAME);
discord_st->presence.state = label;
discord_st->presence.instance = 0;
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
{
discord_st->peer_party_id[0] = '\0';
discord_st->connecting = false;
discord_st->presence.partyId = NULL;
discord_st->presence.partyMax = 0;
discord_st->presence.partySize = 0;
discord_st->presence.joinSecret = (const char*)'\0';
}
}
}
break;
case DISCORD_PRESENCE_NETPLAY_HOSTING:
{
char join_secret[128];
struct netplay_room *room = &p_rarch->netplay_host_room;
bool host_method_is_mitm = room->host_method == NETPLAY_HOST_METHOD_MITM;
const char *srv_address = host_method_is_mitm ? room->mitm_address : room->address;
unsigned srv_port = host_method_is_mitm ? room->mitm_port : room->port;
if (room->id == 0)
return;
RARCH_LOG("[DISCORD]: Netplay room details: ID=%d"
", Nick=%s IP=%s Port=%d\n",
room->id, room->nickname,
srv_address, srv_port);
snprintf(discord_st->self_party_id,
sizeof(discord_st->self_party_id), "%d", room->id);
snprintf(join_secret,
sizeof(join_secret), "%d|%" PRId64,
room->id, cpu_features_get_time_usec());
discord_st->presence.joinSecret = strdup(join_secret);
#if 0
discord_st->presence.spectateSecret = "SPECSPECSPEC";
#endif
discord_st->presence.partyId = strdup(discord_st->self_party_id);
discord_st->presence.partyMax = 2;
discord_st->presence.partySize = 1;
RARCH_LOG("[DISCORD]: Join secret: %s\n", join_secret);
RARCH_LOG("[DISCORD]: Party ID: %s\n", discord_st->self_party_id);
}
break;
case DISCORD_PRESENCE_NETPLAY_CLIENT:
RARCH_LOG("[DISCORD]: Party ID: %s\n", discord_st->peer_party_id);
discord_st->presence.partyId = strdup(discord_st->peer_party_id);
break;
case DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED:
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) &&
!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_CONNECTED, NULL))
{
discord_st->peer_party_id[0] = '\0';
discord_st->connecting = false;
discord_st->presence.partyId = NULL;
discord_st->presence.partyMax = 0;
discord_st->presence.partySize = 0;
discord_st->presence.joinSecret = (const char*)'\0';
}
}
break;
#ifdef HAVE_CHEEVOS
case DISCORD_PRESENCE_RETROACHIEVEMENTS:
if (discord_st->pause_time)
return;
if (rcheevos_get_richpresence(cheevos_richpresence, sizeof(cheevos_richpresence)) > 0)
discord_st->presence.details = cheevos_richpresence;
presence = DISCORD_PRESENCE_GAME;
break;
#endif
case DISCORD_PRESENCE_SHUTDOWN:
discord_st->presence.partyId = NULL;
discord_st->presence.partyMax = 0;
discord_st->presence.partySize = 0;
discord_st->presence.joinSecret = (const char*)'\0';
discord_st->connecting = false;
default:
break;
}
#ifdef DEBUG
RARCH_LOG("[DISCORD]: Updating (%d)\n", presence);
#endif
Discord_UpdatePresence(&discord_st->presence);
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
discord_st->status = presence;
}
static void discord_init(
discord_state_t *discord_st,
const char *discord_app_id, char *args)
{
DiscordEventHandlers handlers;
#ifdef _WIN32
char full_path[PATH_MAX_LENGTH];
#endif
char command[PATH_MAX_LENGTH];
discord_st->start_time = time(0);
handlers.ready = handle_discord_ready;
handlers.disconnected = handle_discord_disconnected;
handlers.errored = handle_discord_error;
handlers.joinGame = handle_discord_join;
handlers.spectateGame = handle_discord_spectate;
handlers.joinRequest = handle_discord_join_request;
Discord_Initialize(discord_app_id, &handlers, 0, NULL);
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
#ifdef _WIN32
fill_pathname_application_path(full_path, sizeof(full_path));
if (strstr(args, full_path))
strlcpy(command, args, sizeof(command));
else
{
path_basedir(full_path);
strlcpy(command, full_path, sizeof(command));
strlcat(command, args, sizeof(command));
}
#else
strcpy_literal(command, "sh -c ");
strlcat(command, args, sizeof(command));
#endif
RARCH_LOG("[DISCORD]: Registering startup command: %s\n", command);
Discord_Register(discord_app_id, command);
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
discord_st->ready = true;
}
#endif
#ifdef HAVE_NETWORKING
static bool init_netplay_deferred(
struct rarch_state *p_rarch,
const char* server, unsigned port)
{
if (!string_is_empty(server) && port != 0)
{
strlcpy(p_rarch->server_address_deferred, server,
sizeof(p_rarch->server_address_deferred));
p_rarch->server_port_deferred = port;
p_rarch->netplay_client_deferred = true;
}
else
p_rarch->netplay_client_deferred = false;
return p_rarch->netplay_client_deferred;
}
/**
* input_poll_net
*
* Poll the network if necessary.
*/
void input_poll_net(void)
{
struct rarch_state *p_rarch = &rarch_st;
netplay_t *netplay = p_rarch->netplay_data;
if (!netplay_should_skip(netplay) && netplay && netplay->can_poll)
{
netplay->can_poll = false;
netplay_poll(
p_rarch->input_driver_block_libretro_input,
p_rarch->configuration_settings,
netplay);
}
}
/* Netplay polling callbacks */
static void video_frame_net(const void *data, unsigned width,
unsigned height, size_t pitch)
{
struct rarch_state *p_rarch = &rarch_st;
netplay_t *netplay = p_rarch->netplay_data;
if (!netplay_should_skip(netplay))
netplay->cbs.frame_cb(data, width, height, pitch);
}
static void audio_sample_net(int16_t left, int16_t right)
{
struct rarch_state *p_rarch = &rarch_st;
netplay_t *netplay = p_rarch->netplay_data;
if (!netplay_should_skip(netplay) && !netplay->stall)
netplay->cbs.sample_cb(left, right);
}
static size_t audio_sample_batch_net(const int16_t *data, size_t frames)
{
struct rarch_state *p_rarch = &rarch_st;
netplay_t *netplay = p_rarch->netplay_data;
if (!netplay_should_skip(netplay) && !netplay->stall)
return netplay->cbs.sample_batch_cb(data, frames);
return frames;
}
static void netplay_announce_cb(retro_task_t *task,
void *task_data, void *user_data, const char *error)
{
if (task_data)
{
unsigned i, ip_len, port_len;
struct rarch_state *p_rarch = &rarch_st;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
struct netplay_room *host_room = &p_rarch->netplay_host_room;
struct string_list *lines = NULL;
char *mitm_ip = NULL;
char *mitm_port = NULL;
char *buf = NULL;
char *host_string = NULL;
if (data->len == 0)
return;
buf = (char*)calloc(1, data->len + 1);
memcpy(buf, data->data, data->len);
lines = string_split(buf, "\n");
if (lines->size == 0)
{
string_list_free(lines);
free(buf);
return;
}
memset(host_room, 0, sizeof(*host_room));
for (i = 0; i < lines->size; i++)
{
const char *line = lines->elems[i].data;
if (!string_is_empty(line))
{
struct string_list *kv = string_split(line, "=");
const char *key = NULL;
const char *val = NULL;
if (!kv)
continue;
if (kv->size != 2)
{
string_list_free(kv);
continue;
}
key = kv->elems[0].data;
val = kv->elems[1].data;
if (string_is_equal(key, "id"))
sscanf(val, "%i", &host_room->id);
if (string_is_equal(key, "username"))
strlcpy(host_room->nickname, val, sizeof(host_room->nickname));
if (string_is_equal(key, "ip"))
strlcpy(host_room->address, val, sizeof(host_room->address));
if (string_is_equal(key, "mitm_ip"))
{
mitm_ip = strdup(val);
strlcpy(host_room->mitm_address, val, sizeof(host_room->mitm_address));
}
if (string_is_equal(key, "port"))
sscanf(val, "%i", &host_room->port);
if (string_is_equal(key, "mitm_port"))
{
mitm_port = strdup(val);
sscanf(mitm_port, "%i", &host_room->mitm_port);
}
if (string_is_equal(key, "core_name"))
strlcpy(host_room->corename, val, sizeof(host_room->corename));
if (string_is_equal(key, "frontend"))
strlcpy(host_room->frontend, val, sizeof(host_room->frontend));
if (string_is_equal(key, "core_version"))
strlcpy(host_room->coreversion, val, sizeof(host_room->coreversion));
if (string_is_equal(key, "game_name"))
strlcpy(host_room->gamename, val, sizeof(host_room->gamename));
if (string_is_equal(key, "game_crc"))
sscanf(val, "%08d", &host_room->gamecrc);
if (string_is_equal(key, "host_method"))
sscanf(val, "%i", &host_room->host_method);
if (string_is_equal(key, "has_password"))
{
if (string_is_equal_noncase(val, "true") || string_is_equal(val, "1"))
host_room->has_password = true;
else
host_room->has_password = false;
}
if (string_is_equal(key, "has_spectate_password"))
{
if (string_is_equal_noncase(val, "true") || string_is_equal(val, "1"))
host_room->has_spectate_password = true;
else
host_room->has_spectate_password = false;
}
if (string_is_equal(key, "fixed"))
{
if (string_is_equal_noncase(val, "true") || string_is_equal(val, "1"))
host_room->fixed = true;
else
host_room->fixed = false;
}
if (string_is_equal(key, "retroarch_version"))
strlcpy(host_room->retroarch_version, val, sizeof(host_room->retroarch_version));
if (string_is_equal(key, "country"))
strlcpy(host_room->country, val, sizeof(host_room->country));
string_list_free(kv);
}
}
if (mitm_ip && mitm_port)
{
ip_len = (unsigned)strlen(mitm_ip);
port_len = (unsigned)strlen(mitm_port);
/* Enable Netplay client mode */
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
{
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
p_rarch->is_mitm = true;
host_room->host_method = NETPLAY_HOST_METHOD_MITM;
}
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
host_string = (char*)calloc(1, ip_len + port_len + 2);
memcpy(host_string, mitm_ip, ip_len);
memcpy(host_string + ip_len, "|", 1);
memcpy(host_string + ip_len + 1, mitm_port, port_len);
/* Enable Netplay */
command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, (void*)host_string);
command_event(CMD_EVENT_NETPLAY_INIT, (void*)host_string);
free(host_string);
}
#ifdef HAVE_DISCORD
if (discord_is_inited)
{
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_NETPLAY_HOSTING;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
}
#endif
string_list_free(lines);
free(buf);
if (mitm_ip)
free(mitm_ip);
if (mitm_port)
free(mitm_port);
}
}
static void netplay_announce(struct rarch_state *p_rarch)
{
char buf[4600];
char frontend_architecture[PATH_MAX_LENGTH];
char frontend_architecture_tmp[32];
const frontend_ctx_driver_t
*frontend_drv = NULL;
char url[2048] = "http://lobby.libretro.com/add/";
char *username = NULL;
char *corename = NULL;
char *gamename = NULL;
char *subsystemname = NULL;
char *coreversion = NULL;
char *frontend_ident = NULL;
settings_t *settings = p_rarch->configuration_settings;
struct retro_system_info *system = &runloop_state.system.info;
uint32_t content_crc = content_get_crc();
struct string_list *subsystem = path_get_subsystem_list();
frontend_architecture[0] = '\0';
buf[0] = '\0';
if (subsystem)
{
unsigned i;
for (i = 0; i < subsystem->size; i++)
{
strlcat(buf, path_basename(subsystem->elems[i].data), sizeof(buf));
if (i < subsystem->size - 1)
strlcat(buf, "|", sizeof(buf));
}
net_http_urlencode(&gamename, buf);
net_http_urlencode(&subsystemname, path_get(RARCH_PATH_SUBSYSTEM));
content_crc = 0;
}
else
{
const char *base = path_basename(path_get(RARCH_PATH_BASENAME));
net_http_urlencode(&gamename,
!string_is_empty(base) ? base : "N/A");
/* TODO/FIXME - subsystem should be implemented later? */
net_http_urlencode(&subsystemname, "N/A");
}
frontend_drv =
(const frontend_ctx_driver_t*)frontend_driver_get_cpu_architecture_str(
frontend_architecture_tmp, sizeof(frontend_architecture_tmp));
snprintf(frontend_architecture,
sizeof(frontend_architecture),
"%s %s",
frontend_drv->ident,
frontend_architecture_tmp);
#ifdef HAVE_DISCORD
if (discord_is_ready())
net_http_urlencode(&username, discord_get_own_username(p_rarch));
else
#endif
net_http_urlencode(&username, settings->paths.username);
net_http_urlencode(&corename, system->library_name);
net_http_urlencode(&coreversion, system->library_version);
net_http_urlencode(&frontend_ident, frontend_architecture);
buf[0] = '\0';
snprintf(buf, sizeof(buf), "username=%s&core_name=%s&core_version=%s&"
"game_name=%s&game_crc=%08lX&port=%d&mitm_server=%s"
"&has_password=%d&has_spectate_password=%d&force_mitm=%d"
"&retroarch_version=%s&frontend=%s&subsystem_name=%s",
username, corename, coreversion, gamename, (unsigned long)content_crc,
settings->uints.netplay_port,
settings->arrays.netplay_mitm_server,
*settings->paths.netplay_password ? 1 : 0,
*settings->paths.netplay_spectate_password ? 1 : 0,
settings->bools.netplay_use_mitm_server,
PACKAGE_VERSION, frontend_architecture, subsystemname);
task_push_http_post_transfer(url, buf, true, NULL,
netplay_announce_cb, NULL);
if (username)
free(username);
if (corename)
free(corename);
if (gamename)
free(gamename);
if (coreversion)
free(coreversion);
if (frontend_ident)
free(frontend_ident);
}
static int16_t input_state_net(unsigned port, unsigned device,
unsigned idx, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
netplay_t *netplay = p_rarch->netplay_data;
if (netplay)
{
if (netplay_is_alive(netplay))
return netplay_input_state(netplay, port, device, idx, id);
return netplay->cbs.state_cb(port, device, idx, id);
}
return 0;
}
/* ^^^ Netplay polling callbacks */
/**
* netplay_disconnect
* @netplay : pointer to netplay object
*
* Disconnect netplay.
*
* Returns: true (1) if successful. At present, cannot fail.
**/
static void netplay_disconnect(
struct rarch_state *p_rarch,
netplay_t *netplay)
{
size_t i;
for (i = 0; i < netplay->connections_size; i++)
netplay_hangup(netplay, &netplay->connections[i]);
deinit_netplay(p_rarch);
#ifdef HAVE_DISCORD
if (discord_is_inited)
{
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
}
#endif
}
/**
* netplay_pre_frame:
* @netplay : pointer to netplay object
*
* Pre-frame for Netplay.
* Call this before running retro_run().
*
* Returns: true (1) if the frontend is cleared to emulate the frame, false (0)
* if we're stalled or paused
**/
static bool netplay_pre_frame(
struct rarch_state *p_rarch,
bool netplay_public_announce,
bool netplay_use_mitm_server,
netplay_t *netplay)
{
bool sync_stalled = false;
retro_assert(netplay);
if (netplay_public_announce)
{
p_rarch->reannounce++;
if (
(netplay->is_server || p_rarch->is_mitm) &&
(p_rarch->reannounce % 300 == 0))
netplay_announce(p_rarch);
}
/* Make sure that if announcement is turned on mid-game, it gets announced */
else
p_rarch->reannounce = -1;
/* FIXME: This is an ugly way to learn we're not paused anymore */
if (netplay->local_paused)
if (netplay->local_paused != false)
netplay_frontend_paused(netplay, false);
/* Are we ready now? */
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
netplay_try_init_serialization(netplay);
if (netplay->is_server && !netplay_use_mitm_server)
{
#ifdef HAVE_NETPLAYDISCOVERY
/* Advertise our server */
netplay_lan_ad_server(netplay);
#endif
/* NAT traversal if applicable */
if (netplay->nat_traversal &&
!netplay->nat_traversal_task_oustanding &&
netplay->nat_traversal_state.request_outstanding &&
!netplay->nat_traversal_state.have_inet4)
{
struct timeval tmptv = {0};
fd_set fds = netplay->nat_traversal_state.fds;
if (socket_select(netplay->nat_traversal_state.nfds, &fds, NULL, NULL, &tmptv) > 0)
natt_read(&netplay->nat_traversal_state);
#ifndef HAVE_SOCKET_LEGACY
if (!netplay->nat_traversal_state.request_outstanding ||
netplay->nat_traversal_state.have_inet4)
netplay_announce_nat_traversal(netplay);
#endif
}
}
sync_stalled = !netplay_sync_pre_frame(netplay);
/* If we're disconnected, deinitialize */
if (!netplay->is_server && !netplay->connections[0].active)
{
netplay_disconnect(p_rarch, netplay);
return true;
}
if (sync_stalled ||
((!netplay->is_server || (netplay->connected_players>1)) &&
(netplay->stall || netplay->remote_paused)))
{
/* We may have received data even if we're stalled, so run post-frame
* sync */
netplay_sync_post_frame(netplay, true);
return false;
}
return true;
}
static void deinit_netplay(struct rarch_state *p_rarch)
{
if (p_rarch->netplay_data)
{
netplay_free(p_rarch->netplay_data);
p_rarch->netplay_enabled = false;
p_rarch->netplay_is_client = false;
p_rarch->is_mitm = false;
}
p_rarch->netplay_data = NULL;
core_unset_netplay_callbacks();
}
/**
* init_netplay
* @direct_host : Host to connect to directly, if applicable (client only)
* @server : server address to connect to (client only)
* @port : TCP port to host on/connect to
*
* Initializes netplay.
*
* If netplay is already initialized, will return false (0).
*
* Returns: true (1) if successful, otherwise false (0).
**/
static bool init_netplay(
struct rarch_state *p_rarch,
settings_t *settings,
void *direct_host,
const char *server, unsigned port)
{
struct retro_callbacks cbs = {0};
uint64_t serialization_quirks = 0;
uint64_t quirks = 0;
bool _netplay_is_client = p_rarch->netplay_is_client;
bool _netplay_enabled = p_rarch->netplay_enabled;
if (!_netplay_enabled)
return false;
core_set_default_callbacks(&cbs);
if (!core_set_netplay_callbacks())
return false;
/* Map the core's quirks to our quirks */
serialization_quirks = core_serialization_quirks();
/* Quirks we don't support! Just disable everything. */
if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD))
quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_SAVESTATES)
quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_TRANSMISSION)
quirks |= NETPLAY_QUIRK_NO_TRANSMISSION;
if (serialization_quirks & NETPLAY_QUIRK_MAP_INITIALIZATION)
quirks |= NETPLAY_QUIRK_INITIALIZATION;
if (serialization_quirks & NETPLAY_QUIRK_MAP_ENDIAN_DEPENDENT)
quirks |= NETPLAY_QUIRK_ENDIAN_DEPENDENT;
if (serialization_quirks & NETPLAY_QUIRK_MAP_PLATFORM_DEPENDENT)
quirks |= NETPLAY_QUIRK_PLATFORM_DEPENDENT;
if (_netplay_is_client)
{
RARCH_LOG("[Netplay]: %s\n", msg_hash_to_str(MSG_CONNECTING_TO_NETPLAY_HOST));
}
else
{
RARCH_LOG("[Netplay]: %s\n", msg_hash_to_str(MSG_WAITING_FOR_CLIENT));
runloop_msg_queue_push(
msg_hash_to_str(MSG_WAITING_FOR_CLIENT),
0, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (settings->bools.netplay_public_announce)
netplay_announce(p_rarch);
}
p_rarch->netplay_data = (netplay_t*)netplay_new(
_netplay_is_client
? direct_host
: NULL,
_netplay_is_client
? (!p_rarch->netplay_client_deferred
? server
: p_rarch->server_address_deferred)
: NULL,
_netplay_is_client ? (!p_rarch->netplay_client_deferred
? port
: p_rarch->server_port_deferred)
: (port != 0 ? port : RARCH_DEFAULT_PORT),
settings->bools.netplay_stateless_mode,
settings->ints.netplay_check_frames,
&cbs,
settings->bools.netplay_nat_traversal && !settings->bools.netplay_use_mitm_server,
#ifdef HAVE_DISCORD
discord_get_own_username(p_rarch)
? discord_get_own_username(p_rarch)
:
#endif
settings->paths.username,
quirks);
if (p_rarch->netplay_data)
{
if ( p_rarch->netplay_data->is_server
&& !settings->bools.netplay_start_as_spectator)
netplay_toggle_play_spectate(p_rarch->netplay_data);
return true;
}
RARCH_WARN("%s\n", msg_hash_to_str(MSG_NETPLAY_FAILED));
runloop_msg_queue_push(
msg_hash_to_str(MSG_NETPLAY_FAILED),
0, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
return false;
}
/**
* netplay_driver_ctl
*
* Frontend access to Netplay functionality
*/
bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
netplay_t *netplay = p_rarch->netplay_data;
bool ret = true;
if (p_rarch->in_netplay)
return true;
p_rarch->in_netplay = true;
if (!netplay)
{
switch (state)
{
case RARCH_NETPLAY_CTL_ENABLE_SERVER:
p_rarch->netplay_enabled = true;
p_rarch->netplay_is_client = false;
goto done;
case RARCH_NETPLAY_CTL_ENABLE_CLIENT:
p_rarch->netplay_enabled = true;
p_rarch->netplay_is_client = true;
break;
case RARCH_NETPLAY_CTL_DISABLE:
p_rarch->netplay_enabled = false;
#ifdef HAVE_DISCORD
if (discord_is_inited)
{
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
}
#endif
goto done;
case RARCH_NETPLAY_CTL_IS_ENABLED:
ret = p_rarch->netplay_enabled;
goto done;
case RARCH_NETPLAY_CTL_IS_REPLAYING:
case RARCH_NETPLAY_CTL_IS_DATA_INITED:
ret = false;
goto done;
case RARCH_NETPLAY_CTL_IS_SERVER:
ret = p_rarch->netplay_enabled
&& !p_rarch->netplay_is_client;
goto done;
case RARCH_NETPLAY_CTL_IS_CONNECTED:
ret = false;
goto done;
default:
goto done;
}
}
switch (state)
{
case RARCH_NETPLAY_CTL_ENABLE_SERVER:
case RARCH_NETPLAY_CTL_ENABLE_CLIENT:
case RARCH_NETPLAY_CTL_IS_DATA_INITED:
goto done;
case RARCH_NETPLAY_CTL_DISABLE:
ret = false;
goto done;
case RARCH_NETPLAY_CTL_IS_ENABLED:
goto done;
case RARCH_NETPLAY_CTL_IS_REPLAYING:
ret = netplay->is_replay;
goto done;
case RARCH_NETPLAY_CTL_IS_SERVER:
ret = p_rarch->netplay_enabled
&& !p_rarch->netplay_is_client;
goto done;
case RARCH_NETPLAY_CTL_IS_CONNECTED:
ret = netplay->is_connected;
goto done;
case RARCH_NETPLAY_CTL_POST_FRAME:
netplay_post_frame(netplay);
/* If we're disconnected, deinitialize */
if (!netplay->is_server && !netplay->connections[0].active)
netplay_disconnect(p_rarch, netplay);
break;
case RARCH_NETPLAY_CTL_PRE_FRAME:
ret = netplay_pre_frame(p_rarch,
p_rarch->configuration_settings->bools.netplay_public_announce,
p_rarch->configuration_settings->bools.netplay_use_mitm_server,
netplay);
goto done;
case RARCH_NETPLAY_CTL_GAME_WATCH:
netplay_toggle_play_spectate(netplay);
break;
case RARCH_NETPLAY_CTL_PAUSE:
if (netplay->local_paused != true)
netplay_frontend_paused(netplay, true);
break;
case RARCH_NETPLAY_CTL_UNPAUSE:
if (netplay->local_paused != false)
netplay_frontend_paused(netplay, false);
break;
case RARCH_NETPLAY_CTL_LOAD_SAVESTATE:
netplay_load_savestate(netplay, (retro_ctx_serialize_info_t*)data, true);
break;
case RARCH_NETPLAY_CTL_RESET:
netplay_core_reset(netplay);
break;
case RARCH_NETPLAY_CTL_DISCONNECT:
ret = true;
if (netplay)
netplay_disconnect(p_rarch, netplay);
goto done;
case RARCH_NETPLAY_CTL_FINISHED_NAT_TRAVERSAL:
netplay->nat_traversal_task_oustanding = false;
#ifndef HAVE_SOCKET_LEGACY
netplay_announce_nat_traversal(netplay);
#endif
goto done;
case RARCH_NETPLAY_CTL_DESYNC_PUSH:
netplay->desync++;
break;
case RARCH_NETPLAY_CTL_DESYNC_POP:
if (netplay->desync)
{
netplay->desync--;
if (!netplay->desync)
netplay_load_savestate(netplay, NULL, true);
}
break;
default:
case RARCH_NETPLAY_CTL_NONE:
ret = false;
}
done:
p_rarch->in_netplay = false;
return ret;
}
#endif
static void log_counters(
struct retro_perf_counter **counters, unsigned num)
{
unsigned 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);
}
}
}
static void retro_perf_log(void)
{
struct rarch_state *p_rarch = &rarch_st;
RARCH_LOG("[PERF]: Performance counters (libretro):\n");
log_counters(p_rarch->perf_counters_libretro, p_rarch->perf_ptr_libretro);
}
struct retro_perf_counter **retro_get_perf_counter_rarch(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->perf_counters_rarch;
}
struct retro_perf_counter **retro_get_perf_counter_libretro(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->perf_counters_libretro;
}
unsigned retro_get_perf_count_rarch(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->perf_ptr_rarch;
}
unsigned retro_get_perf_count_libretro(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->perf_ptr_libretro;
}
void rarch_perf_register(struct retro_perf_counter *perf)
{
struct rarch_state *p_rarch = &rarch_st;
if (
!runloop_state.perfcnt_enable
|| perf->registered
|| p_rarch->perf_ptr_rarch >= MAX_COUNTERS
)
return;
p_rarch->perf_counters_rarch[p_rarch->perf_ptr_rarch++] = perf;
perf->registered = true;
}
static void performance_counter_register(struct retro_perf_counter *perf)
{
struct rarch_state *p_rarch = &rarch_st;
if (perf->registered || p_rarch->perf_ptr_libretro >= MAX_COUNTERS)
return;
p_rarch->perf_counters_libretro[p_rarch->perf_ptr_libretro++] = perf;
perf->registered = true;
}
struct string_list *dir_list_new_special(const char *input_dir,
enum dir_list_type type, const char *filter,
bool show_hidden_files)
{
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
char ext_shaders[255];
#endif
char ext_name[255];
const char *exts = NULL;
bool recursive = false;
switch (type)
{
case DIR_LIST_AUTOCONFIG:
exts = filter;
break;
case DIR_LIST_CORES:
ext_name[0] = '\0';
if (!frontend_driver_get_core_extension(ext_name, sizeof(ext_name)))
return NULL;
exts = ext_name;
break;
case DIR_LIST_RECURSIVE:
recursive = true;
/* fall-through */
case DIR_LIST_CORE_INFO:
{
core_info_list_t *list = NULL;
core_info_get_list(&list);
if (list)
exts = list->all_ext;
}
break;
case DIR_LIST_SHADERS:
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
{
union string_list_elem_attr attr;
struct string_list str_list;
if (!string_list_initialize(&str_list))
return NULL;
ext_shaders[0] = '\0';
attr.i = 0;
if (video_shader_is_supported(RARCH_SHADER_CG))
{
string_list_append(&str_list, "cgp", attr);
string_list_append(&str_list, "cg", attr);
}
if (video_shader_is_supported(RARCH_SHADER_GLSL))
{
string_list_append(&str_list, "glslp", attr);
string_list_append(&str_list, "glsl", attr);
}
if (video_shader_is_supported(RARCH_SHADER_SLANG))
{
string_list_append(&str_list, "slangp", attr);
string_list_append(&str_list, "slang", attr);
}
string_list_join_concat(ext_shaders, sizeof(ext_shaders), &str_list, "|");
string_list_deinitialize(&str_list);
exts = ext_shaders;
}
break;
#else
return NULL;
#endif
case DIR_LIST_COLLECTIONS:
exts = "lpl";
break;
case DIR_LIST_DATABASES:
exts = "rdb";
break;
case DIR_LIST_PLAIN:
exts = filter;
break;
case DIR_LIST_NONE:
default:
return NULL;
}
return dir_list_new(input_dir, exts, false,
show_hidden_files,
type == DIR_LIST_CORE_INFO, recursive);
}
struct string_list *string_list_new_special(enum string_list_type type,
void *data, unsigned *len, size_t *list_size)
{
union string_list_elem_attr attr;
unsigned i;
struct string_list *s = string_list_new();
if (!s || !len)
goto error;
attr.i = 0;
*len = 0;
switch (type)
{
case STRING_LIST_MENU_DRIVERS:
#ifdef HAVE_MENU
for (i = 0; menu_ctx_drivers[i]; i++)
{
const char *opt = menu_ctx_drivers[i]->ident;
*len += strlen(opt) + 1;
/* Don't allow the user to set menu driver to "null" using the UI.
* Can prevent the user from locking him/herself out of the program. */
if (string_is_not_equal(opt, "null"))
string_list_append(s, opt, attr);
}
break;
#endif
case STRING_LIST_CAMERA_DRIVERS:
for (i = 0; camera_drivers[i]; i++)
{
const char *opt = camera_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_BLUETOOTH_DRIVERS:
#ifdef HAVE_BLUETOOTH
for (i = 0; bluetooth_drivers[i]; i++)
{
const char *opt = bluetooth_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
#endif
case STRING_LIST_WIFI_DRIVERS:
#ifdef HAVE_WIFI
for (i = 0; wifi_drivers[i]; i++)
{
const char *opt = wifi_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
#endif
case STRING_LIST_LOCATION_DRIVERS:
for (i = 0; location_drivers[i]; i++)
{
const char *opt = location_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_AUDIO_DRIVERS:
for (i = 0; audio_drivers[i]; i++)
{
const char *opt = audio_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_AUDIO_RESAMPLER_DRIVERS:
for (i = 0; audio_resampler_driver_find_handle(i); i++)
{
const char *opt = audio_resampler_driver_find_ident(i);
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_VIDEO_DRIVERS:
for (i = 0; video_drivers[i]; i++)
{
const char *opt = video_drivers[i]->ident;
*len += strlen(opt) + 1;
/* Don't allow the user to set video driver to "null" using the UI.
* Can prevent the user from locking him/herself out of the program. */
if (string_is_not_equal(opt, "null"))
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_INPUT_DRIVERS:
for (i = 0; input_drivers[i]; i++)
{
const char *opt = input_drivers[i]->ident;
*len += strlen(opt) + 1;
/* Don't allow the user to set input driver to "null" using the UI.
* Can prevent the user from locking him/herself out of the program. */
if (string_is_not_equal(opt, "null"))
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_INPUT_HID_DRIVERS:
#ifdef HAVE_HID
for (i = 0; hid_drivers[i]; i++)
{
const char *opt = hid_drivers[i]->ident;
*len += strlen(opt) + 1;
/* Don't allow the user to set input HID driver to "null" using the UI.
* Can prevent the user from locking him/herself out of the program. */
if (string_is_not_equal(opt, "null"))
string_list_append(s, opt, attr);
}
#endif
break;
case STRING_LIST_INPUT_JOYPAD_DRIVERS:
for (i = 0; joypad_drivers[i]; i++)
{
const char *opt = joypad_drivers[i]->ident;
*len += strlen(opt) + 1;
/* Don't allow the user to set input joypad driver to "null" using the UI.
* Can prevent the user from locking him/herself out of the program. */
if (string_is_not_equal(opt, "null"))
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_RECORD_DRIVERS:
for (i = 0; record_drivers[i]; i++)
{
const char *opt = record_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_MIDI_DRIVERS:
for (i = 0; midi_driver_find_handle(i); i++)
{
const char *opt = midi_drivers[i]->ident;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
break;
#ifdef HAVE_LAKKA
case STRING_LIST_TIMEZONES:
{
const char *opt = DEFAULT_TIMEZONE;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
FILE *zones_file = popen("grep -v ^# /usr/share/zoneinfo/zone.tab | "
"cut -f3 | "
"sort", "r");
if (zones_file != NULL)
{
char zone_desc[TIMEZONE_LENGTH];
while (fgets(zone_desc, TIMEZONE_LENGTH, zones_file))
{
size_t zone_desc_len = strlen(zone_desc);
if (zone_desc_len > 0)
if (zone_desc[--zone_desc_len] == '\n')
zone_desc[zone_desc_len] = '\0';
if (strlen(zone_desc) > 0)
{
const char *opt = zone_desc;
*len += strlen(opt) + 1;
string_list_append(s, opt, attr);
}
}
pclose(zones_file);
}
}
break;
#endif
case STRING_LIST_NONE:
default:
goto error;
}
return s;
error:
string_list_free(s);
s = NULL;
return NULL;
}
const char *char_list_new_special(enum string_list_type type, void *data)
{
unsigned len = 0;
size_t list_size;
struct string_list *s = string_list_new_special(type, data, &len, &list_size);
char *options = (len > 0) ? (char*)calloc(len, sizeof(char)): NULL;
if (options && s)
string_list_join_concat(options, len, s, "|");
string_list_free(s);
s = NULL;
return options;
}
static void path_set_redirect(struct rarch_state *p_rarch,
settings_t *settings)
{
char content_dir_name[PATH_MAX_LENGTH];
char new_savefile_dir[PATH_MAX_LENGTH];
char new_savestate_dir[PATH_MAX_LENGTH];
global_t *global = &p_rarch->g_extern;
const char *old_savefile_dir = p_rarch->dir_savefile;
const char *old_savestate_dir = p_rarch->dir_savestate;
struct retro_system_info *system = &runloop_state.system.info;
bool sort_savefiles_enable = settings->bools.sort_savefiles_enable;
bool sort_savefiles_by_content_enable = settings->bools.sort_savefiles_by_content_enable;
bool sort_savestates_enable = settings->bools.sort_savestates_enable;
bool sort_savestates_by_content_enable = settings->bools.sort_savestates_by_content_enable;
bool savefiles_in_content_dir = settings->bools.savefiles_in_content_dir;
bool savestates_in_content_dir = settings->bools.savestates_in_content_dir;
content_dir_name[0] = '\0';
new_savefile_dir[0] = '\0';
new_savestate_dir[0] = '\0';
/* Initialize current save directories
* with the values from the config. */
strlcpy(new_savefile_dir, old_savefile_dir, sizeof(new_savefile_dir));
strlcpy(new_savestate_dir, old_savestate_dir, sizeof(new_savestate_dir));
/* Get content directory name, if per-content-directory
* saves/states are enabled */
if ((sort_savefiles_by_content_enable ||
sort_savestates_by_content_enable) &&
!string_is_empty(p_rarch->path_main_basename))
fill_pathname_parent_dir_name(content_dir_name,
p_rarch->path_main_basename, sizeof(content_dir_name));
if (system && !string_is_empty(system->library_name))
{
#ifdef HAVE_MENU
if (!string_is_equal(system->library_name,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE)))
#endif
{
/* Per-core and/or per-content-directory saves */
if ((sort_savefiles_enable || sort_savefiles_by_content_enable)
&& !string_is_empty(old_savefile_dir))
{
/* Append content directory name to save location */
if (sort_savefiles_by_content_enable)
fill_pathname_join(
new_savefile_dir,
old_savefile_dir,
content_dir_name,
sizeof(new_savefile_dir));
/* Append library_name to the save location */
if (sort_savefiles_enable)
fill_pathname_join(
new_savefile_dir,
new_savefile_dir,
system->library_name,
sizeof(new_savefile_dir));
/* If path doesn't exist, try to create it,
* if everything fails revert to the original path. */
if (!path_is_directory(new_savefile_dir))
if (!path_mkdir(new_savefile_dir))
{
RARCH_LOG("%s %s\n",
msg_hash_to_str(MSG_REVERTING_SAVEFILE_DIRECTORY_TO),
old_savefile_dir);
strlcpy(new_savefile_dir, old_savefile_dir, sizeof(new_savefile_dir));
}
}
/* Per-core and/or per-content-directory savestates */
if ((sort_savestates_enable || sort_savestates_by_content_enable)
&& !string_is_empty(old_savestate_dir))
{
/* Append content directory name to savestate location */
if (sort_savestates_by_content_enable)
fill_pathname_join(
new_savestate_dir,
old_savestate_dir,
content_dir_name,
sizeof(new_savestate_dir));
/* Append library_name to the savestate location */
if (sort_savestates_enable)
{
fill_pathname_join(
new_savestate_dir,
new_savestate_dir,
system->library_name,
sizeof(new_savestate_dir));
}
/* If path doesn't exist, try to create it.
* If everything fails, revert to the original path. */
if (!path_is_directory(new_savestate_dir))
if (!path_mkdir(new_savestate_dir))
{
RARCH_LOG("%s %s\n",
msg_hash_to_str(MSG_REVERTING_SAVESTATE_DIRECTORY_TO),
old_savestate_dir);
strlcpy(new_savestate_dir,
old_savestate_dir,
sizeof(new_savestate_dir));
}
}
}
}
/* Set savefile directory if empty to content directory */
if (string_is_empty(new_savefile_dir) || savefiles_in_content_dir)
{
strlcpy(new_savefile_dir, p_rarch->path_main_basename,
sizeof(new_savefile_dir));
path_basedir(new_savefile_dir);
if (string_is_empty(new_savefile_dir))
RARCH_LOG("Cannot resolve save file path.\n",
msg_hash_to_str(MSG_REVERTING_SAVEFILE_DIRECTORY_TO),
new_savefile_dir);
else if (sort_savefiles_enable || sort_savefiles_by_content_enable)
RARCH_LOG("Saving files in content directory is set. This overrides other save file directory settings.\n");
}
/* Set savestate directory if empty based on content directory */
if (string_is_empty(new_savestate_dir) || savestates_in_content_dir)
{
strlcpy(new_savestate_dir, p_rarch->path_main_basename,
sizeof(new_savestate_dir));
path_basedir(new_savestate_dir);
if (string_is_empty(new_savestate_dir))
RARCH_LOG("Cannot resolve save state file path.\n",
msg_hash_to_str(MSG_REVERTING_SAVESTATE_DIRECTORY_TO),
new_savestate_dir);
else if (sort_savestates_enable || sort_savestates_by_content_enable)
RARCH_LOG("Saving save states in content directory is set. This overrides other save state file directory settings.\n");
}
if (global && system && !string_is_empty(system->library_name))
{
bool savefile_is_dir = path_is_directory(new_savefile_dir);
bool savestate_is_dir = path_is_directory(new_savestate_dir);
if (savefile_is_dir)
strlcpy(global->name.savefile, new_savefile_dir,
sizeof(global->name.savefile));
else
savefile_is_dir = path_is_directory(global->name.savefile);
if (savestate_is_dir)
strlcpy(global->name.savestate, new_savestate_dir,
sizeof(global->name.savestate));
else
savestate_is_dir = path_is_directory(global->name.savestate);
if (savefile_is_dir)
{
fill_pathname_dir(global->name.savefile,
!string_is_empty(p_rarch->path_main_basename)
? p_rarch->path_main_basename
: system->library_name,
FILE_PATH_SRM_EXTENSION,
sizeof(global->name.savefile));
RARCH_LOG("[Overrides]: %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
global->name.savefile);
}
if (savestate_is_dir)
{
fill_pathname_dir(global->name.savestate,
!string_is_empty(p_rarch->path_main_basename)
? p_rarch->path_main_basename
: system->library_name,
FILE_PATH_STATE_EXTENSION,
sizeof(global->name.savestate));
RARCH_LOG("[Overrides]: %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVESTATE_TO),
global->name.savestate);
}
#ifdef HAVE_CHEATS
if (path_is_directory(global->name.cheatfile))
{
fill_pathname_dir(global->name.cheatfile,
!string_is_empty(p_rarch->path_main_basename)
? p_rarch->path_main_basename
: system->library_name,
FILE_PATH_CHT_EXTENSION,
sizeof(global->name.cheatfile));
RARCH_LOG("[Overrides]: %s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_CHEATFILE_TO),
global->name.cheatfile);
}
#endif
}
dir_set(RARCH_DIR_CURRENT_SAVEFILE, new_savefile_dir);
dir_set(RARCH_DIR_CURRENT_SAVESTATE, new_savestate_dir);
}
static void path_set_basename(
struct rarch_state *p_rarch,
const char *path)
{
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(p_rarch->path_main_basename);
if (!string_is_empty(p_rarch->path_main_basename))
fill_pathname_dir(p_rarch->path_main_basename, path, "", sizeof(p_rarch->path_main_basename));
#endif
if ((dst = strrchr(p_rarch->path_main_basename, '.')))
*dst = '\0';
}
struct string_list *path_get_subsystem_list(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->subsystem_fullpaths;
}
void path_set_special(char **argv, unsigned num_content)
{
unsigned i;
char str[PATH_MAX_LENGTH];
union string_list_elem_attr attr;
struct string_list subsystem_paths = {0};
struct rarch_state *p_rarch = &rarch_st;
global_t *global = &p_rarch->g_extern;
const char *savestate_dir = p_rarch->current_savestate_dir;
/* First content file is the significant one. */
path_set_basename(p_rarch, argv[0]);
string_list_initialize(&subsystem_paths);
p_rarch->subsystem_fullpaths = string_list_new();
retro_assert(p_rarch->subsystem_fullpaths);
attr.i = 0;
for (i = 0; i < num_content; i++)
{
string_list_append(p_rarch->subsystem_fullpaths, argv[i], attr);
strlcpy(str, argv[i], sizeof(str));
path_remove_extension(str);
string_list_append(&subsystem_paths, path_basename(str), attr);
}
str[0] = '\0';
string_list_join_concat(str, sizeof(str), &subsystem_paths, " + ");
string_list_deinitialize(&subsystem_paths);
/* We defer SRAM path updates until we can resolve it.
* It is more complicated for special content types. */
if (global)
{
bool is_dir = path_is_directory(savestate_dir);
if (is_dir)
strlcpy(global->name.savestate, savestate_dir,
sizeof(global->name.savestate));
else
is_dir = path_is_directory(global->name.savestate);
if (is_dir)
{
fill_pathname_dir(global->name.savestate,
str,
".state",
sizeof(global->name.savestate));
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVESTATE_TO),
global->name.savestate);
}
}
}
static bool path_init_subsystem(struct rarch_state *p_rarch)
{
unsigned i, j;
const struct retro_subsystem_info *info = NULL;
global_t *global = &p_rarch->g_extern;
rarch_system_info_t *system = &runloop_state.system;
bool subsystem_path_empty = path_is_empty(RARCH_PATH_SUBSYSTEM);
const char *savefile_dir = p_rarch->current_savefile_dir;
if (!system || subsystem_path_empty)
return false;
/* For subsystems, we know exactly which RAM types are supported. */
info = libretro_find_subsystem_info(
system->subsystem.data,
system->subsystem.size,
path_get(RARCH_PATH_SUBSYSTEM));
/* We'll handle this error gracefully later. */
if (info)
{
unsigned num_content = MIN(info->num_roms,
subsystem_path_empty ?
0 : (unsigned)p_rarch->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];
path[0] = ext[0] = '\0';
ext[0] = '.';
ext[1] = '\0';
strlcat(ext, mem->extension, sizeof(ext));
strlcpy(savename,
p_rarch->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);
}
}
}
if (global)
{
/* Let other relevant paths be inferred from the main SRAM location. */
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL))
fill_pathname_noext(global->name.savefile,
p_rarch->path_main_basename,
".srm",
sizeof(global->name.savefile));
if (path_is_directory(global->name.savefile))
{
fill_pathname_dir(global->name.savefile,
p_rarch->path_main_basename,
".srm",
sizeof(global->name.savefile));
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_REDIRECTING_SAVEFILE_TO),
global->name.savefile);
}
}
return true;
}
static void path_init_savefile(struct rarch_state *p_rarch)
{
bool should_sram_be_used = p_rarch->rarch_use_sram
&& !p_rarch->rarch_is_sram_save_disabled;
p_rarch->rarch_use_sram = should_sram_be_used;
if (!p_rarch->rarch_use_sram)
{
RARCH_LOG("[SRAM]: %s\n",
msg_hash_to_str(MSG_SRAM_WILL_NOT_BE_SAVED));
return;
}
command_event(CMD_EVENT_AUTOSAVE_INIT, NULL);
}
static void path_init_savefile_internal(
global_t *global,
struct rarch_state *p_rarch)
{
path_deinit_savefile();
path_init_savefile_new();
if (!path_init_subsystem(p_rarch))
path_init_savefile_rtc(global->name.savefile);
}
static void path_fill_names(struct rarch_state *p_rarch)
{
global_t *global = &p_rarch->g_extern;
path_init_savefile_internal(global, p_rarch);
#ifdef HAVE_BSV_MOVIE
if (global)
strlcpy(p_rarch->bsv_movie_state.movie_path,
global->name.savefile,
sizeof(p_rarch->bsv_movie_state.movie_path));
#endif
if (string_is_empty(p_rarch->path_main_basename))
return;
if (global)
{
if (string_is_empty(global->name.ups))
fill_pathname_noext(global->name.ups,
p_rarch->path_main_basename,
".ups",
sizeof(global->name.ups));
if (string_is_empty(global->name.bps))
fill_pathname_noext(global->name.bps,
p_rarch->path_main_basename,
".bps",
sizeof(global->name.bps));
if (string_is_empty(global->name.ips))
fill_pathname_noext(global->name.ips,
p_rarch->path_main_basename,
".ips",
sizeof(global->name.ips));
}
}
char *path_get_ptr(enum rarch_path_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_PATH_CONTENT:
return p_rarch->path_content;
case RARCH_PATH_DEFAULT_SHADER_PRESET:
return p_rarch->path_default_shader_preset;
case RARCH_PATH_BASENAME:
return p_rarch->path_main_basename;
case RARCH_PATH_CORE_OPTIONS:
if (!path_is_empty(RARCH_PATH_CORE_OPTIONS))
return p_rarch->path_core_options_file;
break;
case RARCH_PATH_SUBSYSTEM:
return p_rarch->subsystem_path;
case RARCH_PATH_CONFIG:
if (!path_is_empty(RARCH_PATH_CONFIG))
return p_rarch->path_config_file;
break;
case RARCH_PATH_CONFIG_APPEND:
if (!path_is_empty(RARCH_PATH_CONFIG_APPEND))
return p_rarch->path_config_append_file;
break;
case RARCH_PATH_CORE:
return p_rarch->path_libretro;
case RARCH_PATH_NONE:
case RARCH_PATH_NAMES:
break;
}
return NULL;
}
const char *path_get(enum rarch_path_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_PATH_CONTENT:
return p_rarch->path_content;
case RARCH_PATH_DEFAULT_SHADER_PRESET:
return p_rarch->path_default_shader_preset;
case RARCH_PATH_BASENAME:
return p_rarch->path_main_basename;
case RARCH_PATH_CORE_OPTIONS:
if (!path_is_empty(RARCH_PATH_CORE_OPTIONS))
return p_rarch->path_core_options_file;
break;
case RARCH_PATH_SUBSYSTEM:
return p_rarch->subsystem_path;
case RARCH_PATH_CONFIG:
if (!path_is_empty(RARCH_PATH_CONFIG))
return p_rarch->path_config_file;
break;
case RARCH_PATH_CONFIG_APPEND:
if (!path_is_empty(RARCH_PATH_CONFIG_APPEND))
return p_rarch->path_config_append_file;
break;
case RARCH_PATH_CORE:
return p_rarch->path_libretro;
case RARCH_PATH_NONE:
case RARCH_PATH_NAMES:
break;
}
return NULL;
}
size_t path_get_realsize(enum rarch_path_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_PATH_CONTENT:
return sizeof(p_rarch->path_content);
case RARCH_PATH_DEFAULT_SHADER_PRESET:
return sizeof(p_rarch->path_default_shader_preset);
case RARCH_PATH_BASENAME:
return sizeof(p_rarch->path_main_basename);
case RARCH_PATH_CORE_OPTIONS:
return sizeof(p_rarch->path_core_options_file);
case RARCH_PATH_SUBSYSTEM:
return sizeof(p_rarch->subsystem_path);
case RARCH_PATH_CONFIG:
return sizeof(p_rarch->path_config_file);
case RARCH_PATH_CONFIG_APPEND:
return sizeof(p_rarch->path_config_append_file);
case RARCH_PATH_CORE:
return sizeof(p_rarch->path_libretro);
case RARCH_PATH_NONE:
case RARCH_PATH_NAMES:
break;
}
return 0;
}
static void path_set_names(struct rarch_state *p_rarch,
global_t *global)
{
if (global)
{
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL))
fill_pathname_noext(global->name.savefile,
p_rarch->path_main_basename,
".srm", sizeof(global->name.savefile));
if (!retroarch_override_setting_is_set(
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL))
fill_pathname_noext(global->name.savestate,
p_rarch->path_main_basename,
".state", sizeof(global->name.savestate));
#ifdef HAVE_CHEATS
if (!string_is_empty(p_rarch->path_main_basename))
fill_pathname_noext(global->name.cheatfile,
p_rarch->path_main_basename,
".cht", sizeof(global->name.cheatfile));
#endif
}
}
bool path_set(enum rarch_path_type type, const char *path)
{
struct rarch_state *p_rarch = &rarch_st;
if (!path)
return false;
switch (type)
{
case RARCH_PATH_BASENAME:
strlcpy(p_rarch->path_main_basename, path,
sizeof(p_rarch->path_main_basename));
break;
case RARCH_PATH_NAMES:
path_set_basename(p_rarch, path);
path_set_names(p_rarch, &p_rarch->g_extern);
path_set_redirect(p_rarch, p_rarch->configuration_settings);
break;
case RARCH_PATH_CORE:
strlcpy(p_rarch->path_libretro, path,
sizeof(p_rarch->path_libretro));
break;
case RARCH_PATH_DEFAULT_SHADER_PRESET:
strlcpy(p_rarch->path_default_shader_preset, path,
sizeof(p_rarch->path_default_shader_preset));
break;
case RARCH_PATH_CONFIG_APPEND:
strlcpy(p_rarch->path_config_append_file, path,
sizeof(p_rarch->path_config_append_file));
break;
case RARCH_PATH_CONFIG:
strlcpy(p_rarch->path_config_file, path,
sizeof(p_rarch->path_config_file));
break;
case RARCH_PATH_SUBSYSTEM:
strlcpy(p_rarch->subsystem_path, path,
sizeof(p_rarch->subsystem_path));
break;
case RARCH_PATH_CORE_OPTIONS:
strlcpy(p_rarch->path_core_options_file, path,
sizeof(p_rarch->path_core_options_file));
break;
case RARCH_PATH_CONTENT:
strlcpy(p_rarch->path_content, path,
sizeof(p_rarch->path_content));
break;
case RARCH_PATH_NONE:
break;
}
return true;
}
bool path_is_empty(enum rarch_path_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_PATH_DEFAULT_SHADER_PRESET:
if (string_is_empty(p_rarch->path_default_shader_preset))
return true;
break;
case RARCH_PATH_SUBSYSTEM:
if (string_is_empty(p_rarch->subsystem_path))
return true;
break;
case RARCH_PATH_CONFIG:
if (string_is_empty(p_rarch->path_config_file))
return true;
break;
case RARCH_PATH_CORE_OPTIONS:
if (string_is_empty(p_rarch->path_core_options_file))
return true;
break;
case RARCH_PATH_CONFIG_APPEND:
if (string_is_empty(p_rarch->path_config_append_file))
return true;
break;
case RARCH_PATH_CONTENT:
if (string_is_empty(p_rarch->path_content))
return true;
break;
case RARCH_PATH_CORE:
if (string_is_empty(p_rarch->path_libretro))
return true;
break;
case RARCH_PATH_BASENAME:
if (string_is_empty(p_rarch->path_main_basename))
return true;
break;
case RARCH_PATH_NONE:
case RARCH_PATH_NAMES:
break;
}
return false;
}
void path_clear(enum rarch_path_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_PATH_SUBSYSTEM:
*p_rarch->subsystem_path = '\0';
break;
case RARCH_PATH_CORE:
*p_rarch->path_libretro = '\0';
break;
case RARCH_PATH_CONFIG:
*p_rarch->path_config_file = '\0';
break;
case RARCH_PATH_CONTENT:
*p_rarch->path_content = '\0';
break;
case RARCH_PATH_BASENAME:
*p_rarch->path_main_basename = '\0';
break;
case RARCH_PATH_CORE_OPTIONS:
*p_rarch->path_core_options_file = '\0';
break;
case RARCH_PATH_DEFAULT_SHADER_PRESET:
*p_rarch->path_default_shader_preset = '\0';
break;
case RARCH_PATH_CONFIG_APPEND:
*p_rarch->path_config_append_file = '\0';
break;
case RARCH_PATH_NONE:
case RARCH_PATH_NAMES:
break;
}
}
static void path_clear_all(void)
{
path_clear(RARCH_PATH_CONTENT);
path_clear(RARCH_PATH_CONFIG);
path_clear(RARCH_PATH_CONFIG_APPEND);
path_clear(RARCH_PATH_CORE_OPTIONS);
path_clear(RARCH_PATH_BASENAME);
}
void ram_state_to_file(void)
{
char state_path[PATH_MAX_LENGTH];
if (!content_ram_state_pending())
return;
state_path[0] = '\0';
if (retroarch_get_current_savestate_path(state_path, sizeof(state_path)))
command_event(CMD_EVENT_RAM_STATE_TO_FILE, state_path);
}
bool retroarch_get_current_savestate_path(char *path, size_t len)
{
struct rarch_state *p_rarch = &rarch_st;
const global_t *global = &p_rarch->g_extern;
settings_t *settings = p_rarch->configuration_settings;
int state_slot = settings ? settings->ints.state_slot : 0;
const char *name_savestate = NULL;
if (!path || !global)
return false;
name_savestate = global->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;
}
enum rarch_content_type path_is_media_type(const char *path)
{
char ext_lower[128];
ext_lower[0] = '\0';
strlcpy(ext_lower, path_get_extension(path), sizeof(ext_lower));
string_to_lower(ext_lower);
/* hack, to detect livestreams so the ffmpeg core can be started */
if (string_starts_with_size(path, "udp://", STRLEN_CONST("udp://")) ||
string_starts_with_size(path, "http://", STRLEN_CONST("http://")) ||
string_starts_with_size(path, "https://", STRLEN_CONST("https://")) ||
string_starts_with_size(path, "tcp://", STRLEN_CONST("tcp://")) ||
string_starts_with_size(path, "rtmp://", STRLEN_CONST("rtmp://")) ||
string_starts_with_size(path, "rtp://", STRLEN_CONST("rtp://")))
return RARCH_CONTENT_MOVIE;
switch (msg_hash_to_file_type(msg_hash_calculate(ext_lower)))
{
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
case FILE_TYPE_OGM:
case FILE_TYPE_MKV:
case FILE_TYPE_AVI:
case FILE_TYPE_MP4:
case FILE_TYPE_FLV:
case FILE_TYPE_WEBM:
case FILE_TYPE_3GP:
case FILE_TYPE_3G2:
case FILE_TYPE_F4F:
case FILE_TYPE_F4V:
case FILE_TYPE_MOV:
case FILE_TYPE_WMV:
case FILE_TYPE_MPG:
case FILE_TYPE_MPEG:
case FILE_TYPE_VOB:
case FILE_TYPE_ASF:
case FILE_TYPE_DIVX:
case FILE_TYPE_M2P:
case FILE_TYPE_M2TS:
case FILE_TYPE_PS:
case FILE_TYPE_TS:
case FILE_TYPE_MXF:
return RARCH_CONTENT_MOVIE;
case FILE_TYPE_WMA:
case FILE_TYPE_OGG:
case FILE_TYPE_MP3:
case FILE_TYPE_M4A:
case FILE_TYPE_FLAC:
case FILE_TYPE_WAV:
return RARCH_CONTENT_MUSIC;
#endif
#ifdef HAVE_IMAGEVIEWER
case FILE_TYPE_JPEG:
case FILE_TYPE_PNG:
case FILE_TYPE_TGA:
case FILE_TYPE_BMP:
return RARCH_CONTENT_IMAGE;
#endif
#ifdef HAVE_IBXM
case FILE_TYPE_MOD:
case FILE_TYPE_S3M:
case FILE_TYPE_XM:
return RARCH_CONTENT_MUSIC;
#endif
#ifdef HAVE_GONG
case FILE_TYPE_GONG:
return RARCH_CONTENT_GONG;
#endif
case FILE_TYPE_NONE:
default:
break;
}
return RARCH_CONTENT_NONE;
}
static void path_deinit_subsystem(struct rarch_state *p_rarch)
{
if (p_rarch->subsystem_fullpaths)
string_list_free(p_rarch->subsystem_fullpaths);
p_rarch->subsystem_fullpaths = NULL;
}
/* get size functions */
size_t dir_get_size(enum rarch_dir_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_DIR_SYSTEM:
return sizeof(p_rarch->dir_system);
case RARCH_DIR_SAVESTATE:
return sizeof(p_rarch->dir_savestate);
case RARCH_DIR_CURRENT_SAVESTATE:
return sizeof(p_rarch->current_savestate_dir);
case RARCH_DIR_SAVEFILE:
return sizeof(p_rarch->dir_savefile);
case RARCH_DIR_CURRENT_SAVEFILE:
return sizeof(p_rarch->current_savefile_dir);
case RARCH_DIR_NONE:
break;
}
return 0;
}
/* clear functions */
void dir_clear(enum rarch_dir_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_DIR_SAVEFILE:
*p_rarch->dir_savefile = '\0';
break;
case RARCH_DIR_CURRENT_SAVEFILE:
*p_rarch->current_savefile_dir = '\0';
break;
case RARCH_DIR_SAVESTATE:
*p_rarch->dir_savestate = '\0';
break;
case RARCH_DIR_CURRENT_SAVESTATE:
*p_rarch->current_savestate_dir = '\0';
break;
case RARCH_DIR_SYSTEM:
*p_rarch->dir_system = '\0';
break;
case RARCH_DIR_NONE:
break;
}
}
static void dir_clear_all(void)
{
dir_clear(RARCH_DIR_SYSTEM);
dir_clear(RARCH_DIR_SAVEFILE);
dir_clear(RARCH_DIR_SAVESTATE);
}
/* get ptr functions */
char *dir_get_ptr(enum rarch_dir_type type)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_DIR_SAVEFILE:
return p_rarch->dir_savefile;
case RARCH_DIR_CURRENT_SAVEFILE:
return p_rarch->current_savefile_dir;
case RARCH_DIR_SAVESTATE:
return p_rarch->dir_savestate;
case RARCH_DIR_CURRENT_SAVESTATE:
return p_rarch->current_savestate_dir;
case RARCH_DIR_SYSTEM:
return p_rarch->dir_system;
case RARCH_DIR_NONE:
break;
}
return NULL;
}
void dir_set(enum rarch_dir_type type, const char *path)
{
struct rarch_state *p_rarch = &rarch_st;
switch (type)
{
case RARCH_DIR_CURRENT_SAVEFILE:
strlcpy(p_rarch->current_savefile_dir, path,
sizeof(p_rarch->current_savefile_dir));
break;
case RARCH_DIR_SAVEFILE:
strlcpy(p_rarch->dir_savefile, path,
sizeof(p_rarch->dir_savefile));
break;
case RARCH_DIR_CURRENT_SAVESTATE:
strlcpy(p_rarch->current_savestate_dir, path,
sizeof(p_rarch->current_savestate_dir));
break;
case RARCH_DIR_SAVESTATE:
strlcpy(p_rarch->dir_savestate, path,
sizeof(p_rarch->dir_savestate));
break;
case RARCH_DIR_SYSTEM:
strlcpy(p_rarch->dir_system, path,
sizeof(p_rarch->dir_system));
break;
case RARCH_DIR_NONE:
break;
}
}
void dir_check_defaults(const char *custom_ini_path)
{
size_t i;
/* Early return for people with a custom folder setup
* so it doesn't create unnecessary directories */
if (!string_is_empty(custom_ini_path) &&
path_is_valid(custom_ini_path))
return;
for (i = 0; i < DEFAULT_DIR_LAST; i++)
{
const char *dir_path = g_defaults.dirs[i];
char new_path[PATH_MAX_LENGTH];
if (string_is_empty(dir_path))
continue;
new_path[0] = '\0';
fill_pathname_expand_special(new_path,
dir_path, sizeof(new_path));
if (!path_is_directory(new_path))
path_mkdir(new_path);
}
}
#ifdef HAVE_ACCESSIBILITY
static bool is_accessibility_enabled(bool accessibility_enable,
bool accessibility_enabled)
{
return accessibility_enabled || accessibility_enable;
}
#endif
bool gfx_widgets_ready(void)
{
#ifdef HAVE_GFX_WIDGETS
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->widgets_active;
#else
return false;
#endif
}
#ifdef HAVE_MENU
bool menu_input_dialog_start_search(void)
{
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = p_rarch->configuration_settings;
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
if (!menu)
return false;
menu_st->input_dialog_kb_display = true;
strlcpy(menu_st->input_dialog_kb_label,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH),
sizeof(menu_st->input_dialog_kb_label));
if (p_rarch->keyboard_line.buffer)
free(p_rarch->keyboard_line.buffer);
p_rarch->keyboard_line.buffer = NULL;
p_rarch->keyboard_line.ptr = 0;
p_rarch->keyboard_line.size = 0;
p_rarch->keyboard_line.cb = NULL;
p_rarch->keyboard_line.userdata = NULL;
p_rarch->keyboard_line.enabled = false;
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
(char*)msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH), 10);
#endif
menu_st->input_dialog_keyboard_buffer =
input_keyboard_start_line(menu,
&p_rarch->keyboard_line,
menu_input_search_cb);
/* While reading keyboard line input, we have to block all hotkeys. */
p_rarch->keyboard_mapping_blocked = true;
return true;
}
bool menu_input_dialog_start(menu_input_ctx_line_t *line)
{
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = p_rarch->configuration_settings;
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
if (!line || !menu)
return false;
menu_st->input_dialog_kb_display = true;
/* Only copy over the menu label and setting if they exist. */
if (line->label)
strlcpy(menu_st->input_dialog_kb_label,
line->label,
sizeof(menu_st->input_dialog_kb_label));
if (line->label_setting)
strlcpy(menu_st->input_dialog_kb_label_setting,
line->label_setting,
sizeof(menu_st->input_dialog_kb_label_setting));
menu_st->input_dialog_kb_type = line->type;
menu_st->input_dialog_kb_idx = line->idx;
if (p_rarch->keyboard_line.buffer)
free(p_rarch->keyboard_line.buffer);
p_rarch->keyboard_line.buffer = NULL;
p_rarch->keyboard_line.ptr = 0;
p_rarch->keyboard_line.size = 0;
p_rarch->keyboard_line.cb = NULL;
p_rarch->keyboard_line.userdata = NULL;
p_rarch->keyboard_line.enabled = false;
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
"Keyboard input:", 10);
#endif
menu_st->input_dialog_keyboard_buffer =
input_keyboard_start_line(menu,
&p_rarch->keyboard_line,
line->cb);
/* While reading keyboard line input, we have to block all hotkeys. */
p_rarch->keyboard_mapping_blocked= true;
return true;
}
bool menu_input_dialog_get_display_kb(void)
{
struct menu_state *menu_st = menu_state_get_ptr();
#ifdef HAVE_LIBNX
struct rarch_state *p_rarch = &rarch_st;
SwkbdConfig kbd;
Result rc;
/* Indicates that we are "typing" from the swkbd
* result to RetroArch with repeated calls to input_keyboard_event
* This prevents input_keyboard_event from calling back
* menu_input_dialog_get_display_kb, looping indefinintely */
static bool typing = false;
if (typing)
return false;
/* swkbd only works on "real" titles */
if ( __nx_applet_type != AppletType_Application
&& __nx_applet_type != AppletType_SystemApplication)
return menu_st->input_dialog_kb_display;
if (!menu_st->input_dialog_kb_display)
return false;
rc = swkbdCreate(&kbd, 0);
if (R_SUCCEEDED(rc))
{
unsigned i;
char buf[LIBNX_SWKBD_LIMIT] = {'\0'};
swkbdConfigMakePresetDefault(&kbd);
swkbdConfigSetGuideText(&kbd,
menu_st->input_dialog_kb_label);
rc = swkbdShow(&kbd, buf, sizeof(buf));
swkbdClose(&kbd);
/* RetroArch uses key-by-key input
so we need to simulate it */
typing = true;
for (i = 0; i < LIBNX_SWKBD_LIMIT; i++)
{
/* In case a previous "Enter" press closed the keyboard */
if (!menu_st->input_dialog_kb_display)
break;
if (buf[i] == '\n' || buf[i] == '\0')
input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
else
{
const char *word = &buf[i];
/* input_keyboard_line_append expects a null-terminated
string, so just make one (yes, the touch keyboard is
a list of "null-terminated characters") */
char oldchar = buf[i+1];
buf[i+1] = '\0';
input_keyboard_line_append(&p_rarch->keyboard_line, word);
osk_update_last_codepoint(
&p_rarch->osk_last_codepoint,
&p_rarch->osk_last_codepoint_len,
word);
buf[i+1] = oldchar;
}
}
/* fail-safe */
if (menu_st->input_dialog_kb_display)
input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
typing = false;
libnx_apply_overclock();
return false;
}
libnx_apply_overclock();
#endif /* HAVE_LIBNX */
return menu_st->input_dialog_kb_display;
}
/* Checks if the menu is still running */
bool menu_driver_is_alive(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->menu_driver_alive;
}
#endif
/* MESSAGE QUEUE */
static void retroarch_msg_queue_deinit(void)
{
RUNLOOP_MSG_QUEUE_LOCK(runloop_state);
msg_queue_deinitialize(&runloop_state.msg_queue);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
#ifdef HAVE_THREADS
slock_free(runloop_state.msg_queue_lock);
runloop_state.msg_queue_lock = NULL;
#endif
runloop_state.msg_queue_size = 0;
}
static void retroarch_msg_queue_init(void)
{
retroarch_msg_queue_deinit();
msg_queue_initialize(&runloop_state.msg_queue, 8);
#ifdef HAVE_THREADS
runloop_state.msg_queue_lock = slock_new();
#endif
}
/* COMMAND */
#ifdef HAVE_COMMAND
bool command_get_status(command_t *cmd, const char* arg)
{
char reply[4096] = {0};
bool contentless = false;
bool is_inited = false;
content_get_status(&contentless, &is_inited);
if (!is_inited)
strcpy_literal(reply, "GET_STATUS CONTENTLESS");
else
{
/* add some content info */
const char *status = "PLAYING";
const char *content_name = path_basename(path_get(RARCH_PATH_BASENAME)); /* filename only without ext */
int content_crc32 = content_get_crc();
const char* system_id = NULL;
core_info_t *core_info = NULL;
core_info_get_current_core(&core_info);
if (runloop_state.paused)
status = "PAUSED";
if (core_info)
system_id = core_info->system_id;
if (!system_id)
system_id = runloop_state.system.info.library_name;
snprintf(reply, sizeof(reply), "GET_STATUS %s %s,%s,crc32=%x\n", status, system_id, content_name, content_crc32);
}
cmd->replier(cmd, reply, strlen(reply));
return true;
}
bool command_get_config_param(command_t *cmd, const char* arg)
{
char reply[8192] = {0};
struct rarch_state *p_rarch = &rarch_st;
const char *value = "unsupported";
settings_t *settings = p_rarch->configuration_settings;
bool video_fullscreen = settings->bools.video_fullscreen;
const char *dir_runtime_log = settings->paths.directory_runtime_log;
const char *log_dir = settings->paths.log_dir;
const char *directory_cache = settings->paths.directory_cache;
const char *directory_system = settings->paths.directory_system;
const char *path_username = settings->paths.username;
if (string_is_equal(arg, "video_fullscreen"))
{
if (video_fullscreen)
value = "true";
else
value = "false";
}
else if (string_is_equal(arg, "savefile_directory"))
value = p_rarch->dir_savefile;
else if (string_is_equal(arg, "savestate_directory"))
value = p_rarch->dir_savestate;
else if (string_is_equal(arg, "runtime_log_directory"))
value = dir_runtime_log;
else if (string_is_equal(arg, "log_dir"))
value = log_dir;
else if (string_is_equal(arg, "cache_directory"))
value = directory_cache;
else if (string_is_equal(arg, "system_directory"))
value = directory_system;
else if (string_is_equal(arg, "netplay_nickname"))
value = path_username;
/* TODO: query any string */
snprintf(reply, sizeof(reply), "GET_CONFIG_PARAM %s %s\n", arg, value);
cmd->replier(cmd, reply, strlen(reply));
return true;
}
bool command_read_memory(command_t *cmd, const char *arg)
{
unsigned i;
char* reply = NULL;
char* reply_at = NULL;
const uint8_t* data = NULL;
unsigned int nbytes = 0;
unsigned int alloc_size = 0;
unsigned int address = -1;
unsigned int len = 0;
unsigned int max_bytes = 0;
const rarch_system_info_t* system = &runloop_state.system;
if (sscanf(arg, "%x %u", &address, &nbytes) != 2)
return false;
/* Ensure large enough to return all requested bytes or an error message */
alloc_size = 64 + nbytes * 3;
reply = (char*)malloc(alloc_size);
reply_at = reply + snprintf(reply, alloc_size - 1, "READ_CORE_MEMORY %x", address);
data = command_memory_get_pointer(system, address, &max_bytes, 0, reply_at, alloc_size - strlen(reply));
if (data)
{
if (nbytes > max_bytes)
nbytes = max_bytes;
for (i = 0; i < nbytes; i++)
snprintf(reply_at + 3 * i, 4, " %02X", data[i]);
reply_at[3 * nbytes] = '\n';
len = reply_at + 3 * nbytes + 1 - reply;
}
else
len = strlen(reply);
cmd->replier(cmd, reply, len);
free(reply);
return true;
}
bool command_write_memory(command_t *cmd, const char *arg)
{
unsigned int address = (unsigned int)strtoul(arg, (char**)&arg, 16);
unsigned int max_bytes = 0;
char reply[128] = "";
const rarch_system_info_t
*system = &runloop_state.system;
char *reply_at = reply + snprintf(reply, sizeof(reply) - 1, "WRITE_CORE_MEMORY %x", address);
uint8_t *data = command_memory_get_pointer(system, address, &max_bytes, 1, reply_at, sizeof(reply) - strlen(reply) - 1);
if (data)
{
uint8_t* start = data;
while (*arg && max_bytes > 0)
{
--max_bytes;
*data = strtoul(arg, (char**)&arg, 16);
data++;
}
snprintf(reply_at, sizeof(reply) - strlen(reply) - 1,
" %u\n", (unsigned)(data - start));
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
{
RARCH_LOG("Achievements hardcore mode disabled by WRITE_CORE_MEMORY\n");
rcheevos_pause_hardcore();
}
#endif
}
cmd->replier(cmd, reply, strlen(reply));
return true;
}
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
static bool retroarch_apply_shader(
struct rarch_state *p_rarch,
settings_t *settings,
enum rarch_shader_type type,
const char *preset_path, bool message)
{
char msg[256];
const char *core_name = runloop_state.system.info.library_name;
const char *preset_file = NULL;
#ifdef HAVE_MENU
struct video_shader *shader = menu_shader_get();
#endif
/* Disallow loading shaders when no core is loaded */
if (string_is_empty(core_name))
return false;
if (!string_is_empty(preset_path))
preset_file = path_basename_nocompression(preset_path);
/* TODO/FIXME - This loads the shader into the video driver
* But then we load the shader from disk twice more to put it in the menu
* We need to reconfigure this at some point to only load it once */
if (p_rarch->current_video->set_shader)
{
if ((p_rarch->current_video->set_shader(
p_rarch->video_driver_data, type, preset_path)))
{
configuration_set_bool(settings, settings->bools.video_shader_enable, true);
if (!string_is_empty(preset_path))
{
strlcpy(p_rarch->runtime_shader_preset, preset_path,
sizeof(p_rarch->runtime_shader_preset));
#ifdef HAVE_MENU
/* reflect in shader manager */
if (menu_shader_manager_set_preset(
shader, type, preset_path, false))
shader->modified = false;
#endif
}
else
p_rarch->runtime_shader_preset[0] = '\0';
if (message)
{
/* Display message */
if (preset_file)
snprintf(msg, sizeof(msg),
"%s: \"%s\"",
msg_hash_to_str(MSG_SHADER),
preset_file);
else
snprintf(msg, sizeof(msg),
"%s: %s",
msg_hash_to_str(MSG_SHADER),
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE)
);
#ifdef HAVE_GFX_WIDGETS
if (p_rarch->widgets_active)
gfx_widget_set_generic_message(&p_rarch->dispwidget_st,
msg, 2000);
else
#endif
runloop_msg_queue_push(msg, 1, 120, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_APPLYING_SHADER),
preset_path ? preset_path : "null");
return true;
}
}
#ifdef HAVE_MENU
/* reflect in shader manager */
menu_shader_manager_set_preset(shader, type, NULL, false);
#endif
/* Display error message */
fill_pathname_join_delim(msg,
msg_hash_to_str(MSG_FAILED_TO_APPLY_SHADER_PRESET),
preset_file ? preset_file : "null",
' ',
sizeof(msg));
runloop_msg_queue_push(
msg, 1, 180, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
return false;
}
bool command_set_shader(command_t *cmd, const char *arg)
{
enum rarch_shader_type type = video_shader_parse_type(arg);
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
if (!string_is_empty(arg))
{
if (!video_shader_is_supported(type))
return false;
/* rebase on shader directory */
if (!path_is_absolute(arg))
{
static char abs_arg[PATH_MAX_LENGTH];
const char *ref_path = settings->paths.directory_video_shader;
fill_pathname_join(abs_arg,
ref_path, arg, sizeof(abs_arg));
/* TODO/FIXME - pointer to local variable -
* making abs_arg static for now to workaround this
*/
arg = abs_arg;
}
}
return retroarch_apply_shader(p_rarch, settings, type, arg, true);
}
#endif
/* TRANSLATION */
#ifdef HAVE_TRANSLATE
static bool task_auto_translate_callback(void)
{
bool was_paused = runloop_state.paused;
command_event(CMD_EVENT_AI_SERVICE_CALL, &was_paused);
return true;
}
/* TODO/FIXME - Doesn't currently work. Fix this. */
static bool is_ai_service_speech_running(void)
{
#ifdef HAVE_AUDIOMIXER
enum audio_mixer_state res = audio_driver_mixer_get_stream_state(10);
bool ret = (res == AUDIO_STREAM_STATE_NONE) || (res == AUDIO_STREAM_STATE_STOPPED);
if (!ret)
return true;
#endif
return false;
}
static void task_auto_translate_handler(retro_task_t *task)
{
int *mode_ptr = (int*)task->user_data;
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = p_rarch->configuration_settings;
#endif
if (task_get_cancelled(task))
goto task_finished;
switch (*mode_ptr)
{
case 1: /* Speech Mode */
#ifdef HAVE_AUDIOMIXER
if (!is_ai_service_speech_running())
goto task_finished;
#endif
break;
case 2: /* Narrator Mode */
#ifdef HAVE_ACCESSIBILITY
if (!is_narrator_running(p_rarch, settings->bools.accessibility_enable))
goto task_finished;
#endif
break;
default:
break;
}
return;
task_finished:
if (p_rarch->ai_service_auto == 1)
p_rarch->ai_service_auto = 2;
task_set_finished(task, true);
if (*mode_ptr == 1 || *mode_ptr == 2)
task_auto_translate_callback();
if (task->user_data)
free(task->user_data);
}
static void call_auto_translate_task(
struct rarch_state *p_rarch,
settings_t *settings,
bool *was_paused)
{
int ai_service_mode = settings->uints.ai_service_mode;
/*Image Mode*/
if (ai_service_mode == 0)
{
if (p_rarch->ai_service_auto == 1)
p_rarch->ai_service_auto = 2;
command_event(CMD_EVENT_AI_SERVICE_CALL, was_paused);
}
else /* Speech or Narrator Mode */
{
int* mode = NULL;
retro_task_t *t = task_init();
if (!t)
return;
mode = (int*)malloc(sizeof(int));
*mode = ai_service_mode;
t->handler = task_auto_translate_handler;
t->user_data = mode;
t->mute = true;
task_queue_push(t);
}
}
static void handle_translation_cb(
retro_task_t *task, void *task_data,
void *user_data, const char *error)
{
size_t pitch;
unsigned width, height;
unsigned image_width, image_height;
uint8_t* raw_output_data = NULL;
char* raw_image_file_data = NULL;
struct scaler_ctx* scaler = NULL;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
int new_image_size = 0;
#ifdef HAVE_AUDIOMIXER
int new_sound_size = 0;
#endif
const void* dummy_data = NULL;
void* raw_image_data = NULL;
void* raw_image_data_alpha = NULL;
void* raw_sound_data = NULL;
int retval = 0;
rjson_t* json = NULL;
int json_current_key = 0;
char* err_string = NULL;
char* text_string = NULL;
char* auto_string = NULL;
char* key_string = NULL;
struct rarch_state *p_rarch = &rarch_st;
settings_t* settings = p_rarch->configuration_settings;
bool was_paused = runloop_state.paused;
const enum retro_pixel_format
video_driver_pix_fmt = p_rarch->video_driver_pix_fmt;
#ifdef HAVE_ACCESSIBILITY
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
#ifdef HAVE_GFX_WIDGETS
bool gfx_widgets_paused = p_rarch->gfx_widgets_paused;
/* When auto mode is on, we turn off the overlay
* once we have the result for the next call.*/
if (p_rarch->dispwidget_st.ai_service_overlay_state != 0
&& p_rarch->ai_service_auto == 2)
gfx_widgets_ai_service_overlay_unload(&p_rarch->dispwidget_st);
#endif
#ifdef DEBUG
if (p_rarch->ai_service_auto != 2)
RARCH_LOG("RESULT FROM AI SERVICE...\n");
#endif
if (!data || error || !data->data)
goto finish;
json = rjson_open_buffer(data->data, data->len);
if (!json)
goto finish;
/* Parse JSON body for the image and sound data */
for (;;)
{
static const char* keys[] = { "image", "sound", "text", "error", "auto", "press" };
const char *str = NULL;
size_t str_len = 0;
enum rjson_type json_type = rjson_next(json);
if (json_type == RJSON_DONE || json_type == RJSON_ERROR)
break;
if (json_type != RJSON_STRING)
continue;
if (rjson_get_context_type(json) != RJSON_OBJECT)
continue;
str = rjson_get_string(json, &str_len);
if ((rjson_get_context_count(json) & 1) == 1)
{
int i;
json_current_key = -1;
for (i = 0; i < ARRAY_SIZE(keys); i++)
{
if (string_is_equal(str, keys[i]))
{
json_current_key = i;
break;
}
}
}
else
{
switch (json_current_key)
{
case 0: /* image */
raw_image_file_data = (char*)unbase64(str,
(int)str_len, &new_image_size);
break;
#ifdef HAVE_AUDIOMIXER
case 1: /* sound */
raw_sound_data = (void*)unbase64(str,
(int)str_len, &new_sound_size);
break;
#endif
case 2: /* text */
text_string = strdup(str);
break;
case 3: /* error */
err_string = strdup(str);
break;
case 4: /* auto */
auto_string = strdup(str);
break;
case 5: /* press */
key_string = strdup(str);
break;
}
json_current_key = -1;
}
}
if (string_is_equal(err_string, "No text found."))
{
#ifdef DEBUG
RARCH_LOG("No text found...\n");
#endif
if (text_string)
{
free(text_string);
text_string = NULL;
}
text_string = (char*)malloc(15);
strlcpy(text_string, err_string, 15);
#ifdef HAVE_GFX_WIDGETS
if (gfx_widgets_paused)
{
/* In this case we have to unpause and then repause for a frame */
p_rarch->dispwidget_st.ai_service_overlay_state = 2;
command_event(CMD_EVENT_UNPAUSE, NULL);
}
#endif
}
if ( !raw_image_file_data
&& !raw_sound_data
&& !text_string
&& (p_rarch->ai_service_auto != 2)
&& !key_string)
{
error = "Invalid JSON body.";
goto finish;
}
if (raw_image_file_data)
{
/* Get the video frame dimensions reference */
video_driver_cached_frame_get(&dummy_data, &width, &height, &pitch);
/* try two different modes for text display *
* In the first mode, we use display widget overlays, but they require
* the video poke interface to be able to load image buffers.
*
* The other method is to draw to the video buffer directly, which needs
* a software core to be running. */
#ifdef HAVE_GFX_WIDGETS
if (p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->load_texture
&& p_rarch->video_driver_poke->unload_texture)
{
bool ai_res;
enum image_type_enum image_type;
/* Write to overlay */
if ( raw_image_file_data[0] == 'B' &&
raw_image_file_data[1] == 'M')
image_type = IMAGE_TYPE_BMP;
else if (raw_image_file_data[1] == 'P' &&
raw_image_file_data[2] == 'N' &&
raw_image_file_data[3] == 'G')
image_type = IMAGE_TYPE_PNG;
else
{
RARCH_LOG("Invalid image type returned from server.\n");
goto finish;
}
ai_res = gfx_widgets_ai_service_overlay_load(
&p_rarch->dispwidget_st,
raw_image_file_data, (unsigned)new_image_size,
image_type);
if (!ai_res)
{
RARCH_LOG("Video driver not supported for AI Service.");
runloop_msg_queue_push(
/* msg_hash_to_str(MSG_VIDEO_DRIVER_NOT_SUPPORTED), */
"Video driver not supported.",
1, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
else if (gfx_widgets_paused)
{
/* In this case we have to unpause and then repause for a frame */
#ifdef HAVE_TRANSLATE
/* Unpausing state */
p_rarch->dispwidget_st.ai_service_overlay_state = 2;
#endif
command_event(CMD_EVENT_UNPAUSE, NULL);
}
}
else
#endif
/* Can't use display widget overlays, so try writing to video buffer */
{
/* Write to video buffer directly (software cores only) */
if (raw_image_file_data[0] == 'B' && raw_image_file_data[1] == 'M')
{
/* This is a BMP file coming back. */
/* Get image data (24 bit), and convert to the emulated pixel format */
image_width =
((uint32_t) ((uint8_t)raw_image_file_data[21]) << 24) +
((uint32_t) ((uint8_t)raw_image_file_data[20]) << 16) +
((uint32_t) ((uint8_t)raw_image_file_data[19]) << 8) +
((uint32_t) ((uint8_t)raw_image_file_data[18]) << 0);
image_height =
((uint32_t) ((uint8_t)raw_image_file_data[25]) << 24) +
((uint32_t) ((uint8_t)raw_image_file_data[24]) << 16) +
((uint32_t) ((uint8_t)raw_image_file_data[23]) << 8) +
((uint32_t) ((uint8_t)raw_image_file_data[22]) << 0);
raw_image_data = (void*)malloc(image_width*image_height*3*sizeof(uint8_t));
memcpy(raw_image_data,
raw_image_file_data+54*sizeof(uint8_t),
image_width*image_height*3*sizeof(uint8_t));
}
else if (raw_image_file_data[1] == 'P' && raw_image_file_data[2] == 'N' &&
raw_image_file_data[3] == 'G')
{
rpng_t *rpng = NULL;
/* PNG coming back from the url */
image_width =
((uint32_t) ((uint8_t)raw_image_file_data[16]) << 24)+
((uint32_t) ((uint8_t)raw_image_file_data[17]) << 16)+
((uint32_t) ((uint8_t)raw_image_file_data[18]) << 8)+
((uint32_t) ((uint8_t)raw_image_file_data[19]) << 0);
image_height =
((uint32_t) ((uint8_t)raw_image_file_data[20]) << 24)+
((uint32_t) ((uint8_t)raw_image_file_data[21]) << 16)+
((uint32_t) ((uint8_t)raw_image_file_data[22]) << 8)+
((uint32_t) ((uint8_t)raw_image_file_data[23]) << 0);
rpng = rpng_alloc();
if (!rpng)
{
error = "Can't allocate memory.";
goto finish;
}
rpng_set_buf_ptr(rpng, raw_image_file_data, (size_t)new_image_size);
rpng_start(rpng);
while (rpng_iterate_image(rpng));
do
{
retval = rpng_process_image(rpng, &raw_image_data_alpha,
(size_t)new_image_size, &image_width, &image_height);
} while (retval == IMAGE_PROCESS_NEXT);
/* Returned output from the png processor is an upside down RGBA
* image, so we have to change that to RGB first. This should
* probably be replaced with a scaler call.*/
{
unsigned ui;
int d,tw,th,tc;
d=0;
raw_image_data = (void*)malloc(image_width*image_height*3*sizeof(uint8_t));
for (ui = 0; ui < image_width * image_height * 4; ui++)
{
if (ui % 4 != 3)
{
tc = d%3;
th = image_height-d / (3*image_width)-1;
tw = (d%(image_width*3)) / 3;
((uint8_t*) raw_image_data)[tw*3+th*3*image_width+tc] = ((uint8_t *)raw_image_data_alpha)[ui];
d+=1;
}
}
}
rpng_free(rpng);
}
else
{
RARCH_LOG("Output from URL not a valid file type, or is not supported.\n");
goto finish;
}
scaler = (struct scaler_ctx*)calloc(1, sizeof(struct scaler_ctx));
if (!scaler)
goto finish;
if (dummy_data == RETRO_HW_FRAME_BUFFER_VALID)
{
/*
In this case, we used the viewport to grab the image
and translate it, and we have the translated image in
the raw_image_data buffer.
*/
RARCH_LOG("Hardware frame buffer core, but selected video driver isn't supported.\n");
goto finish;
}
/* The assigned pitch may not be reliable. The width of
the video frame can change during run-time, but the
pitch may not, so we just assign it as the width
times the byte depth.
*/
if (video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888)
{
raw_output_data = (uint8_t*)malloc(width * height * 4 * sizeof(uint8_t));
scaler->out_fmt = SCALER_FMT_ARGB8888;
pitch = width * 4;
scaler->out_stride = width * 4;
}
else
{
raw_output_data = (uint8_t*)malloc(width * height * 2 * sizeof(uint8_t));
scaler->out_fmt = SCALER_FMT_RGB565;
pitch = width * 2;
scaler->out_stride = width * 1;
}
if (!raw_output_data)
goto finish;
scaler->in_fmt = SCALER_FMT_BGR24;
scaler->in_width = image_width;
scaler->in_height = image_height;
scaler->out_width = width;
scaler->out_height = height;
scaler->scaler_type = SCALER_TYPE_POINT;
scaler_ctx_gen_filter(scaler);
scaler->in_stride = -1 * width * 3;
scaler_ctx_scale_direct(scaler, raw_output_data,
(uint8_t*)raw_image_data + (image_height - 1) * width * 3);
video_driver_frame(raw_output_data, image_width, image_height, pitch);
}
}
#ifdef HAVE_AUDIOMIXER
if (raw_sound_data)
{
audio_mixer_stream_params_t params;
params.volume = 1.0f;
params.slot_selection_type = AUDIO_MIXER_SLOT_SELECTION_MANUAL; /* user->slot_selection_type; */
params.slot_selection_idx = 10;
params.stream_type = AUDIO_STREAM_TYPE_SYSTEM; /* user->stream_type; */
params.type = AUDIO_MIXER_TYPE_WAV;
params.state = AUDIO_STREAM_STATE_PLAYING;
params.buf = raw_sound_data;
params.bufsize = new_sound_size;
params.cb = NULL;
params.basename = NULL;
audio_driver_mixer_add_stream(&params);
if (raw_sound_data)
{
free(raw_sound_data);
raw_sound_data = NULL;
}
}
#endif
if (key_string)
{
char key[8];
size_t length = strlen(key_string);
int i = 0;
int start = 0;
char t = ' ';
for (i = 1; i < (int)length; i++)
{
t = key_string[i];
if (i == length-1 || t == ' ' || t == ',')
{
if (i == length-1 && t != ' ' && t!= ',')
i++;
if (i-start > 7)
{
start = i;
continue;
}
strncpy(key, key_string+start, i-start);
key[i-start] = '\0';
#ifdef HAVE_ACCESSIBILITY
#ifdef HAVE_TRANSLATE
if (string_is_equal(key, "b"))
p_rarch->ai_gamepad_state[0] = 2;
if (string_is_equal(key, "y"))
p_rarch->ai_gamepad_state[1] = 2;
if (string_is_equal(key, "select"))
p_rarch->ai_gamepad_state[2] = 2;
if (string_is_equal(key, "start"))
p_rarch->ai_gamepad_state[3] = 2;
if (string_is_equal(key, "up"))
p_rarch->ai_gamepad_state[4] = 2;
if (string_is_equal(key, "down"))
p_rarch->ai_gamepad_state[5] = 2;
if (string_is_equal(key, "left"))
p_rarch->ai_gamepad_state[6] = 2;
if (string_is_equal(key, "right"))
p_rarch->ai_gamepad_state[7] = 2;
if (string_is_equal(key, "a"))
p_rarch->ai_gamepad_state[8] = 2;
if (string_is_equal(key, "x"))
p_rarch->ai_gamepad_state[9] = 2;
if (string_is_equal(key, "l"))
p_rarch->ai_gamepad_state[10] = 2;
if (string_is_equal(key, "r"))
p_rarch->ai_gamepad_state[11] = 2;
if (string_is_equal(key, "l2"))
p_rarch->ai_gamepad_state[12] = 2;
if (string_is_equal(key, "r2"))
p_rarch->ai_gamepad_state[13] = 2;
if (string_is_equal(key, "l3"))
p_rarch->ai_gamepad_state[14] = 2;
if (string_is_equal(key, "r3"))
p_rarch->ai_gamepad_state[15] = 2;
#endif
#endif
if (string_is_equal(key, "pause"))
command_event(CMD_EVENT_PAUSE, NULL);
if (string_is_equal(key, "unpause"))
command_event(CMD_EVENT_UNPAUSE, NULL);
start = i+1;
}
}
}
#ifdef HAVE_ACCESSIBILITY
if (text_string && is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
text_string, 10);
#endif
finish:
if (error)
RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), error);
if (user_data)
free(user_data);
if (json)
rjson_free(json);
if (raw_image_file_data)
free(raw_image_file_data);
if (raw_image_data_alpha)
free(raw_image_data_alpha);
if (raw_image_data)
free(raw_image_data);
if (scaler)
free(scaler);
if (err_string)
free(err_string);
if (text_string)
free(text_string);
if (raw_output_data)
free(raw_output_data);
if (string_is_equal(auto_string, "auto"))
{
if ( (p_rarch->ai_service_auto != 0)
&& !settings->bools.ai_service_pause)
call_auto_translate_task(p_rarch, settings, &was_paused);
}
if (auto_string)
free(auto_string);
if (key_string)
free(key_string);
}
static const char *ai_service_get_str(enum translation_lang id)
{
switch (id)
{
case TRANSLATION_LANG_EN:
return "en";
case TRANSLATION_LANG_ES:
return "es";
case TRANSLATION_LANG_FR:
return "fr";
case TRANSLATION_LANG_IT:
return "it";
case TRANSLATION_LANG_DE:
return "de";
case TRANSLATION_LANG_JP:
return "ja";
case TRANSLATION_LANG_NL:
return "nl";
case TRANSLATION_LANG_CS:
return "cs";
case TRANSLATION_LANG_DA:
return "da";
case TRANSLATION_LANG_SV:
return "sv";
case TRANSLATION_LANG_HR:
return "hr";
case TRANSLATION_LANG_KO:
return "ko";
case TRANSLATION_LANG_ZH_CN:
return "zh-CN";
case TRANSLATION_LANG_ZH_TW:
return "zh-TW";
case TRANSLATION_LANG_CA:
return "ca";
case TRANSLATION_LANG_BG:
return "bg";
case TRANSLATION_LANG_BN:
return "bn";
case TRANSLATION_LANG_EU:
return "eu";
case TRANSLATION_LANG_AZ:
return "az";
case TRANSLATION_LANG_AR:
return "ar";
case TRANSLATION_LANG_AST:
return "ast";
case TRANSLATION_LANG_SQ:
return "sq";
case TRANSLATION_LANG_AF:
return "af";
case TRANSLATION_LANG_EO:
return "eo";
case TRANSLATION_LANG_ET:
return "et";
case TRANSLATION_LANG_TL:
return "tl";
case TRANSLATION_LANG_FI:
return "fi";
case TRANSLATION_LANG_GL:
return "gl";
case TRANSLATION_LANG_KA:
return "ka";
case TRANSLATION_LANG_EL:
return "el";
case TRANSLATION_LANG_GU:
return "gu";
case TRANSLATION_LANG_HT:
return "ht";
case TRANSLATION_LANG_HE:
return "he";
case TRANSLATION_LANG_HI:
return "hi";
case TRANSLATION_LANG_HU:
return "hu";
case TRANSLATION_LANG_IS:
return "is";
case TRANSLATION_LANG_ID:
return "id";
case TRANSLATION_LANG_GA:
return "ga";
case TRANSLATION_LANG_KN:
return "kn";
case TRANSLATION_LANG_LA:
return "la";
case TRANSLATION_LANG_LV:
return "lv";
case TRANSLATION_LANG_LT:
return "lt";
case TRANSLATION_LANG_MK:
return "mk";
case TRANSLATION_LANG_MS:
return "ms";
case TRANSLATION_LANG_MT:
return "mt";
case TRANSLATION_LANG_NO:
return "no";
case TRANSLATION_LANG_FA:
return "fa";
case TRANSLATION_LANG_PL:
return "pl";
case TRANSLATION_LANG_PT:
return "pt";
case TRANSLATION_LANG_RO:
return "ro";
case TRANSLATION_LANG_RU:
return "ru";
case TRANSLATION_LANG_SR:
return "sr";
case TRANSLATION_LANG_SK:
return "sk";
case TRANSLATION_LANG_SL:
return "sl";
case TRANSLATION_LANG_SW:
return "sw";
case TRANSLATION_LANG_TA:
return "ta";
case TRANSLATION_LANG_TE:
return "te";
case TRANSLATION_LANG_TH:
return "th";
case TRANSLATION_LANG_TR:
return "tr";
case TRANSLATION_LANG_UK:
return "uk";
case TRANSLATION_LANG_UR:
return "ur";
case TRANSLATION_LANG_VI:
return "vi";
case TRANSLATION_LANG_CY:
return "cy";
case TRANSLATION_LANG_YI:
return "yi";
case TRANSLATION_LANG_DONT_CARE:
case TRANSLATION_LANG_LAST:
break;
}
return "";
}
/*
This function does all the stuff needed to translate the game screen,
using the URL given in the settings. Once the image from the frame
buffer is sent to the server, the callback will write the translated
image to the screen.
Supported client/services (thus far)
-VGTranslate client ( www.gitlab.com/spherebeaker/vg_translate )
-Ztranslate client/service ( www.ztranslate.net/docs/service )
To use a client, download the relevant code/release, configure
them, and run them on your local machine, or network. Set the
retroarch configuration to point to your local client (usually
listening on localhost:4404 ) and enable translation service.
If you don't want to run a client, you can also use a service,
which is basically like someone running a client for you. The
downside here is that your retroarch device will have to have
an internet connection, and you may have to sign up for it.
To make your own server, it must listen for a POST request, which
will consist of a JSON body, with the "image" field as a base64
encoded string of a 24bit-BMP/PNG that the will be translated.
The server must output the translated image in the form of a
JSON body, with the "image" field also as a base64 encoded
24bit-BMP, or as an alpha channel png.
"paused" boolean is passed in to indicate if the current call
was made during a paused frame. Due to how the menu widgets work,
if the ai service is called in "auto" mode, then this call will
be made while the menu widgets unpause the core for a frame to update
the on-screen widgets. To tell the ai service what the pause
mode is honestly, we store the runloop_paused variable from before
the handle_translation_cb wipes the widgets, and pass that in here.
*/
static bool run_translation_service(
settings_t *settings,
struct rarch_state *p_rarch,
bool paused)
{
struct video_viewport vp;
uint8_t header[54];
size_t pitch;
unsigned width, height;
const void *data = NULL;
uint8_t *bit24_image = NULL;
uint8_t *bit24_image_prev = NULL;
struct scaler_ctx *scaler = (struct scaler_ctx*)
calloc(1, sizeof(struct scaler_ctx));
bool error = false;
uint8_t *bmp_buffer = NULL;
uint64_t buffer_bytes = 0;
char *bmp64_buffer = NULL;
rjsonwriter_t* jsonwriter = NULL;
const char *json_buffer = NULL;
int bmp64_length = 0;
bool TRANSLATE_USE_BMP = false;
bool use_overlay = false;
const char *label = NULL;
char* system_label = NULL;
core_info_t *core_info = NULL;
const enum retro_pixel_format
video_driver_pix_fmt = p_rarch->video_driver_pix_fmt;
#ifdef HAVE_GFX_WIDGETS
/* For the case when ai service pause is disabled. */
if ( (p_rarch->dispwidget_st.ai_service_overlay_state != 0)
&& (p_rarch->ai_service_auto == 1))
{
gfx_widgets_ai_service_overlay_unload(&p_rarch->dispwidget_st);
goto finish;
}
#endif
#ifdef HAVE_GFX_WIDGETS
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->load_texture
&& p_rarch->video_driver_poke->unload_texture)
use_overlay = true;
#endif
/* get the core info here so we can pass long the game name */
core_info_get_current_core(&core_info);
if (core_info)
{
size_t label_len;
const char *system_id = core_info->system_id
? core_info->system_id : "core";
size_t system_id_len = strlen(system_id);
const struct playlist_entry *entry = NULL;
playlist_t *current_playlist = playlist_get_cached();
if (current_playlist)
{
playlist_get_index_by_path(
current_playlist, path_get(RARCH_PATH_CONTENT), &entry);
if (entry && !string_is_empty(entry->label))
label = entry->label;
}
if (!label)
label = path_basename(path_get(RARCH_PATH_BASENAME));
label_len = strlen(label);
system_label = (char*)malloc(label_len + system_id_len + 3);
memcpy(system_label, system_id, system_id_len);
memcpy(system_label + system_id_len, "__", 2);
memcpy(system_label + 2 + system_id_len, label, label_len);
system_label[system_id_len + 2 + label_len] = '\0';
}
if (!scaler)
goto finish;
video_driver_cached_frame_get(&data, &width, &height, &pitch);
if (!data)
goto finish;
if (data == RETRO_HW_FRAME_BUFFER_VALID)
{
/*
The direct frame capture didn't work, so try getting it
from the viewport instead. This isn't as good as the
raw frame buffer, since the viewport may us bilinear
filtering, or other shaders that will completely trash
the OCR, but it's better than nothing.
*/
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
goto finish;
bit24_image_prev = (uint8_t*)malloc(vp.width * vp.height * 3);
bit24_image = (uint8_t*)malloc(width * height * 3);
if (!bit24_image_prev || !bit24_image)
goto finish;
if (!video_driver_read_viewport(bit24_image_prev, false))
{
RARCH_LOG("Could not read viewport for translation service...\n");
goto finish;
}
/* TODO: Rescale down to regular resolution */
scaler->in_fmt = SCALER_FMT_BGR24;
scaler->out_fmt = SCALER_FMT_BGR24;
scaler->scaler_type = SCALER_TYPE_POINT;
scaler->in_width = vp.width;
scaler->in_height = vp.height;
scaler->out_width = width;
scaler->out_height = height;
scaler_ctx_gen_filter(scaler);
scaler->in_stride = vp.width*3;
scaler->out_stride = width*3;
scaler_ctx_scale_direct(scaler, bit24_image, bit24_image_prev);
}
else
{
/* This is a software core, so just change the pixel format to 24-bit. */
bit24_image = (uint8_t*)malloc(width * height * 3);
if (!bit24_image)
goto finish;
if (video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888)
scaler->in_fmt = SCALER_FMT_ARGB8888;
else
scaler->in_fmt = SCALER_FMT_RGB565;
video_frame_convert_to_bgr24(
scaler,
(uint8_t *)bit24_image,
(const uint8_t*)data + ((int)height - 1)*pitch,
width, height,
(int)-pitch);
}
scaler_ctx_gen_reset(scaler);
if (!bit24_image)
{
error = true;
goto finish;
}
if (TRANSLATE_USE_BMP)
{
/*
At this point, we should have a screenshot in the buffer,
so allocate an array to contain the BMP image along with
the BMP header as bytes, and then covert that to a
b64 encoded array for transport in JSON.
*/
form_bmp_header(header, width, height, false);
bmp_buffer = (uint8_t*)malloc(width * height * 3 + 54);
if (!bmp_buffer)
goto finish;
memcpy(bmp_buffer, header, 54 * sizeof(uint8_t));
memcpy(bmp_buffer + 54,
bit24_image,
width * height * 3 * sizeof(uint8_t));
buffer_bytes = sizeof(uint8_t) * (width * height * 3 + 54);
}
else
{
pitch = width * 3;
bmp_buffer = rpng_save_image_bgr24_string(
bit24_image + width * (height-1) * 3,
width, height, (signed)-pitch, &buffer_bytes);
}
bmp64_buffer = base64((void *)bmp_buffer,
sizeof(uint8_t) * buffer_bytes,
&bmp64_length);
if (!bmp64_buffer)
goto finish;
jsonwriter = rjsonwriter_open_memory();
if (!jsonwriter)
goto finish;
rjsonwriter_add_start_object(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string(jsonwriter, "image");
rjsonwriter_add_colon(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string_len(jsonwriter, bmp64_buffer, bmp64_length);
/* Form request... */
if (system_label)
{
rjsonwriter_add_comma(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string(jsonwriter, "label");
rjsonwriter_add_colon(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string(jsonwriter, system_label);
}
rjsonwriter_add_comma(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string(jsonwriter, "state");
rjsonwriter_add_colon(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_start_object(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string(jsonwriter, "paused");
rjsonwriter_add_colon(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_unsigned(jsonwriter, (paused ? 1 : 0));
{
static const char* state_labels[] = { "b", "y", "select", "start", "up", "down", "left", "right", "a", "x", "l", "r", "l2", "r2", "l3", "r3" };
int i;
for (i = 0; i < ARRAY_SIZE(state_labels); i++)
{
rjsonwriter_add_comma(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_string(jsonwriter, state_labels[i]);
rjsonwriter_add_colon(jsonwriter);
rjsonwriter_add_space(jsonwriter);
#ifdef HAVE_ACCESSIBILITY
rjsonwriter_add_unsigned(jsonwriter,
(p_rarch->ai_gamepad_state[i] ? 1 : 0)
);
#else
rjsonwriter_add_unsigned(jsonwriter, 0);
#endif
}
}
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_end_object(jsonwriter);
rjsonwriter_add_space(jsonwriter);
rjsonwriter_add_end_object(jsonwriter);
json_buffer = rjsonwriter_get_memory_buffer(jsonwriter, NULL);
if (!json_buffer)
goto finish; /* ran out of memory */
#ifdef DEBUG
if (p_rarch->ai_service_auto != 2)
RARCH_LOG("Request size: %d\n", bmp64_length);
#endif
{
char new_ai_service_url[PATH_MAX_LENGTH];
char separator = '?';
unsigned ai_service_source_lang = settings->uints.ai_service_source_lang;
unsigned ai_service_target_lang = settings->uints.ai_service_target_lang;
const char *ai_service_url = settings->arrays.ai_service_url;
strlcpy(new_ai_service_url, ai_service_url, sizeof(new_ai_service_url));
/* if query already exists in url, then use &'s instead */
if (strrchr(new_ai_service_url, '?'))
separator = '&';
/* source lang */
if (ai_service_source_lang != TRANSLATION_LANG_DONT_CARE)
{
const char *lang_source = ai_service_get_str(
(enum translation_lang)ai_service_source_lang);
if (!string_is_empty(lang_source))
{
char temp_string[PATH_MAX_LENGTH];
snprintf(temp_string,
sizeof(temp_string),
"%csource_lang=%s", separator, lang_source);
separator = '&';
strlcat(new_ai_service_url,
temp_string, sizeof(new_ai_service_url));
}
}
/* target lang */
if (ai_service_target_lang != TRANSLATION_LANG_DONT_CARE)
{
const char *lang_target = ai_service_get_str(
(enum translation_lang)ai_service_target_lang);
if (!string_is_empty(lang_target))
{
char temp_string[PATH_MAX_LENGTH];
snprintf(temp_string,
sizeof(temp_string),
"%ctarget_lang=%s", separator, lang_target);
separator = '&';
strlcat(new_ai_service_url, temp_string,
sizeof(new_ai_service_url));
}
}
/* mode */
{
char temp_string[PATH_MAX_LENGTH];
const char *mode_chr = NULL;
unsigned ai_service_mode = settings->uints.ai_service_mode;
/*"image" is included for backwards compatability with
* vgtranslate < 1.04 */
temp_string[0] = '\0';
switch (ai_service_mode)
{
case 0:
if (use_overlay)
mode_chr = "image,png,png-a";
else
mode_chr = "image,png";
break;
case 1:
mode_chr = "sound,wav";
break;
case 2:
mode_chr = "text";
break;
case 3:
if (use_overlay)
mode_chr = "image,png,png-a,sound,wav";
else
mode_chr = "image,png,sound,wav";
break;
default:
break;
}
snprintf(temp_string,
sizeof(temp_string),
"%coutput=%s", separator, mode_chr);
separator = '&';
strlcat(new_ai_service_url, temp_string,
sizeof(new_ai_service_url));
}
#ifdef DEBUG
if (p_rarch->ai_service_auto != 2)
RARCH_LOG("SENDING... %s\n", new_ai_service_url);
#endif
task_push_http_post_transfer(new_ai_service_url,
json_buffer, true, NULL, handle_translation_cb, NULL);
}
error = false;
finish:
if (bit24_image_prev)
free(bit24_image_prev);
if (bit24_image)
free(bit24_image);
if (scaler)
free(scaler);
if (bmp_buffer)
free(bmp_buffer);
if (bmp64_buffer)
free(bmp64_buffer);
if (system_label)
free(system_label);
if (jsonwriter)
rjsonwriter_free(jsonwriter);
return !error;
}
#endif
/**
* command_event_disk_control_append_image:
* @path : Path to disk image.
*
* Appends disk image to disk image list.
**/
static bool command_event_disk_control_append_image(
struct rarch_state *p_rarch,
rarch_system_info_t *sys_info,
const char *path)
{
if ( !sys_info ||
!disk_control_append_image(&sys_info->disk_control, path))
return false;
#ifdef HAVE_THREADS
if (p_rarch->rarch_use_sram)
autosave_deinit();
#endif
/* TODO/FIXME: Need to figure out what to do with subsystems case. */
if (path_is_empty(RARCH_PATH_SUBSYSTEM))
{
/* Update paths for our new image.
* If we actually use append_image, we assume that we
* started out in a single disk case, and that this way
* of doing it makes the most sense. */
path_set(RARCH_PATH_NAMES, path);
path_fill_names(p_rarch);
}
command_event(CMD_EVENT_AUTOSAVE_INIT, NULL);
return true;
}
static void command_event_deinit_core(
struct rarch_state *p_rarch,
bool reinit)
{
core_unload_game(p_rarch);
video_driver_set_cached_frame_ptr(NULL);
if (p_rarch->current_core.inited)
{
RARCH_LOG("[Core]: Unloading core..\n");
p_rarch->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_state.fastmotion_override.pending)
{
runloop_apply_fastmotion_override(p_rarch,
&runloop_state, p_rarch->configuration_settings);
runloop_state.fastmotion_override.pending = false;
}
RARCH_LOG("[Core]: Unloading core symbols..\n");
uninit_libretro_symbols(p_rarch, &p_rarch->current_core);
p_rarch->current_core.symbols_inited = false;
/* Restore original refresh rate, if it has been changed
* automatically in SET_SYSTEM_AV_INFO */
if (p_rarch->video_refresh_rate_original)
video_display_server_restore_refresh_rate();
if (reinit)
driver_uninit(p_rarch, DRIVERS_CMD_ALL);
#ifdef HAVE_CONFIGFILE
if (runloop_state.overrides_active)
{
/* Reload the original config */
config_unload_override();
runloop_state.overrides_active = false;
}
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
p_rarch->runtime_shader_preset[0] = '\0';
#endif
if ( runloop_state.remaps_core_active
|| runloop_state.remaps_content_dir_active
|| runloop_state.remaps_game_active
)
{
input_remapping_deinit();
input_remapping_set_defaults(true);
}
else
input_remapping_restore_global_config(true);
}
static bool event_init_content(
settings_t *settings,
struct rarch_state *p_rarch)
{
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
global_t *global = &p_rarch->g_extern;
const enum rarch_core_type current_core_type = p_rarch->current_core_type;
content_get_status(&contentless, &is_inited);
/* TODO/FIXME - just because we have a contentless core does not
* necessarily mean there should be no SRAM, try to find a solution here */
p_rarch->rarch_use_sram = (current_core_type == CORE_TYPE_PLAIN)
&& !contentless;
/* 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 (!contentless)
path_fill_names(p_rarch);
if (!content_init())
return false;
command_event_set_savestate_auto_index(settings, global);
if (event_load_save_files(p_rarch->rarch_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 (global && settings->bools.savestate_auto_load)
command_event_load_auto_state(global);
#ifdef HAVE_BSV_MOVIE
bsv_movie_deinit(p_rarch);
if (bsv_movie_init(p_rarch))
{
/* 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 update_runtime_log(
struct rarch_state *p_rarch,
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(
p_rarch->runtime_content_path,
p_rarch->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,
p_rarch->libretro_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);
}
static void command_event_runtime_log_deinit(
struct rarch_state *p_rarch,
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(
p_rarch->libretro_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 (p_rarch->libretro_core_runtime_usec > 0)
{
/* Per core logging */
if (content_runtime_log)
update_runtime_log(p_rarch, dir_runtime_log, dir_playlist, true);
/* Aggregate logging */
if (content_runtime_log_aggregate)
update_runtime_log(p_rarch, dir_runtime_log, dir_playlist, false);
}
/* Reset runtime + content/core paths, to prevent any
* possibility of duplicate logging */
p_rarch->libretro_core_runtime_usec = 0;
memset(p_rarch->runtime_content_path, 0, sizeof(p_rarch->runtime_content_path));
memset(p_rarch->runtime_core_path, 0, sizeof(p_rarch->runtime_core_path));
}
static void command_event_runtime_log_init(struct rarch_state *p_rarch)
{
const char *content_path = path_get(RARCH_PATH_CONTENT);
const char *core_path = path_get(RARCH_PATH_CORE);
p_rarch->libretro_core_runtime_last = cpu_features_get_time_usec();
p_rarch->libretro_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(p_rarch->runtime_content_path,
0, sizeof(p_rarch->runtime_content_path));
memset(p_rarch->runtime_core_path,
0, sizeof(p_rarch->runtime_core_path));
if (!string_is_empty(content_path))
strlcpy(p_rarch->runtime_content_path,
content_path,
sizeof(p_rarch->runtime_content_path));
if (!string_is_empty(core_path))
strlcpy(p_rarch->runtime_core_path,
core_path,
sizeof(p_rarch->runtime_core_path));
}
static INLINE float retroarch_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));
}
static INLINE float retroarch_get_runloop_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;
}
static bool command_event_init_core(
settings_t *settings,
struct rarch_state *p_rarch,
enum rarch_core_type type)
{
#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_state.system;
if (!init_libretro_symbols(p_rarch,
type, &p_rarch->current_core))
return false;
if (!p_rarch->current_core.retro_run)
p_rarch->current_core.retro_run = retro_run_null;
p_rarch->current_core.symbols_inited = true;
p_rarch->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";
fill_pathname_join_concat_noext(
p_rarch->video_driver_title_buf,
msg_hash_to_str(MSG_PROGRAM),
" ",
sys_info->info.library_name,
sizeof(p_rarch->video_driver_title_buf));
strlcat(p_rarch->video_driver_title_buf, " ",
sizeof(p_rarch->video_driver_title_buf));
strlcat(p_rarch->video_driver_title_buf,
sys_info->info.library_version,
sizeof(p_rarch->video_driver_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_state.overrides_active =
config_load_override(&runloop_state.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 = retroarch_get_runloop_fastforward_ratio(
settings, &runloop_state.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)
p_rarch->shader_presets_need_reload = true;
p_rarch->shader_delay_timer.timer_begin = false; /* not initialized */
p_rarch->shader_delay_timer.timer_end = false; /* not expired */
#endif
/* reset video format to libretro's default */
p_rarch->video_driver_pix_fmt = RETRO_PIXEL_FORMAT_0RGB1555;
p_rarch->current_core.retro_set_environment(rarch_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_state.system);
#endif
/* Per-core saves: reset redirection paths */
path_set_redirect(p_rarch, settings);
video_driver_set_cached_frame_ptr(NULL);
p_rarch->current_core.retro_init();
p_rarch->current_core.inited = true;
/* Attempt to set initial disk index */
disk_control_set_initial_index(
&sys_info->disk_control,
path_get(RARCH_PATH_CONTENT),
p_rarch->current_savefile_dir);
if (!event_init_content(settings, p_rarch))
{
runloop_state.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(p_rarch, poll_type_behavior))
return false;
p_rarch->frame_limit_minimum_time =
retroarch_set_frame_limit(&p_rarch->video_driver_av_info,
fastforward_ratio);
p_rarch->frame_limit_last_time = cpu_features_get_time_usec();
command_event_runtime_log_init(p_rarch);
return true;
}
#ifdef HAVE_CONFIGFILE
/**
* command_event_save_core_config:
*
* Saves a new (core) configuration to a file. Filename is based
* on heuristics to avoid typing.
*
* Returns: true (1) on success, otherwise false (0).
**/
static bool command_event_save_core_config(
const char *dir_menu_config,
const char *rarch_path_config)
{
char msg[128];
char config_name[PATH_MAX_LENGTH];
char config_path[PATH_MAX_LENGTH];
char config_dir[PATH_MAX_LENGTH];
bool found_path = false;
bool overrides_active = false;
const char *core_path = NULL;
msg[0] = '\0';
config_dir[0] = '\0';
if (!string_is_empty(dir_menu_config))
strlcpy(config_dir, dir_menu_config, sizeof(config_dir));
else if (!string_is_empty(rarch_path_config)) /* Fallback */
fill_pathname_basedir(config_dir, rarch_path_config,
sizeof(config_dir));
if (string_is_empty(config_dir))
{
runloop_msg_queue_push(msg_hash_to_str(MSG_CONFIG_DIRECTORY_NOT_SET), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_ERR("[Config]: %s\n", msg_hash_to_str(MSG_CONFIG_DIRECTORY_NOT_SET));
return false;
}
core_path = path_get(RARCH_PATH_CORE);
config_name[0] = '\0';
config_path[0] = '\0';
/* Infer file name based on libretro core. */
if (path_is_valid(core_path))
{
unsigned i;
RARCH_LOG("%s\n", msg_hash_to_str(MSG_USING_CORE_NAME_FOR_NEW_CONFIG));
/* In case of collision, find an alternative name. */
for (i = 0; i < 16; i++)
{
char tmp[64];
fill_pathname_base_noext(
config_name,
core_path,
sizeof(config_name));
fill_pathname_join(config_path, config_dir, config_name,
sizeof(config_path));
if (i)
snprintf(tmp, sizeof(tmp), "-%u.cfg", i);
else
{
tmp[0] = '\0';
strlcpy(tmp, ".cfg", sizeof(tmp));
}
strlcat(config_path, tmp, sizeof(config_path));
if (!path_is_valid(config_path))
{
found_path = true;
break;
}
}
}
if (!found_path)
{
/* Fallback to system time... */
RARCH_WARN("[Config]: %s\n",
msg_hash_to_str(MSG_CANNOT_INFER_NEW_CONFIG_PATH));
fill_dated_filename(config_name, ".cfg", sizeof(config_name));
fill_pathname_join(config_path, config_dir, config_name,
sizeof(config_path));
}
if (runloop_state.overrides_active)
{
/* Overrides block config file saving,
* make it appear as overrides weren't enabled
* for a manual save. */
runloop_state.overrides_active = false;
overrides_active = true;
}
#ifdef HAVE_CONFIGFILE
command_event_save_config(config_path, msg, sizeof(msg));
#endif
if (!string_is_empty(msg))
runloop_msg_queue_push(msg, 1, 180, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
runloop_state.overrides_active = overrides_active;
return true;
}
/**
* event_save_current_config:
*
* Saves current configuration file to disk, and (optionally)
* autosave state.
**/
static void command_event_save_current_config(
enum override_type type)
{
switch (type)
{
case OVERRIDE_NONE:
{
if (path_is_empty(RARCH_PATH_CONFIG))
{
char msg[128];
msg[0] = '\0';
strcpy_literal(msg, "[Config]: Config directory not set, cannot save configuration.");
runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
else
{
char msg[256];
msg[0] = '\0';
command_event_save_config(path_get(RARCH_PATH_CONFIG), msg, sizeof(msg));
runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
break;
case OVERRIDE_GAME:
case OVERRIDE_CORE:
case OVERRIDE_CONTENT_DIR:
{
char msg[128];
msg[0] = '\0';
if (config_save_overrides(type, &runloop_state.system))
{
strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_SAVED_SUCCESSFULLY), sizeof(msg));
/* set overrides to active so the original config can be
restored after closing content */
runloop_state.overrides_active = true;
}
else
strlcpy(msg, msg_hash_to_str(MSG_OVERRIDES_ERROR_SAVING), sizeof(msg));
RARCH_LOG("[Config - Overrides]: %s\n", msg);
runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
}
}
#endif
static bool command_event_main_state(
struct rarch_state *p_rarch,
unsigned cmd)
{
retro_ctx_size_info_t info;
char msg[128];
char state_path[16384];
const global_t *global = &p_rarch->g_extern;
settings_t *settings = p_rarch->configuration_settings;
bool ret = false;
bool push_msg = true;
state_path[0] = msg[0] = '\0';
retroarch_get_current_savestate_path(state_path, sizeof(state_path));
core_serialize_size(&info);
if (info.size)
{
switch (cmd)
{
case CMD_EVENT_SAVE_STATE:
case CMD_EVENT_SAVE_STATE_TO_RAM:
{
bool savestate_auto_index =
settings->bools.savestate_auto_index;
unsigned savestate_max_keep =
settings->uints.savestate_max_keep;
bool frame_time_counter_reset_after_save_state =
settings->bools.frame_time_counter_reset_after_save_state;
if (cmd == CMD_EVENT_SAVE_STATE)
content_save_state(state_path, true, false);
else
content_save_state_to_ram();
/* Clean up excess savestates if necessary */
if (savestate_auto_index && (savestate_max_keep > 0))
command_event_set_savestate_garbage_collect(global,
settings->uints.savestate_max_keep,
settings->bools.show_hidden_files
);
if (frame_time_counter_reset_after_save_state)
p_rarch->video_driver_frame_time_count = 0;
ret = true;
push_msg = false;
}
break;
case CMD_EVENT_LOAD_STATE:
case CMD_EVENT_LOAD_STATE_FROM_RAM:
{
bool res = false;
if (cmd == CMD_EVENT_LOAD_STATE)
res = content_load_state(state_path, false, false);
else
res = content_load_state_from_ram();
if (res)
{
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
{
rcheevos_pause_hardcore();
runloop_msg_queue_push(msg_hash_to_str(MSG_CHEEVOS_HARDCORE_MODE_DISABLED), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
#endif
ret = true;
#ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL);
#endif
{
bool frame_time_counter_reset_after_load_state =
settings->bools.frame_time_counter_reset_after_load_state;
if (frame_time_counter_reset_after_load_state)
p_rarch->video_driver_frame_time_count = 0;
}
}
}
push_msg = false;
break;
case CMD_EVENT_UNDO_LOAD_STATE:
command_event_undo_load_state(msg, sizeof(msg));
ret = true;
break;
case CMD_EVENT_UNDO_SAVE_STATE:
command_event_undo_save_state(msg, sizeof(msg));
ret = true;
break;
}
}
else
strlcpy(msg, msg_hash_to_str(
MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES), sizeof(msg));
if (push_msg)
runloop_msg_queue_push(msg, 2, 180, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
if (!string_is_empty(msg))
RARCH_LOG("%s\n", msg);
return ret;
}
void input_remapping_cache_global_config(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
global_t *global = &p_rarch->g_extern;
unsigned i;
for (i = 0; i < MAX_USERS; i++)
{
global->old_analog_dpad_mode[i] = settings->uints.input_analog_dpad_mode[i];
global->old_libretro_device[i] = settings->uints.input_libretro_device[i];
}
global->old_analog_dpad_mode_set = true;
global->old_libretro_device_set = true;
}
void input_remapping_enable_global_config_restore(void)
{
struct rarch_state *p_rarch = &rarch_st;
global_t *global = &p_rarch->g_extern;
global->remapping_cache_active = true;
}
void input_remapping_restore_global_config(bool clear_cache)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
global_t *global = &p_rarch->g_extern;
unsigned i;
if (!global->remapping_cache_active)
goto end;
for (i = 0; i < MAX_USERS; i++)
{
if (global->old_analog_dpad_mode_set &&
(settings->uints.input_analog_dpad_mode[i] !=
global->old_analog_dpad_mode[i]))
configuration_set_uint(settings,
settings->uints.input_analog_dpad_mode[i],
global->old_analog_dpad_mode[i]);
if (global->old_libretro_device_set &&
(settings->uints.input_libretro_device[i] !=
global->old_libretro_device[i]))
configuration_set_uint(settings,
settings->uints.input_libretro_device[i],
global->old_libretro_device[i]);
}
end:
if (clear_cache)
{
global->old_analog_dpad_mode_set = false;
global->old_libretro_device_set = false;
global->remapping_cache_active = false;
}
}
void input_remapping_update_port_map(void)
{
unsigned i, j;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
unsigned port_map_index[MAX_USERS] = {0};
/* First pass: 'reset' port map */
for (i = 0; i < MAX_USERS; i++)
for (j = 0; j < (MAX_USERS + 1); j++)
settings->uints.input_remap_port_map[i][j] = MAX_USERS;
/* Second pass: assign port indices from
* 'input_remap_ports' */
for (i = 0; i < MAX_USERS; i++)
{
unsigned remap_port = settings->uints.input_remap_ports[i];
if (remap_port < MAX_USERS)
{
/* 'input_remap_port_map' provides a list of
* 'physical' ports for each 'virtual' port
* sampled in input_state().
* (Note: in the following explanation, port
* index starts from 0, rather than the frontend
* display convention of 1)
* For example - the following remap configuration
* will map input devices 0+1 to port 0, and input
* device 2 to port 1
* > input_remap_ports[0] = 0;
* input_remap_ports[1] = 0;
* input_remap_ports[2] = 1;
* This gives a port map of:
* > input_remap_port_map[0] = { 0, 1, MAX_USERS, ... };
* input_remap_port_map[1] = { 2, MAX_USERS, ... }
* input_remap_port_map[2] = { MAX_USERS, ... }
* ...
* A port map value of MAX_USERS indicates the end
* of the 'physical' port list */
settings->uints.input_remap_port_map[remap_port]
[port_map_index[remap_port]] = i;
port_map_index[remap_port]++;
}
}
}
void input_remapping_deinit(void)
{
struct rarch_state *p_rarch = &rarch_st;
global_t *global = &p_rarch->g_extern;
if (global->name.remapfile)
free(global->name.remapfile);
global->name.remapfile = NULL;
runloop_state.remaps_core_active = false;
runloop_state.remaps_content_dir_active = false;
runloop_state.remaps_game_active = false;
}
void input_remapping_set_defaults(bool clear_cache)
{
unsigned i, j;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
for (i = 0; i < MAX_USERS; i++)
{
/* Button/keyboard remaps */
for (j = 0; j < RARCH_FIRST_CUSTOM_BIND; j++)
{
const struct retro_keybind *keybind = &input_config_binds[i][j];
configuration_set_uint(settings,
settings->uints.input_remap_ids[i][j],
keybind ? keybind->id : RARCH_UNMAPPED);
configuration_set_uint(settings,
settings->uints.input_keymapper_ids[i][j], RETROK_UNKNOWN);
}
/* Analog stick remaps */
for (j = RARCH_FIRST_CUSTOM_BIND; j < (RARCH_FIRST_CUSTOM_BIND + 8); j++)
configuration_set_uint(settings,
settings->uints.input_remap_ids[i][j], j);
/* Controller port remaps */
configuration_set_uint(settings,
settings->uints.input_remap_ports[i], i);
}
/* Need to call 'input_remapping_update_port_map()'
* whenever 'settings->uints.input_remap_ports'
* is modified */
input_remapping_update_port_map();
/* Restore 'global' settings that were cached on
* the last core init
* > Prevents remap changes from 'bleeding through'
* into the main config file */
input_remapping_restore_global_config(clear_cache);
}
static bool input_driver_grab_mouse(struct rarch_state *p_rarch)
{
if (!p_rarch->input_driver_state.current_driver || !p_rarch->input_driver_state.current_driver->grab_mouse)
return false;
p_rarch->input_driver_state.current_driver->grab_mouse(p_rarch->input_driver_state.current_data, true);
p_rarch->input_driver_grab_mouse_state = true;
return true;
}
static bool input_driver_ungrab_mouse(struct rarch_state *p_rarch)
{
if (!p_rarch->input_driver_state.current_driver || !p_rarch->input_driver_state.current_driver->grab_mouse)
return false;
p_rarch->input_driver_state.current_driver->grab_mouse(p_rarch->input_driver_state.current_data, false);
p_rarch->input_driver_grab_mouse_state = false;
return true;
}
static void command_event_reinit(struct rarch_state *p_rarch,
const int flags)
{
settings_t *settings = p_rarch->configuration_settings;
input_driver_state_t
*input_driver_st = &p_rarch->input_driver_state;
#ifdef HAVE_MENU
bool video_fullscreen = settings->bools.video_fullscreen;
bool adaptive_vsync = settings->bools.video_adaptive_vsync;
unsigned swap_interval = settings->uints.video_swap_interval;
#endif
enum input_game_focus_cmd_type
game_focus_cmd = GAME_FOCUS_CMD_REAPPLY;
const input_device_driver_t
*joypad = input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_driver_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
video_driver_reinit(flags);
/* Poll input to avoid possibly stale data to corrupt things. */
if ( joypad && joypad->poll)
joypad->poll();
if ( sec_joypad && sec_joypad->poll)
sec_joypad->poll();
if ( p_rarch->input_driver_state.current_driver &&
p_rarch->input_driver_state.current_driver->poll)
p_rarch->input_driver_state.current_driver->poll(p_rarch->input_driver_state.current_data);
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd);
#ifdef HAVE_MENU
p_rarch->dispgfx.framebuf_dirty = true;
if (video_fullscreen)
video_driver_hide_mouse();
if (p_rarch->menu_driver_alive && p_rarch->current_video->set_nonblock_state)
p_rarch->current_video->set_nonblock_state(
p_rarch->video_driver_data, false,
video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
adaptive_vsync,
swap_interval);
#endif
}
static void retroarch_pause_checks(struct rarch_state *p_rarch)
{
#ifdef HAVE_DISCORD
discord_userdata_t userdata;
#endif
bool is_paused = runloop_state.paused;
bool is_idle = runloop_state.idle;
#if defined(HAVE_GFX_WIDGETS)
bool widgets_active = p_rarch->widgets_active;
if (widgets_active)
p_rarch->gfx_widgets_paused = is_paused;
#endif
if (is_paused)
{
RARCH_LOG("[Core]: %s\n", msg_hash_to_str(MSG_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_DISCORD
userdata.status = DISCORD_PRESENCE_GAME_PAUSED;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
#endif
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
#endif
}
else
{
RARCH_LOG("[Core]: %s\n", msg_hash_to_str(MSG_UNPAUSED));
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
#endif
}
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
if (p_rarch->dispwidget_st.ai_service_overlay_state == 1)
gfx_widgets_ai_service_overlay_unload(&p_rarch->dispwidget_st);
#endif
}
static void retroarch_frame_time_free(void)
{
memset(&runloop_state.frame_time, 0,
sizeof(struct retro_frame_time_callback));
runloop_state.frame_time_last = 0;
runloop_state.max_frames = 0;
}
static void retroarch_audio_buffer_status_free(void)
{
memset(&runloop_state.audio_buffer_status, 0,
sizeof(struct retro_audio_buffer_status_callback));
runloop_state.audio_latency = 0;
}
static void retroarch_game_focus_free(input_game_focus_state_t *game_focus_st)
{
/* Ensure that game focus mode is disabled */
if (game_focus_st->enabled)
{
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_OFF;
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd);
}
game_focus_st->enabled = false;
game_focus_st->core_requested = false;
}
static void retroarch_fastmotion_override_free(
struct rarch_state *p_rarch,
runloop_state_t *p_runloop)
{
settings_t *settings = p_rarch->configuration_settings;
float fastforward_ratio = settings->floats.fastforward_ratio;
bool reset_frame_limit = p_runloop->fastmotion_override.current.fastforward &&
(p_runloop->fastmotion_override.current.ratio >= 0.0f) &&
(p_runloop->fastmotion_override.current.ratio != fastforward_ratio);
p_runloop->fastmotion_override.current.ratio = 0.0f;
p_runloop->fastmotion_override.current.fastforward = false;
p_runloop->fastmotion_override.current.notification = false;
p_runloop->fastmotion_override.current.inhibit_toggle = false;
p_runloop->fastmotion_override.next.ratio = 0.0f;
p_runloop->fastmotion_override.next.fastforward = false;
p_runloop->fastmotion_override.next.notification = false;
p_runloop->fastmotion_override.next.inhibit_toggle = false;
p_runloop->fastmotion_override.pending = false;
if (reset_frame_limit)
p_rarch->frame_limit_minimum_time = retroarch_set_frame_limit(
&p_rarch->video_driver_av_info, fastforward_ratio);
}
static void retroarch_core_options_callback_free(runloop_state_t *p_runloop)
{
/* Only a single core options callback is used at present */
p_runloop->core_options_callback.update_display = NULL;
}
static void retroarch_system_info_free(runloop_state_t *runloop_st)
{
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_state.key_event = NULL;
runloop_state.frontend_key_event = NULL;
memset(&runloop_state.system, 0, sizeof(rarch_system_info_t));
}
static bool libretro_get_system_info(
struct rarch_state *p_rarch,
const char *path,
struct retro_system_info *info,
bool *load_no_content);
#ifdef HAVE_RUNAHEAD
static void runahead_clear_variables(struct rarch_state *p_rarch)
{
p_rarch->runahead_save_state_size = 0;
p_rarch->runahead_save_state_size_known = false;
p_rarch->runahead_video_driver_is_active = true;
p_rarch->runahead_available = true;
p_rarch->runahead_secondary_core_available = true;
p_rarch->runahead_force_input_dirty = true;
p_rarch->runahead_last_frame_count = 0;
}
#endif
/**
* command_event:
* @cmd : Event command index.
*
* Performs program event command with index @cmd.
*
* Returns: true (1) on success, otherwise false (0).
**/
bool command_event(enum event_command cmd, void *data)
{
bool boolean = false;
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
settings_t *settings = p_rarch->configuration_settings;
switch (cmd)
{
case CMD_EVENT_SAVE_FILES:
event_save_files(p_rarch->rarch_use_sram);
break;
case CMD_EVENT_OVERLAY_DEINIT:
#ifdef HAVE_OVERLAY
retroarch_overlay_deinit(p_rarch);
#endif
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
/* Because the overlay is a display widget,
* it's going to be written
* over the menu, so we unset it here. */
if (p_rarch->dispwidget_st.ai_service_overlay_state != 0)
gfx_widgets_ai_service_overlay_unload(&p_rarch->dispwidget_st);
#endif
break;
case CMD_EVENT_OVERLAY_INIT:
#ifdef HAVE_OVERLAY
retroarch_overlay_init(p_rarch);
#endif
break;
case CMD_EVENT_CHEAT_INDEX_PLUS:
#ifdef HAVE_CHEATS
cheat_manager_index_next();
#endif
break;
case CMD_EVENT_CHEAT_INDEX_MINUS:
#ifdef HAVE_CHEATS
cheat_manager_index_prev();
#endif
break;
case CMD_EVENT_CHEAT_TOGGLE:
#ifdef HAVE_CHEATS
cheat_manager_toggle();
#endif
break;
case CMD_EVENT_SHADER_NEXT:
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
dir_check_shader(menu_st->driver_data, settings, &p_rarch->dir_shader_list, true, false);
#endif
break;
case CMD_EVENT_SHADER_PREV:
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
dir_check_shader(menu_st->driver_data, settings, &p_rarch->dir_shader_list, false, true);
#endif
break;
case CMD_EVENT_BSV_RECORDING_TOGGLE:
#ifdef HAVE_BSV_MOVIE
if (!recording_is_enabled())
command_event(CMD_EVENT_RECORD_INIT, NULL);
else
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
bsv_movie_check(p_rarch, settings);
#endif
break;
case CMD_EVENT_AI_SERVICE_TOGGLE:
{
#ifdef HAVE_TRANSLATE
bool ai_service_pause = settings->bools.ai_service_pause;
if (!settings->bools.ai_service_enable)
break;
if (ai_service_pause)
{
/* pause on call, unpause on second press. */
if (!runloop_state.paused)
{
command_event(CMD_EVENT_PAUSE, NULL);
command_event(CMD_EVENT_AI_SERVICE_CALL, NULL);
}
else
{
#ifdef HAVE_ACCESSIBILITY
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
(char*)msg_hash_to_str(MSG_UNPAUSED), 10);
#endif
command_event(CMD_EVENT_UNPAUSE, NULL);
}
}
else
{
/* Don't pause - useful for Text-To-Speech since
* the audio can't currently play while paused.
* Also useful for cases when users don't want the
* core's sound to stop while translating.
*
* Also, this mode is required for "auto" translation
* packages, since you don't want to pause for that.
*/
if (p_rarch->ai_service_auto == 2)
{
/* Auto mode was turned on, but we pressed the
* toggle button, so turn it off now. */
p_rarch->ai_service_auto = 0;
#ifdef HAVE_MENU_WIDGETS
gfx_widgets_ai_service_overlay_unload(&p_rarch->dispwidget_st);
#endif
}
else
command_event(CMD_EVENT_AI_SERVICE_CALL, NULL);
}
#endif
break;
}
case CMD_EVENT_STREAMING_TOGGLE:
if (streaming_is_enabled())
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
else
{
streaming_set_state(true);
command_event(CMD_EVENT_RECORD_INIT, NULL);
}
break;
case CMD_EVENT_RUNAHEAD_TOGGLE:
{
char msg[256];
msg[0] = '\0';
settings->bools.run_ahead_enabled =
!(settings->bools.run_ahead_enabled);
if (!settings->bools.run_ahead_enabled)
{
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_DISABLED),
1, 100, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
else if (!settings->bools.run_ahead_secondary_instance)
{
snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_RUNAHEAD_ENABLED),
settings->uints.run_ahead_frames);
runloop_msg_queue_push(
msg, 1, 100, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
else
{
snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_RUNAHEAD_ENABLED_WITH_SECOND_INSTANCE),
settings->uints.run_ahead_frames);
runloop_msg_queue_push(
msg, 1, 100, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
break;
case CMD_EVENT_RECORDING_TOGGLE:
if (recording_is_enabled())
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
else
command_event(CMD_EVENT_RECORD_INIT, NULL);
break;
case CMD_EVENT_OSK_TOGGLE:
if (p_rarch->input_driver_keyboard_linefeed_enable)
p_rarch->input_driver_keyboard_linefeed_enable = false;
else
p_rarch->input_driver_keyboard_linefeed_enable = true;
break;
case CMD_EVENT_SET_PER_GAME_RESOLUTION:
#if defined(GEKKO)
{
unsigned width = 0, height = 0;
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
if (video_driver_get_video_output_size(&width, &height))
{
char msg[128] = {0};
video_driver_set_video_mode(width, height, true);
if (width == 0 || height == 0)
snprintf(msg, sizeof(msg), "%s: DEFAULT",
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCREEN_RESOLUTION));
else
snprintf(msg, sizeof(msg),"%s: %dx%d",
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCREEN_RESOLUTION),
width, height);
runloop_msg_queue_push(msg, 1, 100, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
#endif
break;
case CMD_EVENT_LOAD_CORE_PERSIST:
{
rarch_system_info_t *system_info = &runloop_state.system;
struct retro_system_info *system = &system_info->info;
const char *core_path = path_get(RARCH_PATH_CORE);
#if defined(HAVE_DYNAMIC)
if (string_is_empty(core_path))
return false;
#endif
if (!libretro_get_system_info(
p_rarch,
core_path,
system,
&system_info->load_no_content))
return false;
if (!core_info_load(core_path))
{
#ifdef HAVE_DYNAMIC
return false;
#endif
}
}
break;
case CMD_EVENT_LOAD_CORE:
{
bool success = false;
subsystem_current_count = 0;
content_clear_subsystem();
success = command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
(void)success;
#ifndef HAVE_DYNAMIC
command_event(CMD_EVENT_QUIT, NULL);
#else
if (!success)
return false;
#endif
break;
}
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
case CMD_EVENT_LOAD_SECOND_CORE:
if (!runloop_state.core_running ||
!p_rarch->runahead_secondary_core_available)
return false;
if (p_rarch->secondary_lib_handle)
return true;
if (!secondary_core_ensure_exists(p_rarch, settings))
{
secondary_core_destroy(p_rarch);
p_rarch->runahead_secondary_core_available = false;
return false;
}
return true;
#endif
case CMD_EVENT_LOAD_STATE:
#ifdef HAVE_BSV_MOVIE
/* Immutable - disallow savestate load when
* we absolutely cannot change game state. */
if (p_rarch->bsv_movie_state_handle)
return false;
#endif
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
return false;
#endif
if (!command_event_main_state(p_rarch, cmd))
return false;
break;
case CMD_EVENT_UNDO_LOAD_STATE:
case CMD_EVENT_UNDO_SAVE_STATE:
case CMD_EVENT_LOAD_STATE_FROM_RAM:
if (!command_event_main_state(p_rarch, cmd))
return false;
break;
case CMD_EVENT_RAM_STATE_TO_FILE:
if (!content_ram_state_to_file((char *) data))
return false;
break;
case CMD_EVENT_RESIZE_WINDOWED_SCALE:
if
(!command_event_resize_windowed_scale
(p_rarch->configuration_settings,
runloop_state.pending_windowed_scale))
return false;
break;
case CMD_EVENT_MENU_TOGGLE:
#ifdef HAVE_MENU
if (p_rarch->menu_driver_alive)
retroarch_menu_running_finished(false);
else
retroarch_menu_running();
#endif
break;
case CMD_EVENT_RESET:
RARCH_LOG("[Core]: %s.\n", msg_hash_to_str(MSG_RESET));
runloop_msg_queue_push(msg_hash_to_str(MSG_RESET), 1, 120, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
core_reset();
#ifdef HAVE_CHEEVOS
#ifdef HAVE_GFX_WIDGETS
rcheevos_reset_game(p_rarch->widgets_active);
#else
rcheevos_reset_game(false);
#endif
#endif
#if HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_RESET, NULL);
#endif
return false;
case CMD_EVENT_SAVE_STATE:
case CMD_EVENT_SAVE_STATE_TO_RAM:
{
bool savestate_auto_index = settings->bools.savestate_auto_index;
int state_slot = settings->ints.state_slot;
if (savestate_auto_index)
{
int new_state_slot = state_slot + 1;
configuration_set_int(settings, settings->ints.state_slot, new_state_slot);
}
}
if (!command_event_main_state(p_rarch, cmd))
return false;
break;
case CMD_EVENT_SAVE_STATE_DECREMENT:
{
int state_slot = settings->ints.state_slot;
/* Slot -1 is (auto) slot. */
if (state_slot >= 0)
{
int new_state_slot = state_slot - 1;
configuration_set_int(settings, settings->ints.state_slot, new_state_slot);
}
}
break;
case CMD_EVENT_SAVE_STATE_INCREMENT:
{
int new_state_slot = settings->ints.state_slot + 1;
configuration_set_int(settings, settings->ints.state_slot, new_state_slot);
}
break;
case CMD_EVENT_TAKE_SCREENSHOT:
#ifdef HAVE_SCREENSHOTS
{
const char *dir_screenshot = settings->paths.directory_screenshot;
if (!take_screenshot(dir_screenshot,
path_get(RARCH_PATH_BASENAME), false,
video_driver_cached_frame_has_valid_framebuffer(), false, true))
return false;
}
#endif
break;
case CMD_EVENT_UNLOAD_CORE:
{
bool contentless = false;
bool is_inited = false;
content_ctx_info_t content_info = {0};
global_t *global = &p_rarch->g_extern;
rarch_system_info_t *sys_info = &runloop_state.system;
content_get_status(&contentless, &is_inited);
runloop_state.core_running = false;
/* The platform that uses ram_state_save calls it when the content
* ends and writes it to a file */
ram_state_to_file();
/* Save last selected disk index, if required */
if (sys_info)
disk_control_save_image_index(&sys_info->disk_control);
command_event_runtime_log_deinit(p_rarch,
settings->bools.content_runtime_log,
settings->bools.content_runtime_log_aggregate,
settings->paths.directory_runtime_log,
settings->paths.directory_playlist);
command_event_save_auto_state(settings->bools.savestate_auto_save,
global, p_rarch->current_core_type);
#ifdef HAVE_CONFIGFILE
if (runloop_state.overrides_active)
{
/* Reload the original config */
config_unload_override();
runloop_state.overrides_active = false;
if (!settings->bools.video_fullscreen)
{
video_driver_show_mouse();
input_driver_ungrab_mouse(p_rarch);
}
}
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
p_rarch->runtime_shader_preset[0] = '\0';
#endif
video_driver_restore_cached(p_rarch, settings);
if ( runloop_state.remaps_core_active
|| runloop_state.remaps_content_dir_active
|| runloop_state.remaps_game_active
)
{
input_remapping_deinit();
input_remapping_set_defaults(true);
}
else
input_remapping_restore_global_config(true);
if (is_inited)
{
#ifdef HAVE_MENU
if ( (settings->uints.quit_on_close_content == QUIT_ON_CLOSE_CONTENT_CLI && global->launched_from_cli)
|| settings->uints.quit_on_close_content == QUIT_ON_CLOSE_CONTENT_ENABLED
)
command_event(CMD_EVENT_QUIT, NULL);
#endif
if (!task_push_start_dummy_core(&content_info))
return false;
}
#ifdef HAVE_DISCORD
if (discord_is_inited)
{
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_NETPLAY_NETPLAY_STOPPED;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
userdata.status = DISCORD_PRESENCE_MENU;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
}
#endif
#ifdef HAVE_DYNAMIC
path_clear(RARCH_PATH_CORE);
retroarch_system_info_free(&runloop_state);
#endif
p_rarch->audio_callback.callback = NULL;
p_rarch->audio_callback.set_state = NULL;
if (is_inited)
{
subsystem_current_count = 0;
content_clear_subsystem();
}
}
break;
case CMD_EVENT_CLOSE_CONTENT:
#ifdef HAVE_MENU
/* Closing content via hotkey requires toggling menu
* and resetting the position later on to prevent
* going to empty Quick Menu */
if (!p_rarch->menu_driver_alive)
{
menu_state_get_ptr()->pending_close_content = true;
command_event(CMD_EVENT_MENU_TOGGLE, NULL);
}
#else
command_event(CMD_EVENT_QUIT, NULL);
#endif
break;
case CMD_EVENT_QUIT:
if (!retroarch_main_quit())
return false;
break;
case CMD_EVENT_CHEEVOS_HARDCORE_MODE_TOGGLE:
#ifdef HAVE_CHEEVOS
rcheevos_toggle_hardcore_paused();
if (rcheevos_hardcore_active())
runloop_state.slowmotion = false;
#endif
break;
case CMD_EVENT_REINIT_FROM_TOGGLE:
p_rarch->rarch_force_fullscreen = false;
/* this fallthrough is on purpose, it should do
a CMD_EVENT_REINIT too */
case CMD_EVENT_REINIT:
command_event_reinit(p_rarch,
data ? *(const int*)data : DRIVERS_CMD_ALL);
break;
case CMD_EVENT_CHEATS_APPLY:
#ifdef HAVE_CHEATS
cheat_manager_apply_cheats();
#endif
break;
case CMD_EVENT_REWIND_DEINIT:
#ifdef HAVE_REWIND
{
bool core_type_is_dummy = p_rarch->current_core_type == CORE_TYPE_DUMMY;
if (core_type_is_dummy)
return false;
state_manager_event_deinit(&p_rarch->rewind_st);
}
#endif
break;
case CMD_EVENT_REWIND_INIT:
#ifdef HAVE_REWIND
{
bool rewind_enable = settings->bools.rewind_enable;
size_t rewind_buf_size = settings->sizes.rewind_buffer_size;
bool core_type_is_dummy = p_rarch->current_core_type == CORE_TYPE_DUMMY;
if (core_type_is_dummy)
return false;
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
return false;
#endif
if (rewind_enable)
{
#ifdef HAVE_NETWORKING
/* Only enable state manager if netplay is not underway
TODO/FIXME: Add a setting for these tweaks */
if (!netplay_driver_ctl(
RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
#endif
{
state_manager_event_init(&p_rarch->rewind_st,
(unsigned)rewind_buf_size);
}
}
}
#endif
break;
case CMD_EVENT_REWIND_TOGGLE:
#ifdef HAVE_REWIND
{
bool rewind_enable = settings->bools.rewind_enable;
if (rewind_enable)
command_event(CMD_EVENT_REWIND_INIT, NULL);
else
command_event(CMD_EVENT_REWIND_DEINIT, NULL);
}
#endif
break;
case CMD_EVENT_AUTOSAVE_INIT:
#ifdef HAVE_THREADS
if (p_rarch->rarch_use_sram)
autosave_deinit();
{
#ifdef HAVE_NETWORKING
unsigned autosave_interval =
settings->uints.autosave_interval;
/* Only enable state manager if netplay is not underway
TODO/FIXME: Add a setting for these tweaks */
if ( (autosave_interval != 0)
&& !netplay_driver_ctl(
RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
#endif
runloop_state.autosave = autosave_init();
}
#endif
break;
case CMD_EVENT_AUDIO_STOP:
midi_driver_set_all_sounds_off();
if (!audio_driver_stop(p_rarch))
return false;
break;
case CMD_EVENT_AUDIO_START:
if (!audio_driver_start(p_rarch,
runloop_state.shutdown_initiated))
return false;
break;
case CMD_EVENT_AUDIO_MUTE_TOGGLE:
{
bool audio_mute_enable =
*(audio_get_bool_ptr(AUDIO_ACTION_MUTE_ENABLE));
const char *msg = !audio_mute_enable ?
msg_hash_to_str(MSG_AUDIO_MUTED):
msg_hash_to_str(MSG_AUDIO_UNMUTED);
p_rarch->audio_driver_mute_enable =
!p_rarch->audio_driver_mute_enable;
#if defined(HAVE_GFX_WIDGETS)
if (p_rarch->widgets_active)
gfx_widget_volume_update_and_show(
settings->floats.audio_volume,
p_rarch->audio_driver_mute_enable);
else
#endif
runloop_msg_queue_push(msg, 1, 180, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
case CMD_EVENT_SEND_DEBUG_INFO:
break;
case CMD_EVENT_FPS_TOGGLE:
settings->bools.video_fps_show = !(settings->bools.video_fps_show);
break;
case CMD_EVENT_OVERLAY_NEXT:
/* Switch to the next available overlay screen. */
#ifdef HAVE_OVERLAY
{
bool *check_rotation = (bool*)data;
bool inp_overlay_auto_rotate = settings->bools.input_overlay_auto_rotate;
float input_overlay_opacity = settings->floats.input_overlay_opacity;
if (!p_rarch->overlay_ptr)
return false;
p_rarch->overlay_ptr->index = p_rarch->overlay_ptr->next_index;
p_rarch->overlay_ptr->active = &p_rarch->overlay_ptr->overlays[
p_rarch->overlay_ptr->index];
input_overlay_load_active(p_rarch->overlay_visibility,
p_rarch->overlay_ptr, input_overlay_opacity);
p_rarch->overlay_ptr->blocked = true;
p_rarch->overlay_ptr->next_index = (unsigned)((p_rarch->overlay_ptr->index + 1) % p_rarch->overlay_ptr->size);
/* Check orientation, if required */
if (inp_overlay_auto_rotate)
if (check_rotation)
if (*check_rotation)
input_overlay_auto_rotate_(
p_rarch->video_driver_width,
p_rarch->video_driver_height,
settings->bools.input_overlay_enable,
p_rarch->overlay_ptr);
}
#endif
break;
case CMD_EVENT_DSP_FILTER_INIT:
#ifdef HAVE_DSP_FILTER
{
const char *path_audio_dsp_plugin = settings->paths.path_audio_dsp_plugin;
audio_driver_dsp_filter_free();
if (string_is_empty(path_audio_dsp_plugin))
break;
if (!audio_driver_dsp_filter_init(path_audio_dsp_plugin))
{
RARCH_ERR("[DSP]: Failed to initialize DSP filter \"%s\".\n",
path_audio_dsp_plugin);
}
}
#endif
break;
case CMD_EVENT_RECORD_DEINIT:
p_rarch->recording_enable = false;
streaming_set_state(false);
if (!recording_deinit(p_rarch))
return false;
break;
case CMD_EVENT_RECORD_INIT:
p_rarch->recording_enable = true;
if (!recording_init(settings, p_rarch))
{
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
return false;
}
break;
case CMD_EVENT_HISTORY_DEINIT:
if (g_defaults.content_history)
{
playlist_write_file(g_defaults.content_history);
playlist_free(g_defaults.content_history);
}
g_defaults.content_history = NULL;
if (g_defaults.music_history)
{
playlist_write_file(g_defaults.music_history);
playlist_free(g_defaults.music_history);
}
g_defaults.music_history = NULL;
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
if (g_defaults.video_history)
{
playlist_write_file(g_defaults.video_history);
playlist_free(g_defaults.video_history);
}
g_defaults.video_history = NULL;
#endif
#ifdef HAVE_IMAGEVIEWER
if (g_defaults.image_history)
{
playlist_write_file(g_defaults.image_history);
playlist_free(g_defaults.image_history);
}
g_defaults.image_history = NULL;
#endif
break;
case CMD_EVENT_HISTORY_INIT:
{
playlist_config_t playlist_config;
bool history_list_enable = settings->bools.history_list_enable;
const char *path_content_history = settings->paths.path_content_history;
const char *path_content_music_history = settings->paths.path_content_music_history;
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
const char *path_content_video_history = settings->paths.path_content_video_history;
#endif
#ifdef HAVE_IMAGEVIEWER
const char *path_content_image_history = settings->paths.path_content_image_history;
#endif
playlist_config.capacity = settings->uints.content_history_size;
playlist_config.old_format = settings->bools.playlist_use_old_format;
playlist_config.compress = settings->bools.playlist_compression;
playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
/* don't use relative paths for content, music, video, and image histories */
playlist_config_set_base_content_directory(&playlist_config, NULL);
command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
if (!history_list_enable)
return false;
/* Note: Sorting is disabled by default for
* all content history playlists */
RARCH_LOG("[Playlist]: %s: [%s].\n",
msg_hash_to_str(MSG_LOADING_HISTORY_FILE),
path_content_history);
playlist_config_set_path(&playlist_config, path_content_history);
g_defaults.content_history = playlist_init(&playlist_config);
playlist_set_sort_mode(
g_defaults.content_history, PLAYLIST_SORT_MODE_OFF);
RARCH_LOG("[Playlist]: %s: [%s].\n",
msg_hash_to_str(MSG_LOADING_HISTORY_FILE),
path_content_music_history);
playlist_config_set_path(&playlist_config, path_content_music_history);
g_defaults.music_history = playlist_init(&playlist_config);
playlist_set_sort_mode(
g_defaults.music_history, PLAYLIST_SORT_MODE_OFF);
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
RARCH_LOG("[Playlist]: %s: [%s].\n",
msg_hash_to_str(MSG_LOADING_HISTORY_FILE),
path_content_video_history);
playlist_config_set_path(&playlist_config, path_content_video_history);
g_defaults.video_history = playlist_init(&playlist_config);
playlist_set_sort_mode(
g_defaults.video_history, PLAYLIST_SORT_MODE_OFF);
#endif
#ifdef HAVE_IMAGEVIEWER
RARCH_LOG("[Playlist]: %s: [%s].\n",
msg_hash_to_str(MSG_LOADING_HISTORY_FILE),
path_content_image_history);
playlist_config_set_path(&playlist_config, path_content_image_history);
g_defaults.image_history = playlist_init(&playlist_config);
playlist_set_sort_mode(
g_defaults.image_history, PLAYLIST_SORT_MODE_OFF);
#endif
}
break;
case CMD_EVENT_CORE_INFO_DEINIT:
core_info_deinit_list();
core_info_free_current_core();
break;
case CMD_EVENT_CORE_INFO_INIT:
{
char ext_name[255];
const char *dir_libretro = settings->paths.directory_libretro;
const char *path_libretro_info = settings->paths.path_libretro_info;
bool show_hidden_files = settings->bools.show_hidden_files;
bool core_info_cache_enable = settings->bools.core_info_cache_enable;
ext_name[0] = '\0';
command_event(CMD_EVENT_CORE_INFO_DEINIT, NULL);
if (!frontend_driver_get_core_extension(ext_name, sizeof(ext_name)))
return false;
if (!string_is_empty(dir_libretro))
{
bool cache_supported = false;
core_info_init_list(path_libretro_info,
dir_libretro,
ext_name,
show_hidden_files,
core_info_cache_enable,
&cache_supported);
/* If core info cache is enabled but cache
* functionality is unsupported (i.e. because
* the core info directory is on read-only
* storage), force-disable the setting to
* avoid repeated failures */
if (core_info_cache_enable && !cache_supported)
configuration_set_bool(settings,
settings->bools.core_info_cache_enable, false);
}
}
break;
case CMD_EVENT_CORE_DEINIT:
{
struct retro_hw_render_callback *hwr = NULL;
rarch_system_info_t *sys_info = &runloop_state.system;
/* The platform that uses ram_state_save calls it when the content
* ends and writes it to a file */
ram_state_to_file();
/* Save last selected disk index, if required */
if (sys_info)
disk_control_save_image_index(&sys_info->disk_control);
command_event_runtime_log_deinit(p_rarch,
settings->bools.content_runtime_log,
settings->bools.content_runtime_log_aggregate,
settings->paths.directory_runtime_log,
settings->paths.directory_playlist);
content_reset_savestate_backups();
hwr = VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
#ifdef HAVE_CHEEVOS
rcheevos_unload();
#endif
command_event_deinit_core(p_rarch, true);
#ifdef HAVE_RUNAHEAD
/* If 'runahead_available' is false, then
* runahead is enabled by the user but an
* error occurred while the core was running
* (typically a save state issue). In this
* case we have to 'manually' reset the runahead
* runtime variables, otherwise runahead will
* remain disabled until the user restarts
* RetroArch */
if (!p_rarch->runahead_available)
runahead_clear_variables(p_rarch);
#endif
if (hwr)
memset(hwr, 0, sizeof(*hwr));
break;
}
case CMD_EVENT_CORE_INIT:
{
enum rarch_core_type *type = (enum rarch_core_type*)data;
rarch_system_info_t *sys_info = &runloop_state.system;
content_reset_savestate_backups();
/* Ensure that disk control interface is reset */
if (sys_info)
disk_control_set_ext_callback(&sys_info->disk_control, NULL);
if (!type || !command_event_init_core(settings, p_rarch, *type))
return false;
}
break;
case CMD_EVENT_VIDEO_APPLY_STATE_CHANGES:
video_driver_apply_state_changes();
break;
case CMD_EVENT_VIDEO_SET_BLOCKING_STATE:
{
bool adaptive_vsync = settings->bools.video_adaptive_vsync;
unsigned swap_interval = settings->uints.video_swap_interval;
if (p_rarch->current_video->set_nonblock_state)
p_rarch->current_video->set_nonblock_state(
p_rarch->video_driver_data, false,
video_driver_test_all_flags(
GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
adaptive_vsync, swap_interval);
}
break;
case CMD_EVENT_VIDEO_SET_ASPECT_RATIO:
video_driver_set_aspect_ratio();
break;
case CMD_EVENT_OVERLAY_SET_SCALE_FACTOR:
#ifdef HAVE_OVERLAY
{
overlay_layout_desc_t layout_desc;
layout_desc.scale_landscape = settings->floats.input_overlay_scale_landscape;
layout_desc.aspect_adjust_landscape = settings->floats.input_overlay_aspect_adjust_landscape;
layout_desc.x_separation_landscape = settings->floats.input_overlay_x_separation_landscape;
layout_desc.y_separation_landscape = settings->floats.input_overlay_y_separation_landscape;
layout_desc.x_offset_landscape = settings->floats.input_overlay_x_offset_landscape;
layout_desc.y_offset_landscape = settings->floats.input_overlay_y_offset_landscape;
layout_desc.scale_portrait = settings->floats.input_overlay_scale_portrait;
layout_desc.aspect_adjust_portrait = settings->floats.input_overlay_aspect_adjust_portrait;
layout_desc.x_separation_portrait = settings->floats.input_overlay_x_separation_portrait;
layout_desc.y_separation_portrait = settings->floats.input_overlay_y_separation_portrait;
layout_desc.x_offset_portrait = settings->floats.input_overlay_x_offset_portrait;
layout_desc.y_offset_portrait = settings->floats.input_overlay_y_offset_portrait;
layout_desc.touch_scale = (float)settings->uints.input_touch_scale;
layout_desc.auto_scale = settings->bools.input_overlay_auto_scale;
input_overlay_set_scale_factor(p_rarch->overlay_ptr, &layout_desc,
p_rarch->video_driver_width, p_rarch->video_driver_height);
}
#endif
break;
case CMD_EVENT_OVERLAY_SET_ALPHA_MOD:
/* Sets a modulating factor for alpha channel. Default is 1.0.
* The alpha factor is applied for all overlays. */
#ifdef HAVE_OVERLAY
{
float input_overlay_opacity = settings->floats.input_overlay_opacity;
input_overlay_set_alpha_mod(p_rarch->overlay_visibility,
p_rarch->overlay_ptr, input_overlay_opacity);
}
#endif
break;
case CMD_EVENT_AUDIO_REINIT:
driver_uninit(p_rarch, DRIVER_AUDIO_MASK);
drivers_init(p_rarch, settings, DRIVER_AUDIO_MASK, verbosity_is_enabled());
#if defined(HAVE_AUDIOMIXER)
audio_driver_load_system_sounds();
#endif
break;
case CMD_EVENT_SHUTDOWN:
#if defined(__linux__) && !defined(ANDROID)
runloop_msg_queue_push(msg_hash_to_str(MSG_VALUE_SHUTTING_DOWN), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
command_event(CMD_EVENT_QUIT, NULL);
system("shutdown -P now");
#endif
break;
case CMD_EVENT_REBOOT:
#if defined(__linux__) && !defined(ANDROID)
runloop_msg_queue_push(msg_hash_to_str(MSG_VALUE_REBOOTING), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
command_event(CMD_EVENT_QUIT, NULL);
system("shutdown -r now");
#endif
break;
case CMD_EVENT_RESUME:
retroarch_menu_running_finished(false);
if (p_rarch->main_ui_companion_is_on_foreground)
{
#ifdef HAVE_QT
bool desktop_menu_enable = settings->bools.desktop_menu_enable;
bool ui_companion_toggle = settings->bools.ui_companion_toggle;
#else
bool desktop_menu_enable = false;
bool ui_companion_toggle = false;
#endif
ui_companion_driver_toggle(p_rarch, desktop_menu_enable, ui_companion_toggle, false);
}
break;
case CMD_EVENT_ADD_TO_FAVORITES:
{
struct string_list *str_list = (struct string_list*)data;
/* Check whether favourties playlist is at capacity */
if (playlist_size(g_defaults.content_favorites) >=
playlist_capacity(g_defaults.content_favorites))
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_ADD_TO_FAVORITES_FAILED), 1, 180, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
return false;
}
if (str_list)
{
if (str_list->size >= 6)
{
struct playlist_entry entry = {0};
bool playlist_sort_alphabetical = settings->bools.playlist_sort_alphabetical;
entry.path = str_list->elems[0].data; /* content_path */
entry.label = str_list->elems[1].data; /* content_label */
entry.core_path = str_list->elems[2].data; /* core_path */
entry.core_name = str_list->elems[3].data; /* core_name */
entry.crc32 = str_list->elems[4].data; /* crc32 */
entry.db_name = str_list->elems[5].data; /* db_name */
/* Write playlist entry */
if (playlist_push(g_defaults.content_favorites, &entry))
{
enum playlist_sort_mode current_sort_mode =
playlist_get_sort_mode(g_defaults.content_favorites);
/* New addition - need to resort if option is enabled */
if ((playlist_sort_alphabetical && (current_sort_mode == PLAYLIST_SORT_MODE_DEFAULT)) ||
(current_sort_mode == PLAYLIST_SORT_MODE_ALPHABETICAL))
playlist_qsort(g_defaults.content_favorites);
playlist_write_file(g_defaults.content_favorites);
runloop_msg_queue_push(msg_hash_to_str(MSG_ADDED_TO_FAVORITES), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
}
break;
}
case CMD_EVENT_RESET_CORE_ASSOCIATION:
{
const char *core_name = "DETECT";
const char *core_path = "DETECT";
size_t *playlist_index = (size_t*)data;
struct playlist_entry entry = {0};
/* the update function reads our entry as const,
* so these casts are safe */
entry.core_path = (char*)core_path;
entry.core_name = (char*)core_name;
command_playlist_update_write(
NULL, *playlist_index, &entry);
runloop_msg_queue_push(msg_hash_to_str(MSG_RESET_CORE_ASSOCIATION), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
break;
}
case CMD_EVENT_RESTART_RETROARCH:
if (!frontend_driver_set_fork(FRONTEND_FORK_RESTART))
return false;
#ifndef HAVE_DYNAMIC
command_event(CMD_EVENT_QUIT, NULL);
#endif
break;
case CMD_EVENT_MENU_RESET_TO_DEFAULT_CONFIG:
config_set_defaults(&p_rarch->g_extern);
break;
case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG:
#if !defined(HAVE_DYNAMIC)
config_save_file_salamander();
#endif
#ifdef HAVE_CONFIGFILE
command_event_save_current_config(OVERRIDE_NONE);
#endif
break;
case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG_OVERRIDE_CORE:
#ifdef HAVE_CONFIGFILE
command_event_save_current_config(OVERRIDE_CORE);
#endif
break;
case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR:
#ifdef HAVE_CONFIGFILE
command_event_save_current_config(OVERRIDE_CONTENT_DIR);
#endif
break;
case CMD_EVENT_MENU_SAVE_CURRENT_CONFIG_OVERRIDE_GAME:
#ifdef HAVE_CONFIGFILE
command_event_save_current_config(OVERRIDE_GAME);
#endif
break;
case CMD_EVENT_MENU_SAVE_CONFIG:
#ifdef HAVE_CONFIGFILE
if (!command_event_save_core_config(
settings->paths.directory_menu_config,
path_get(RARCH_PATH_CONFIG)))
return false;
#endif
break;
case CMD_EVENT_SHADER_PRESET_LOADED:
ui_companion_event_command(cmd);
break;
case CMD_EVENT_SHADERS_APPLY_CHANGES:
#ifdef HAVE_MENU
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
menu_shader_manager_apply_changes(menu_shader_get(),
settings->paths.directory_video_shader,
settings->paths.directory_menu_config
);
#endif
#endif
ui_companion_event_command(cmd);
break;
case CMD_EVENT_PAUSE_TOGGLE:
{
#ifdef HAVE_ACCESSIBILITY
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
boolean = runloop_state.paused;
boolean = !boolean;
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
{
if (boolean)
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
(char*)msg_hash_to_str(MSG_PAUSED), 10);
else
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
(char*)msg_hash_to_str(MSG_UNPAUSED), 10);
}
#endif
runloop_state.paused = boolean;
retroarch_pause_checks(p_rarch);
}
break;
case CMD_EVENT_UNPAUSE:
boolean = false;
runloop_state.paused = boolean;
retroarch_pause_checks(p_rarch);
break;
case CMD_EVENT_PAUSE:
boolean = true;
runloop_state.paused = boolean;
retroarch_pause_checks(p_rarch);
break;
case CMD_EVENT_MENU_PAUSE_LIBRETRO:
#ifdef HAVE_MENU
if (p_rarch->menu_driver_alive)
{
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
if (menu_pause_libretro)
command_event(CMD_EVENT_AUDIO_STOP, NULL);
else
command_event(CMD_EVENT_AUDIO_START, NULL);
}
else
{
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
if (menu_pause_libretro)
command_event(CMD_EVENT_AUDIO_START, NULL);
}
#endif
break;
#ifdef HAVE_NETWORKING
case CMD_EVENT_NETPLAY_GAME_WATCH:
netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL);
break;
case CMD_EVENT_NETPLAY_DEINIT:
deinit_netplay(p_rarch);
break;
case CMD_EVENT_NETWORK_INIT:
network_init();
break;
/* init netplay manually */
case CMD_EVENT_NETPLAY_INIT:
{
char *hostname = (char*)data;
const char *netplay_server = settings->paths.netplay_server;
unsigned netplay_port = settings->uints.netplay_port;
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
if (!init_netplay(p_rarch,
p_rarch->configuration_settings,
NULL,
hostname
? hostname
: netplay_server, netplay_port))
{
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
return false;
}
/* Disable rewind & SRAM autosave if it was enabled
* TODO/FIXME: Add a setting for these tweaks */
#ifdef HAVE_REWIND
state_manager_event_deinit(&p_rarch->rewind_st);
#endif
#ifdef HAVE_THREADS
autosave_deinit();
#endif
}
break;
/* Initialize netplay via lobby when content is loaded */
case CMD_EVENT_NETPLAY_INIT_DIRECT:
{
/* buf is expected to be address|port */
static struct string_list *hostname = NULL;
char *buf = (char *)data;
unsigned netplay_port = settings->uints.netplay_port;
hostname = string_split(buf, "|");
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
RARCH_LOG("[Netplay]: Connecting to %s:%d (direct)\n",
hostname->elems[0].data, !string_is_empty(hostname->elems[1].data)
? atoi(hostname->elems[1].data)
: netplay_port);
if (!init_netplay(
p_rarch,
p_rarch->configuration_settings,
NULL,
hostname->elems[0].data,
!string_is_empty(hostname->elems[1].data)
? atoi(hostname->elems[1].data)
: netplay_port))
{
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
string_list_free(hostname);
return false;
}
string_list_free(hostname);
/* Disable rewind if it was enabled
TODO/FIXME: Add a setting for these tweaks */
#ifdef HAVE_REWIND
state_manager_event_deinit(&p_rarch->rewind_st);
#endif
#ifdef HAVE_THREADS
autosave_deinit();
#endif
}
break;
/* init netplay via lobby when content is not loaded */
case CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED:
{
static struct string_list *hostname = NULL;
/* buf is expected to be address|port */
char *buf = (char *)data;
unsigned netplay_port = settings->uints.netplay_port;
hostname = string_split(buf, "|");
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
RARCH_LOG("[Netplay]: Connecting to %s:%d (deferred)\n",
hostname->elems[0].data, !string_is_empty(hostname->elems[1].data)
? atoi(hostname->elems[1].data)
: netplay_port);
if (!init_netplay_deferred(p_rarch,
hostname->elems[0].data,
!string_is_empty(hostname->elems[1].data)
? atoi(hostname->elems[1].data)
: netplay_port))
{
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
string_list_free(hostname);
return false;
}
string_list_free(hostname);
/* Disable rewind if it was enabled
* TODO/FIXME: Add a setting for these tweaks */
#ifdef HAVE_REWIND
state_manager_event_deinit(&p_rarch->rewind_st);
#endif
#ifdef HAVE_THREADS
autosave_deinit();
#endif
}
break;
case CMD_EVENT_NETPLAY_ENABLE_HOST:
{
#ifdef HAVE_MENU
bool contentless = false;
bool is_inited = false;
content_get_status(&contentless, &is_inited);
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
/* If we haven't yet started, this will load on its own */
if (!is_inited)
{
runloop_msg_queue_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_START_WHEN_LOADED),
1, 480, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
return false;
}
/* Enable Netplay itself */
if (!command_event(CMD_EVENT_NETPLAY_INIT, NULL))
return false;
#endif
break;
}
case CMD_EVENT_NETPLAY_DISCONNECT:
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_DISCONNECT, NULL);
netplay_driver_ctl(RARCH_NETPLAY_CTL_DISABLE, NULL);
{
bool rewind_enable = settings->bools.rewind_enable;
unsigned autosave_interval = settings->uints.autosave_interval;
#ifdef HAVE_REWIND
/* Re-enable rewind if it was enabled
* TODO/FIXME: Add a setting for these tweaks */
if (rewind_enable)
command_event(CMD_EVENT_REWIND_INIT, NULL);
#endif
if (autosave_interval != 0)
command_event(CMD_EVENT_AUTOSAVE_INIT, NULL);
}
break;
}
case CMD_EVENT_NETPLAY_HOST_TOGGLE:
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) &&
netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL))
command_event(CMD_EVENT_NETPLAY_DISCONNECT, NULL);
else if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL) &&
!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_SERVER, NULL) &&
netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_CONNECTED, NULL))
command_event(CMD_EVENT_NETPLAY_DISCONNECT, NULL);
else
command_event(CMD_EVENT_NETPLAY_ENABLE_HOST, NULL);
break;
#else
case CMD_EVENT_NETPLAY_DEINIT:
case CMD_EVENT_NETWORK_INIT:
case CMD_EVENT_NETPLAY_INIT:
case CMD_EVENT_NETPLAY_INIT_DIRECT:
case CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED:
case CMD_EVENT_NETPLAY_HOST_TOGGLE:
case CMD_EVENT_NETPLAY_DISCONNECT:
case CMD_EVENT_NETPLAY_ENABLE_HOST:
case CMD_EVENT_NETPLAY_GAME_WATCH:
return false;
#endif
case CMD_EVENT_FULLSCREEN_TOGGLE:
{
bool *userdata = (bool*)data;
bool video_fullscreen = settings->bools.video_fullscreen;
bool ra_is_forced_fs = p_rarch->rarch_force_fullscreen;
bool new_fullscreen_state = !video_fullscreen && !ra_is_forced_fs;
if (!video_driver_has_windowed())
return false;
p_rarch->audio_suspended = true;
p_rarch->rarch_is_switching_display_mode = true;
/* we toggled manually, write the new value to settings */
configuration_set_bool(settings, settings->bools.video_fullscreen,
new_fullscreen_state);
/* Need to grab this setting's value again */
video_fullscreen = new_fullscreen_state;
/* we toggled manually, the CLI arg is irrelevant now */
if (ra_is_forced_fs)
p_rarch->rarch_force_fullscreen = false;
/* If we go fullscreen we drop all drivers and
* reinitialize to be safe. */
command_event(CMD_EVENT_REINIT, NULL);
if (video_fullscreen)
{
video_driver_hide_mouse();
if (!settings->bools.video_windowed_fullscreen)
input_driver_grab_mouse(p_rarch);
}
else
{
video_driver_show_mouse();
if (!settings->bools.video_windowed_fullscreen)
input_driver_ungrab_mouse(p_rarch);
}
p_rarch->rarch_is_switching_display_mode = false;
p_rarch->audio_suspended = false;
if (userdata && *userdata == true)
video_driver_cached_frame();
}
break;
case CMD_EVENT_DISK_APPEND_IMAGE:
{
const char *path = (const char*)data;
rarch_system_info_t *sys_info = &runloop_state.system;
if (string_is_empty(path) || !sys_info)
return false;
if (disk_control_enabled(&sys_info->disk_control))
{
#if defined(HAVE_MENU)
bool refresh = false;
/* Get initial disk eject state */
bool initial_disk_ejected = disk_control_get_eject_state(&sys_info->disk_control);
#endif
rarch_system_info_t *
sys_info = &runloop_state.system;
/* Append disk image */
bool success = command_event_disk_control_append_image(p_rarch, sys_info, path);
#if defined(HAVE_MENU)
/* Appending a disk image may or may not affect
* the disk tray eject status. If status has changed,
* must refresh the disk options menu */
if (initial_disk_ejected != disk_control_get_eject_state(&sys_info->disk_control))
{
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
}
#endif
return success;
}
else
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS),
1, 120, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
case CMD_EVENT_DISK_EJECT_TOGGLE:
{
rarch_system_info_t *sys_info = &runloop_state.system;
if (!sys_info)
return false;
if (disk_control_enabled(&sys_info->disk_control))
{
bool *show_msg = (bool*)data;
bool eject = !disk_control_get_eject_state(&sys_info->disk_control);
bool verbose = true;
bool refresh = false;
if (show_msg)
verbose = *show_msg;
disk_control_set_eject_state(
&sys_info->disk_control, eject, verbose);
#if defined(HAVE_MENU)
/* It is necessary to refresh the disk options
* menu when toggling the tray state */
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL);
#endif
}
else
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS),
1, 120, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
case CMD_EVENT_DISK_NEXT:
{
rarch_system_info_t *sys_info = &runloop_state.system;
if (!sys_info)
return false;
if (disk_control_enabled(&sys_info->disk_control))
{
bool *show_msg = (bool*)data;
bool verbose = true;
if (show_msg)
verbose = *show_msg;
disk_control_set_index_next(&sys_info->disk_control, verbose);
}
else
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS),
1, 120, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
case CMD_EVENT_DISK_PREV:
{
rarch_system_info_t *sys_info = &runloop_state.system;
if (!sys_info)
return false;
if (disk_control_enabled(&sys_info->disk_control))
{
bool *show_msg = (bool*)data;
bool verbose = true;
if (show_msg)
verbose = *show_msg;
disk_control_set_index_prev(&sys_info->disk_control, verbose);
}
else
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS),
1, 120, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
case CMD_EVENT_DISK_INDEX:
{
rarch_system_info_t *sys_info = &runloop_state.system;
unsigned *index = (unsigned*)data;
if (!sys_info || !index)
return false;
/* Note: Menu itself provides visual feedback - no
* need to print info message to screen */
if (disk_control_enabled(&sys_info->disk_control))
disk_control_set_index(&sys_info->disk_control, *index, false);
else
runloop_msg_queue_push(
msg_hash_to_str(MSG_CORE_DOES_NOT_SUPPORT_DISK_OPTIONS),
1, 120, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
break;
case CMD_EVENT_RUMBLE_STOP:
{
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
unsigned i;
for (i = 0; i < MAX_USERS; i++)
{
unsigned joy_idx = settings->uints.input_joypad_index[i];
input_driver_set_rumble(input_driver_st, i, joy_idx, RETRO_RUMBLE_STRONG, 0);
input_driver_set_rumble(input_driver_st, i, joy_idx, RETRO_RUMBLE_WEAK, 0);
}
}
break;
case CMD_EVENT_GRAB_MOUSE_TOGGLE:
{
bool ret = false;
bool grab_mouse_state = !p_rarch->input_driver_grab_mouse_state;
if (grab_mouse_state)
ret = input_driver_grab_mouse(p_rarch);
else
ret = input_driver_ungrab_mouse(p_rarch);
if (!ret)
return false;
RARCH_LOG("[Input]: %s => %s\n",
msg_hash_to_str(MSG_GRAB_MOUSE_STATE),
grab_mouse_state ? "ON" : "OFF");
if (grab_mouse_state)
video_driver_hide_mouse();
else
video_driver_show_mouse();
}
break;
case CMD_EVENT_UI_COMPANION_TOGGLE:
{
#ifdef HAVE_QT
bool desktop_menu_enable = settings->bools.desktop_menu_enable;
bool ui_companion_toggle = settings->bools.ui_companion_toggle;
#else
bool desktop_menu_enable = false;
bool ui_companion_toggle = false;
#endif
ui_companion_driver_toggle(p_rarch, desktop_menu_enable, ui_companion_toggle, true);
}
break;
case CMD_EVENT_GAME_FOCUS_TOGGLE:
{
bool video_fullscreen =
settings->bools.video_fullscreen || p_rarch->rarch_force_fullscreen;
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_TOGGLE;
bool current_enable_state = p_rarch->game_focus_state.enabled;
bool apply_update = false;
bool show_message = false;
if (data)
game_focus_cmd = *((enum input_game_focus_cmd_type*)data);
switch (game_focus_cmd)
{
case GAME_FOCUS_CMD_OFF:
/* Force game focus off */
p_rarch->game_focus_state.enabled = false;
if (p_rarch->game_focus_state.enabled != current_enable_state)
{
apply_update = true;
show_message = true;
}
break;
case GAME_FOCUS_CMD_ON:
/* Force game focus on */
p_rarch->game_focus_state.enabled = true;
if (p_rarch->game_focus_state.enabled != current_enable_state)
{
apply_update = true;
show_message = true;
}
break;
case GAME_FOCUS_CMD_TOGGLE:
/* Invert current game focus state */
p_rarch->game_focus_state.enabled = !p_rarch->game_focus_state.enabled;
#ifdef HAVE_MENU
/* If menu is currently active, disable
* 'toggle on' functionality */
if (p_rarch->menu_driver_alive)
p_rarch->game_focus_state.enabled = false;
#endif
if (p_rarch->game_focus_state.enabled != current_enable_state)
{
apply_update = true;
show_message = true;
}
break;
case GAME_FOCUS_CMD_REAPPLY:
/* Reapply current game focus state */
apply_update = true;
show_message = false;
break;
default:
break;
}
if (apply_update)
{
if (p_rarch->game_focus_state.enabled)
{
input_driver_grab_mouse(p_rarch);
video_driver_hide_mouse();
}
/* Ungrab only if windowed and auto mouse grab is disabled */
else if (!video_fullscreen &&
!settings->bools.input_auto_mouse_grab)
{
input_driver_ungrab_mouse(p_rarch);
video_driver_show_mouse();
}
p_rarch->input_driver_block_hotkey =
p_rarch->game_focus_state.enabled;
p_rarch->keyboard_mapping_blocked =
p_rarch->game_focus_state.enabled;
if (show_message)
runloop_msg_queue_push(
p_rarch->game_focus_state.enabled ?
msg_hash_to_str(MSG_GAME_FOCUS_ON) :
msg_hash_to_str(MSG_GAME_FOCUS_OFF),
1, 60, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT,
MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("[Input]: %s => %s\n",
"Game Focus",
p_rarch->game_focus_state.enabled ? "ON" : "OFF");
}
}
break;
case CMD_EVENT_VOLUME_UP:
command_event_set_volume(settings, 0.5f,
#if defined(HAVE_GFX_WIDGETS)
p_rarch->widgets_active,
#else
false,
#endif
p_rarch->audio_driver_mute_enable);
break;
case CMD_EVENT_VOLUME_DOWN:
command_event_set_volume(settings, -0.5f,
#if defined(HAVE_GFX_WIDGETS)
p_rarch->widgets_active,
#else
false,
#endif
p_rarch->audio_driver_mute_enable
);
break;
case CMD_EVENT_MIXER_VOLUME_UP:
command_event_set_mixer_volume(settings, 0.5f);
break;
case CMD_EVENT_MIXER_VOLUME_DOWN:
command_event_set_mixer_volume(settings, -0.5f);
break;
case CMD_EVENT_SET_FRAME_LIMIT:
p_rarch->frame_limit_minimum_time =
retroarch_set_frame_limit(&p_rarch->video_driver_av_info,
retroarch_get_runloop_fastforward_ratio(
settings,
&runloop_state.fastmotion_override.current));
break;
case CMD_EVENT_DISCORD_INIT:
#ifdef HAVE_DISCORD
{
bool discord_enable = settings ? settings->bools.discord_enable : false;
const char *discord_app_id = settings ? settings->arrays.discord_app_id : NULL;
discord_state_t *discord_st = &p_rarch->discord_st;
if (!settings)
return false;
if (!discord_enable)
return false;
if (discord_st->ready)
return true;
discord_init(discord_st,
discord_app_id,
p_rarch->launch_arguments);
}
#endif
break;
case CMD_EVENT_DISCORD_UPDATE:
{
#ifdef HAVE_DISCORD
discord_state_t *discord_st = &p_rarch->discord_st;
if (!data || !discord_st->ready)
return false;
discord_userdata_t *userdata = (discord_userdata_t*)data;
if (discord_st->ready)
discord_update(userdata->status);
#endif
}
break;
case CMD_EVENT_AI_SERVICE_CALL:
{
#ifdef HAVE_TRANSLATE
#ifdef HAVE_ACCESSIBILITY
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
unsigned ai_service_mode = settings->uints.ai_service_mode;
#ifdef HAVE_AUDIOMIXER
if (ai_service_mode == 1 && is_ai_service_speech_running())
{
audio_driver_mixer_stop_stream(10);
audio_driver_mixer_remove_stream(10);
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
"stopped.", 10);
#endif
}
else
#endif
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled) &&
ai_service_mode == 2 &&
is_narrator_running(p_rarch, accessibility_enable))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
"stopped.", 10);
else
#endif
{
bool paused = runloop_state.paused;
if (data)
paused = *((bool*)data);
if ( p_rarch->ai_service_auto == 0
&& !settings->bools.ai_service_pause)
p_rarch->ai_service_auto = 1;
run_translation_service(p_rarch->configuration_settings,
p_rarch, paused);
}
#endif
break;
}
case CMD_EVENT_CONTROLLER_INIT:
{
rarch_system_info_t *info = &runloop_state.system;
if (info)
command_event_init_controllers(info, settings,
settings->uints.input_max_users);
}
break;
case CMD_EVENT_NONE:
return false;
}
return true;
}
/* FRONTEND */
void retroarch_override_setting_set(
enum rarch_override_setting enum_idx, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
switch (enum_idx)
{
case RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE:
{
unsigned *val = (unsigned*)data;
if (val)
{
unsigned bit = *val;
BIT256_SET(p_rarch->has_set_libretro_device, bit);
}
}
break;
case RARCH_OVERRIDE_SETTING_VERBOSITY:
p_rarch->has_set_verbosity = true;
break;
case RARCH_OVERRIDE_SETTING_LIBRETRO:
p_rarch->has_set_libretro = true;
break;
case RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY:
p_rarch->has_set_libretro_directory = true;
break;
case RARCH_OVERRIDE_SETTING_SAVE_PATH:
p_rarch->has_set_save_path = true;
break;
case RARCH_OVERRIDE_SETTING_STATE_PATH:
p_rarch->has_set_state_path = true;
break;
#ifdef HAVE_NETWORKING
case RARCH_OVERRIDE_SETTING_NETPLAY_MODE:
p_rarch->has_set_netplay_mode = true;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS:
p_rarch->has_set_netplay_ip_address = true;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
p_rarch->has_set_netplay_ip_port = true;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
p_rarch->has_set_netplay_stateless_mode = true;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
p_rarch->has_set_netplay_check_frames = true;
break;
#endif
case RARCH_OVERRIDE_SETTING_UPS_PREF:
#ifdef HAVE_PATCH
p_rarch->has_set_ups_pref = true;
#endif
break;
case RARCH_OVERRIDE_SETTING_BPS_PREF:
#ifdef HAVE_PATCH
p_rarch->has_set_bps_pref = true;
#endif
break;
case RARCH_OVERRIDE_SETTING_IPS_PREF:
#ifdef HAVE_PATCH
p_rarch->has_set_ips_pref = true;
#endif
break;
case RARCH_OVERRIDE_SETTING_LOG_TO_FILE:
p_rarch->has_set_log_to_file = true;
break;
case RARCH_OVERRIDE_SETTING_NONE:
default:
break;
}
}
void retroarch_override_setting_unset(
enum rarch_override_setting enum_idx, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
switch (enum_idx)
{
case RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE:
{
unsigned *val = (unsigned*)data;
if (val)
{
unsigned bit = *val;
BIT256_CLEAR(p_rarch->has_set_libretro_device, bit);
}
}
break;
case RARCH_OVERRIDE_SETTING_VERBOSITY:
p_rarch->has_set_verbosity = false;
break;
case RARCH_OVERRIDE_SETTING_LIBRETRO:
p_rarch->has_set_libretro = false;
break;
case RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY:
p_rarch->has_set_libretro_directory = false;
break;
case RARCH_OVERRIDE_SETTING_SAVE_PATH:
p_rarch->has_set_save_path = false;
break;
case RARCH_OVERRIDE_SETTING_STATE_PATH:
p_rarch->has_set_state_path = false;
break;
#ifdef HAVE_NETWORKING
case RARCH_OVERRIDE_SETTING_NETPLAY_MODE:
p_rarch->has_set_netplay_mode = false;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS:
p_rarch->has_set_netplay_ip_address = false;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
p_rarch->has_set_netplay_ip_port = false;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
p_rarch->has_set_netplay_stateless_mode = false;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
p_rarch->has_set_netplay_check_frames = false;
break;
#endif
case RARCH_OVERRIDE_SETTING_UPS_PREF:
#ifdef HAVE_PATCH
p_rarch->has_set_ups_pref = false;
#endif
break;
case RARCH_OVERRIDE_SETTING_BPS_PREF:
#ifdef HAVE_PATCH
p_rarch->has_set_bps_pref = false;
#endif
break;
case RARCH_OVERRIDE_SETTING_IPS_PREF:
#ifdef HAVE_PATCH
p_rarch->has_set_ips_pref = false;
#endif
break;
case RARCH_OVERRIDE_SETTING_LOG_TO_FILE:
p_rarch->has_set_log_to_file = false;
break;
case RARCH_OVERRIDE_SETTING_NONE:
default:
break;
}
}
static void retroarch_override_setting_free_state(void)
{
unsigned i;
for (i = 0; i < RARCH_OVERRIDE_SETTING_LAST; i++)
{
if (i == RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE)
{
unsigned j;
for (j = 0; j < MAX_USERS; j++)
retroarch_override_setting_unset(
(enum rarch_override_setting)(i), &j);
}
else
retroarch_override_setting_unset(
(enum rarch_override_setting)(i), NULL);
}
}
static void global_free(struct rarch_state *p_rarch)
{
global_t *global = NULL;
content_deinit();
path_deinit_subsystem(p_rarch);
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
retro_main_log_file_deinit();
p_rarch->rarch_is_sram_load_disabled = false;
p_rarch->rarch_is_sram_save_disabled = false;
p_rarch->rarch_use_sram = false;
#ifdef HAVE_PATCH
p_rarch->rarch_bps_pref = false;
p_rarch->rarch_ips_pref = false;
p_rarch->rarch_ups_pref = false;
p_rarch->rarch_patch_blocked = false;
#endif
#ifdef HAVE_CONFIGFILE
p_rarch->rarch_block_config_read = false;
runloop_state.overrides_active = false;
runloop_state.remaps_core_active = false;
runloop_state.remaps_game_active = false;
runloop_state.remaps_content_dir_active = false;
#endif
p_rarch->current_core.has_set_input_descriptors = false;
p_rarch->current_core.has_set_subsystems = false;
global = &p_rarch->g_extern;
path_clear_all();
dir_clear_all();
if (global)
{
if (!string_is_empty(global->name.remapfile))
free(global->name.remapfile);
memset(global, 0, sizeof(struct global));
}
retroarch_override_setting_free_state();
}
#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
static void sdl_exit(void)
{
/* Quit any SDL subsystems, then quit
* SDL itself */
uint32_t sdl_subsystem_flags = SDL_WasInit(0);
if (sdl_subsystem_flags != 0)
{
SDL_QuitSubSystem(sdl_subsystem_flags);
SDL_Quit();
}
}
#endif
/**
* main_exit:
*
* Cleanly exit RetroArch.
*
* Also saves configuration files to disk,
* and (optionally) autosave state.
**/
void main_exit(void *args)
{
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
settings_t *settings = p_rarch->configuration_settings;
bool config_save_on_exit = settings->bools.config_save_on_exit;
video_driver_restore_cached(p_rarch, settings);
if (config_save_on_exit)
command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
#if defined(HAVE_GFX_WIDGETS)
/* Do not want display widgets to live any more. */
p_rarch->widgets_persisting = false;
#endif
#ifdef HAVE_MENU
/* Do not want menu context to live any more. */
if (menu_st)
menu_st->data_own = false;
#endif
rarch_ctl(RARCH_CTL_MAIN_DEINIT, NULL);
if (runloop_state.perfcnt_enable)
{
RARCH_LOG("[PERF]: Performance counters (RetroArch):\n");
log_counters(p_rarch->perf_counters_rarch, p_rarch->perf_ptr_rarch);
}
#if defined(HAVE_LOGGER) && !defined(ANDROID)
logger_shutdown();
#endif
frontend_driver_deinit(args);
frontend_driver_exitspawn(
path_get_ptr(RARCH_PATH_CORE),
path_get_realsize(RARCH_PATH_CORE),
p_rarch->launch_arguments);
p_rarch->has_set_username = false;
p_rarch->rarch_is_inited = false;
p_rarch->rarch_error_on_init = false;
#ifdef HAVE_CONFIGFILE
p_rarch->rarch_block_config_read = false;
#endif
retroarch_msg_queue_deinit();
driver_uninit(p_rarch, DRIVERS_CMD_ALL);
retro_main_log_file_deinit();
rarch_ctl(RARCH_CTL_STATE_FREE, NULL);
global_free(p_rarch);
task_queue_deinit();
if (p_rarch->configuration_settings)
free(p_rarch->configuration_settings);
p_rarch->configuration_settings = NULL;
ui_companion_driver_deinit(p_rarch);
frontend_driver_shutdown(false);
retroarch_deinit_drivers(p_rarch, &p_rarch->retro_ctx);
ui_companion_driver_free();
frontend_driver_free();
rtime_deinit();
#if defined(ANDROID)
play_feature_delivery_deinit();
#endif
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
CoUninitialize();
#endif
#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
sdl_exit();
#endif
}
/**
* main_entry:
*
* Main function of RetroArch.
*
* If HAVE_MAIN is not defined, will contain main loop and will not
* be exited from until we exit the program. Otherwise, will
* just do initialization.
*
* Returns: varies per platform.
**/
int rarch_main(int argc, char *argv[], void *data)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
p_rarch->shader_presets_need_reload = true;
#endif
#ifdef HAVE_RUNAHEAD
p_rarch->runahead_video_driver_is_active = true;
p_rarch->runahead_available = true;
p_rarch->runahead_secondary_core_available = true;
p_rarch->runahead_force_input_dirty = true;
#endif
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
if (FAILED(CoInitialize(NULL)))
{
RARCH_ERR("FATAL: Failed to initialize the COM interface\n");
return 1;
}
#endif
rtime_init();
#if defined(ANDROID)
play_feature_delivery_init();
#endif
libretro_free_system_info(&runloop_state.system.info);
command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
rarch_favorites_deinit();
p_rarch->configuration_settings = (settings_t*)calloc(1, sizeof(settings_t));
retroarch_deinit_drivers(p_rarch, &p_rarch->retro_ctx);
rarch_ctl(RARCH_CTL_STATE_FREE, NULL);
global_free(p_rarch);
frontend_driver_init_first(data);
if (p_rarch->rarch_is_inited)
driver_uninit(p_rarch, DRIVERS_CMD_ALL);
#ifdef HAVE_THREAD_STORAGE
sthread_tls_create(&p_rarch->rarch_tls);
sthread_tls_set(&p_rarch->rarch_tls, MAGIC_POINTER);
#endif
p_rarch->video_driver_active = true;
p_rarch->audio_driver_active = true;
{
uint8_t i;
for (i = 0; i < MAX_USERS; i++)
input_config_set_device(i, RETRO_DEVICE_JOYPAD);
}
retroarch_msg_queue_init();
if (p_rarch->current_frontend_ctx)
{
content_ctx_info_t info;
info.argc = argc;
info.argv = argv;
info.args = data;
info.environ_get = p_rarch->current_frontend_ctx->environment_get;
if (!task_push_load_content_from_cli(
NULL,
NULL,
&info,
CORE_TYPE_PLAIN,
NULL,
NULL))
return 1;
}
ui_companion_driver_init_first(p_rarch->configuration_settings,
p_rarch);
#if !defined(HAVE_MAIN) || defined(HAVE_QT)
for (;;)
{
int ret;
bool app_exit = false;
#ifdef HAVE_QT
ui_companion_qt.application->process_events();
#endif
ret = runloop_iterate();
task_queue_check();
#ifdef HAVE_QT
app_exit = ui_companion_qt.application->exiting;
#endif
if (ret == -1 || app_exit)
{
#ifdef HAVE_QT
ui_companion_qt.application->quit();
#endif
break;
}
}
main_exit(data);
#endif
return 0;
}
#if defined(EMSCRIPTEN)
#include "gfx/common/gl_common.h"
void RWebAudioRecalibrateTime(void);
void emscripten_mainloop(void)
{
int ret;
static unsigned emscripten_frame_count = 0;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
bool black_frame_insertion = settings->uints.video_black_frame_insertion;
bool input_driver_nonblock_state = input_driver_st ? input_driver_st->nonblocking_flag : false;
bool runloop_is_slowmotion = runloop_state.slowmotion;
bool runloop_is_paused = runloop_state.paused;
RWebAudioRecalibrateTime();
emscripten_frame_count++;
/* Disable BFI during fast forward, slow-motion,
* and pause to prevent flicker. */
if (
black_frame_insertion
&& !input_driver_nonblock_state
&& !runloop_is_slowmotion
&& !runloop_is_paused)
{
if ((emscripten_frame_count % (black_frame_insertion+1)) != 0)
{
gl_clear();
if (p_rarch->current_video_context.swap_buffers)
p_rarch->current_video_context.swap_buffers(
p_rarch->video_context_data);
return;
}
}
ret = runloop_iterate();
task_queue_check();
if (ret != -1)
return;
main_exit(NULL);
emscripten_force_exit(0);
}
#endif
#ifndef HAVE_MAIN
#ifdef __cplusplus
extern "C"
#endif
int main(int argc, char *argv[])
{
return rarch_main(argc, argv, NULL);
}
#endif
/* DYNAMIC LIBRETRO CORE */
const struct retro_subsystem_info *libretro_find_subsystem_info(
const struct retro_subsystem_info *info, unsigned num_info,
const char *ident)
{
unsigned i;
for (i = 0; i < num_info; i++)
{
if (string_is_equal(info[i].ident, ident))
return &info[i];
else if (string_is_equal(info[i].desc, ident))
return &info[i];
}
return NULL;
}
/**
* libretro_find_controller_description:
* @info : Pointer to controller info handle.
* @id : Identifier of controller to search
* for.
*
* Search for a controller of type @id in @info.
*
* Returns: controller description of found controller on success,
* otherwise NULL.
**/
const struct retro_controller_description *
libretro_find_controller_description(
const struct retro_controller_info *info, unsigned id)
{
unsigned i;
for (i = 0; i < info->num_types; i++)
{
if (info->types[i].id != id)
continue;
return &info->types[i];
}
return NULL;
}
/**
* libretro_free_system_info:
* @info : Pointer to system info information.
*
* Frees system information.
**/
void libretro_free_system_info(struct retro_system_info *info)
{
if (!info)
return;
free((void*)info->library_name);
free((void*)info->library_version);
free((void*)info->valid_extensions);
memset(info, 0, sizeof(*info));
}
static bool environ_cb_get_system_info(unsigned cmd, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
rarch_system_info_t *system = &runloop_state.system;
switch (cmd)
{
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
*p_rarch->load_no_content_hook = *(const bool*)data;
break;
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
{
unsigned i, j, size;
const struct retro_subsystem_info *info =
(const struct retro_subsystem_info*)data;
settings_t *settings = p_rarch->configuration_settings;
unsigned log_level = settings->uints.libretro_log_level;
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++)
{
/* 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(subsystem_data[i].desc))
free((char *)subsystem_data[i].desc);
if (!string_is_empty(subsystem_data[i].ident))
free((char *)subsystem_data[i].ident);
subsystem_data[i].desc = strdup(info[i].desc);
subsystem_data[i].ident = strdup(info[i].ident);
subsystem_data[i].id = info[i].id;
subsystem_data[i].num_roms = info[i].num_roms;
if (log_level == RETRO_LOG_DEBUG)
if (subsystem_data[i].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 < subsystem_data[i].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(p_rarch->subsystem_data_roms[i][j].desc))
free((char *)p_rarch->subsystem_data_roms[i][j].desc);
if (!string_is_empty(p_rarch->subsystem_data_roms[i][j].valid_extensions))
free((char *)p_rarch->subsystem_data_roms[i][j].valid_extensions);
p_rarch->subsystem_data_roms[i][j].desc = strdup(info[i].roms[j].desc);
p_rarch->subsystem_data_roms[i][j].valid_extensions = strdup(info[i].roms[j].valid_extensions);
p_rarch->subsystem_data_roms[i][j].required = info[i].roms[j].required;
p_rarch->subsystem_data_roms[i][j].block_extract = info[i].roms[j].block_extract;
p_rarch->subsystem_data_roms[i][j].need_fullpath = info[i].roms[j].need_fullpath;
}
subsystem_data[i].roms = p_rarch->subsystem_data_roms[i];
}
subsystem_current_count =
size <= SUBSYSTEM_MAX_SUBSYSTEMS
? size
: SUBSYSTEM_MAX_SUBSYSTEMS;
}
break;
}
default:
return false;
}
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 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 void rarch_log_libretro(
enum retro_log_level level,
const char *fmt, ...)
{
va_list vp;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
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 void core_performance_counter_start(
struct retro_perf_counter *perf)
{
bool runloop_perfcnt_enable = runloop_state.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)
{
bool runloop_perfcnt_enable = runloop_state.perfcnt_enable;
if (runloop_perfcnt_enable)
perf->total += cpu_features_get_perf_counter() - perf->start;
}
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;
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;
while (mmap_reduce(top_addr & ~desc->core.select, desc->core.disconnect) >> 1 > desc->core.len - 1)
desc->core.disconnect |= mmap_highest_bit(top_addr & ~desc->core.select & ~desc->core.disconnect);
desc->disconnect_mask = mmap_add_bits_down(desc->core.len - 1);
desc->core.disconnect &= desc->disconnect_mask;
while ((~desc->disconnect_mask) >> 1 & desc->core.disconnect)
{
desc->disconnect_mask >>= 1;
desc->core.disconnect &= desc->disconnect_mask;
}
}
return true;
}
static bool rarch_clear_all_thread_waits(
unsigned clear_threads, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
if ( clear_threads > 0)
audio_driver_start(p_rarch,
false);
else
audio_driver_stop(p_rarch);
return true;
}
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);
}
#if defined(HAVE_VULKAN) || defined(HAVE_D3D11) || defined(HAVE_D3D9) || defined(HAVE_OPENGL_CORE)
static video_driver_t *hw_render_context_driver(enum retro_hw_context_type type, int major, int minor)
{
switch (type)
{
case RETRO_HW_CONTEXT_OPENGL_CORE:
#ifdef HAVE_OPENGL_CORE
return &video_gl_core;
#else
break;
#endif
case RETRO_HW_CONTEXT_OPENGL:
#ifdef HAVE_OPENGL
return &video_gl2;
#else
break;
#endif
case RETRO_HW_CONTEXT_DIRECT3D:
#if defined(HAVE_D3D9)
if (major == 9)
return &video_d3d9;
#endif
#if defined(HAVE_D3D11)
if (major == 11)
return &video_d3d11;
#endif
break;
case RETRO_HW_CONTEXT_VULKAN:
#if defined(HAVE_VULKAN)
return &video_vulkan;
#else
break;
#endif
default:
case RETRO_HW_CONTEXT_NONE:
break;
}
return NULL;
}
#endif
static enum retro_hw_context_type hw_render_context_type(const char *s)
{
#ifdef HAVE_OPENGL_CORE
if (string_is_equal(s, "glcore"))
return RETRO_HW_CONTEXT_OPENGL_CORE;
#endif
#ifdef HAVE_OPENGL
if (string_is_equal(s, "gl"))
return RETRO_HW_CONTEXT_OPENGL;
#endif
#ifdef HAVE_VULKAN
if (string_is_equal(s, "vulkan"))
return RETRO_HW_CONTEXT_VULKAN;
#endif
#ifdef HAVE_D3D11
if (string_is_equal(s, "d3d11"))
return RETRO_HW_CONTEXT_DIRECT3D;
#endif
#ifdef HAVE_D3D11
if (string_is_equal(s, "d3d9"))
return RETRO_HW_CONTEXT_DIRECT3D;
#endif
return RETRO_HW_CONTEXT_NONE;
}
static const char *hw_render_context_name(enum retro_hw_context_type type, int major, int minor)
{
#ifdef HAVE_OPENGL_CORE
if (type == RETRO_HW_CONTEXT_OPENGL_CORE)
return "glcore";
#endif
#ifdef HAVE_OPENGL
switch (type)
{
case RETRO_HW_CONTEXT_OPENGLES2:
case RETRO_HW_CONTEXT_OPENGLES3:
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
case RETRO_HW_CONTEXT_OPENGL:
#ifndef HAVE_OPENGL_CORE
case RETRO_HW_CONTEXT_OPENGL_CORE:
#endif
return "gl";
default:
break;
}
#endif
#ifdef HAVE_VULKAN
if (type == RETRO_HW_CONTEXT_VULKAN)
return "vulkan";
#endif
#ifdef HAVE_D3D11
if (type == RETRO_HW_CONTEXT_DIRECT3D && major == 11)
return "d3d11";
#endif
#ifdef HAVE_D3D9
if (type == RETRO_HW_CONTEXT_DIRECT3D && major == 9)
return "d3d9";
#endif
return "N/A";
}
/**
* rarch_environment_cb:
* @cmd : Identifier of command.
* @data : Pointer to data.
*
* Environment callback function implementation.
*
* Returns: true (1) if environment callback command could
* be performed, otherwise false (0).
**/
static bool rarch_environment_cb(unsigned cmd, void *data)
{
unsigned p;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
rarch_system_info_t *system = &runloop_state.system;
bool ignore_environment_cb = p_rarch->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_state.core_options)
*(bool*)data = runloop_state.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_state.core_options)
{
RARCH_LOG("[Environ]: GET_VARIABLE %s: not implemented.\n",
var->key);
return true;
}
#ifdef HAVE_RUNAHEAD
if (runloop_state.core_options->updated)
p_rarch->has_variable_update = true;
#endif
runloop_state.core_options->updated = false;
if (core_option_manager_get_idx(runloop_state.core_options,
var->key, &opt_idx))
var->value = core_option_manager_get_val(
runloop_state.core_options, opt_idx);
if (log_level == RETRO_LOG_DEBUG)
{
char s[128];
s[0] = '\0';
snprintf(s, sizeof(s), "[Environ]: GET_VARIABLE %s:\n\t%s\n",
var->key, var->value ? var->value :
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE));
RARCH_LOG(s);
}
}
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_state.core_options)
{
retroarch_deinit_core_options(
runloop_state.game_options_active,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_state.core_options);
runloop_state.game_options_active = false;
runloop_state.folder_options_active = false;
runloop_state.core_options = NULL;
}
if ((new_vars = retroarch_init_core_variables(
settings,
(const struct retro_variable *)data)))
runloop_state.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_state.core_options)
{
retroarch_deinit_core_options(
runloop_state.game_options_active,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_state.core_options);
runloop_state.game_options_active = false;
runloop_state.folder_options_active = false;
runloop_state.core_options = NULL;
}
if (options_v2)
{
/* Initialise core options */
core_option_manager_t *new_vars = rarch_init_core_options(settings, options_v2);
if (new_vars)
runloop_state.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_state.core_options)
{
retroarch_deinit_core_options(
runloop_state.game_options_active,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_state.core_options);
runloop_state.game_options_active = false;
runloop_state.folder_options_active = false;
runloop_state.core_options = NULL;
}
if (options_v2)
{
/* Initialise core options */
core_option_manager_t *new_vars = rarch_init_core_options(settings, options_v2);
if (new_vars)
runloop_state.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_state.core_options)
{
retroarch_deinit_core_options(
runloop_state.game_options_active,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_state.core_options);
runloop_state.game_options_active = false;
runloop_state.folder_options_active = false;
runloop_state.core_options = NULL;
}
if (options_v2)
{
new_vars = rarch_init_core_options(settings, options_v2);
if (new_vars)
runloop_state.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_state.core_options)
{
retroarch_deinit_core_options(
runloop_state.game_options_active,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_state.core_options);
runloop_state.game_options_active = false;
runloop_state.folder_options_active = false;
runloop_state.core_options = NULL;
}
if (options_v2)
{
/* Initialise core options */
new_vars = rarch_init_core_options(settings, options_v2);
if (new_vars)
runloop_state.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_state.core_options && core_options_display)
core_option_manager_set_visible(
runloop_state.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_state.core_options_callback.update_display =
update_display_callback->callback;
else
runloop_state.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 (p_rarch->widgets_active)
gfx_widget_set_libretro_message(&p_rarch->dispwidget_st,
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 = p_rarch->configuration_settings;
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, runloop_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_state);
/* If a message is already set, only overwrite
* it if the new message has the same or higher
* priority */
if (!runloop_core_status_msg.set ||
(runloop_core_status_msg.priority <= msg->priority))
{
if (!string_is_empty(msg->msg))
{
strlcpy(runloop_core_status_msg.str, msg->msg,
sizeof(runloop_core_status_msg.str));
runloop_core_status_msg.duration = (float)msg->duration;
runloop_core_status_msg.set = true;
}
else
{
/* Ensure sane behaviour if core sends an
* empty message */
runloop_core_status_msg.str[0] = '\0';
runloop_core_status_msg.priority = 0;
runloop_core_status_msg.duration = 0.0f;
runloop_core_status_msg.set = false;
}
}
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
break;
#if defined(HAVE_GFX_WIDGETS)
/* Handle 'alternate' non-queued notifications */
case RETRO_MESSAGE_TYPE_NOTIFICATION_ALT:
if (p_rarch->widgets_active)
gfx_widget_set_libretro_message(&p_rarch->dispwidget_st,
msg->msg, msg->duration);
else
runloop_core_msg_queue_push(
&p_rarch->video_driver_av_info, msg);
break;
/* Handle 'progress' messages */
case RETRO_MESSAGE_TYPE_PROGRESS:
if (p_rarch->widgets_active)
gfx_widget_set_progress_message(&p_rarch->dispwidget_st,
msg->msg, msg->duration,
msg->priority, msg->progress);
else
runloop_core_msg_queue_push(
&p_rarch->video_driver_av_info, msg);
break;
#endif
/* Handle standard (queued) notifications */
case RETRO_MESSAGE_TYPE_NOTIFICATION:
default:
runloop_core_msg_queue_push(
&p_rarch->video_driver_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)
break;
if (system)
system->rotation = rotation;
if (!video_driver_set_rotation(rotation))
return false;
break;
}
case RETRO_ENVIRONMENT_SHUTDOWN:
RARCH_LOG("[Environ]: SHUTDOWN.\n");
/* This case occurs when a core (internally) requests
* a shutdown event. Must save runtime log file here,
* since normal command.c CMD_EVENT_CORE_DEINIT event
* will not occur until after the current content has
* been cleared (causing log to be skipped) */
command_event_runtime_log_deinit(p_rarch,
settings->bools.content_runtime_log,
settings->bools.content_runtime_log_aggregate,
settings->paths.directory_runtime_log,
settings->paths.directory_playlist);
/* Similarly, since the CMD_EVENT_CORE_DEINIT will
* be called *after* the runloop state has been
* cleared, must also perform the following actions
* here:
* - Disable any active config overrides
* - Unload any active input remaps */
#ifdef HAVE_CONFIGFILE
if (runloop_state.overrides_active)
{
/* Reload the original config */
config_unload_override();
runloop_state.overrides_active = false;
}
#endif
if ( runloop_state.remaps_core_active
|| runloop_state.remaps_content_dir_active
|| runloop_state.remaps_game_active
)
{
input_remapping_deinit();
input_remapping_set_defaults(true);
}
else
input_remapping_restore_global_config(true);
runloop_state.shutdown_initiated = true;
runloop_state.core_shutdown_initiated = true;
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 temp_path[PATH_MAX_LENGTH];
temp_path[0] = '\0';
if (string_is_empty(dir_system))
RARCH_WARN("[Environ]: SYSTEM DIR is empty, assume CONTENT DIR %s\n",
fullpath);
fill_pathname_basedir(temp_path, fullpath, sizeof(temp_path));
dir_set(RARCH_DIR_SYSTEM, temp_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 = p_rarch->current_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:
{
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;
}
p_rarch->video_driver_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;
}
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("\tRetroPad, Port %u, Button \"%s\" => \"%s\"\n",
p + 1, libretro_btn_desc[retro_id], description);
}
}
}
}
p_rarch->current_core.has_set_input_descriptors = true;
}
break;
}
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
{
const struct retro_keyboard_callback *info =
(const struct retro_keyboard_callback*)data;
retro_keyboard_event_t *frontend_key_event = &runloop_state.frontend_key_event;
retro_keyboard_event_t *key_event = &runloop_state.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 */
p_rarch->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 = p_rarch->configuration_settings;
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:
{
struct retro_hw_render_callback *cb =
(struct retro_hw_render_callback*)data;
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
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(p_rarch->configuration_settings->arrays.video_driver,
p_rarch->configuration_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
{
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 (p_rarch->recording_data) /* A/V sync is a must. */
return false;
if (cb)
p_rarch->audio_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_state.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_state.audio_buffer_status.callback = info->callback;
else
runloop_state.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_state.audio_latency > audio_latency_default) ?
runloop_state.audio_latency : audio_latency_default;
unsigned audio_latency_new;
RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY.\n");
/* Sanitise input latency value */
runloop_state.audio_latency = 0;
if (data)
runloop_state.audio_latency = *(const unsigned*)data;
if (runloop_state.audio_latency > 512)
{
RARCH_WARN("[Environ]: Requested audio latency of %u ms - limiting to maximum of 512 ms.\n",
runloop_state.audio_latency);
runloop_state.audio_latency = 512;
}
/* Determine new set-point latency value */
if (runloop_state.audio_latency >= audio_latency_default)
audio_latency_new = runloop_state.audio_latency;
else
{
if (runloop_state.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_state.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)
{
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 (p_rarch->recording_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;
RARCH_LOG("[Environ]: GET_INPUT_DEVICE_CAPABILITIES.\n");
if (!p_rarch->input_driver_state.current_driver->get_capabilities || !p_rarch->input_driver_state.current_data)
return false;
*mask = input_driver_get_capabilities();
break;
}
case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE:
{
settings_t *settings = p_rarch->configuration_settings;
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;
RARCH_LOG("[Environ]: GET_CAMERA_INTERFACE.\n");
cb->start = driver_camera_start;
cb->stop = driver_camera_stop;
p_rarch->camera_cb = *cb;
if (cb->caps != 0)
p_rarch->camera_driver_active = true;
else
p_rarch->camera_driver_active = false;
break;
}
case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
{
struct retro_location_callback *cb =
(struct retro_location_callback*)data;
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;
p_rarch->location_driver_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 = rarch_log_libretro;
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 = performance_counter_register;
cb->perf_start = core_performance_counter_start;
cb->perf_stop = core_performance_counter_stop;
cb->perf_log = retro_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;
struct retro_system_av_info *av_info = &p_rarch->video_driver_av_info;
if (data)
{
int reinit_flags = DRIVERS_CMD_ALL;
settings_t *settings = p_rarch->configuration_settings;
float refresh_rate = (*info)->timing.fps;
unsigned crt_switch_resolution = settings->uints.crt_switch_resolution;
bool video_fullscreen = settings->bools.video_fullscreen;
bool video_has_resolution_list = video_display_server_has_resolution_list();
bool video_switch_refresh_rate = false;
bool no_video_reinit = true;
/* Refresh rate switch for regular displays */
if (video_has_resolution_list)
{
float refresh_mod = 0.0f;
float video_refresh_rate = settings->floats.video_refresh_rate;
unsigned video_swap_interval = settings->uints.video_swap_interval;
unsigned video_bfi = settings->uints.video_black_frame_insertion;
bool video_windowed_full = settings->bools.video_windowed_fullscreen;
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
/* Roundings to PAL & NTSC standards */
refresh_rate = (refresh_rate > 54 && refresh_rate < 60) ? 59.94f : refresh_rate;
refresh_rate = (refresh_rate > 49 && refresh_rate < 55) ? 50.00f : refresh_rate;
/* Black frame insertion + swap interval multiplier */
refresh_mod = video_bfi + 1.0f;
refresh_rate = (refresh_rate * refresh_mod * video_swap_interval);
/* Fallback when target refresh rate is not exposed */
if (!video_display_server_has_refresh_rate(refresh_rate))
refresh_rate = (60.0f * refresh_mod * video_swap_interval);
/* Store original refresh rate on automatic change, and
* restore it in deinit_core and main_quit, because not all
* cores announce refresh rate via SET_SYSTEM_AV_INFO */
if (!p_rarch->video_refresh_rate_original)
p_rarch->video_refresh_rate_original = video_refresh_rate;
/* Try to switch display rate when:
* - Not already at correct rate
* - In exclusive fullscreen
* - 'CRT SwitchRes' OFF & 'Sync to Exact Content Framerate' OFF
*/
video_switch_refresh_rate = (
refresh_rate != video_refresh_rate &&
!crt_switch_resolution && !vrr_runloop_enable &&
video_fullscreen && !video_windowed_full);
}
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_monitor_set_refresh_rate(refresh_rate);
video_display_server_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: %.3f, 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));
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 (p_rarch->recording_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();
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;
p_rarch->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;
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 (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:
{
struct retro_system_av_info *av_info = &p_rarch->video_driver_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:
{
struct retro_framebuffer *fb = (struct retro_framebuffer*)data;
if (
p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->get_current_software_framebuffer
&& p_rarch->video_driver_poke->get_current_software_framebuffer(
p_rarch->video_driver_data, fb))
return true;
return false;
}
case RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE:
{
const struct retro_hw_render_interface **iface = (const struct retro_hw_render_interface **)data;
if (
p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->get_hw_render_interface
&& p_rarch->video_driver_poke->get_hw_render_interface(
p_rarch->video_driver_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:
{
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");
p_rarch->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");
p_rarch->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");
p_rarch->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("Core requested VFS 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("Core requested VFS 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;
}
break;
case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE:
{
int result = 0;
if ( !p_rarch->audio_suspended &&
p_rarch->audio_driver_active)
result |= 2;
if (p_rarch->video_driver_active
&& !(p_rarch->current_video->frame == video_null.frame))
result |= 1;
#ifdef HAVE_RUNAHEAD
if (p_rarch->request_fast_savestate)
result |= 4;
if (p_rarch->hard_disable_audio)
result |= 8;
#endif
#ifdef HAVE_NETWORKING
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_REPLAYING, NULL))
result &= ~(1|2);
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
result |= 4;
#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_state.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_state.fastmotion_override.next,
fastforwarding_override,
sizeof(runloop_state.fastmotion_override.next));
runloop_state.fastmotion_override.pending = true;
}
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();
float video_refresh_rate = settings ? settings->floats.video_refresh_rate : 0.0;
/* 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 && video_refresh_rate != 0.0f)
target_refresh_rate = 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)
p_rarch->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 = rarch_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;
}
#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.
*/
static void libretro_get_environment_info(
void (*func)(retro_environment_t),
bool *load_no_content)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->load_no_content_hook = load_no_content;
/* load_no_content gets set in this callback. */
func(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. */
p_rarch->ignore_environment_cb = true;
func(rarch_environment_cb);
p_rarch->ignore_environment_cb = false;
}
static dylib_t load_dynamic_core(
struct rarch_state *p_rarch,
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(p_rarch, 1, "init_libretro_symbols()");
}
#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
/**
* libretro_get_system_info:
* @path : Path to libretro library.
* @info : Pointer to system info information.
* @load_no_content : If true, core should be able to auto-start
* without any content loaded.
*
* Gets system info from an arbitrary lib.
* The struct returned must be freed as strings are allocated dynamically.
*
* Returns: true (1) if successful, otherwise false (0).
**/
static bool libretro_get_system_info(
struct rarch_state *p_rarch,
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
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
lib = libretro_get_system_info_lib(
path, &dummy_info, load_no_content);
if (!lib)
{
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)
{
p_rarch->load_no_content_hook = load_no_content;
/* load_no_content gets set in this callback. */
retro_set_environment(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. */
p_rarch->ignore_environment_cb = true;
retro_set_environment(rarch_environment_cb);
p_rarch->ignore_environment_cb = false;
}
retro_get_system_info(&dummy_info);
#endif
memcpy(info, &dummy_info, sizeof(*info));
p_rarch->current_library_name[0] = '\0';
p_rarch->current_library_version[0] = '\0';
p_rarch->current_valid_extensions[0] = '\0';
if (!string_is_empty(dummy_info.library_name))
strlcpy(p_rarch->current_library_name,
dummy_info.library_name, sizeof(p_rarch->current_library_name));
if (!string_is_empty(dummy_info.library_version))
strlcpy(p_rarch->current_library_version,
dummy_info.library_version, sizeof(p_rarch->current_library_version));
if (dummy_info.valid_extensions)
strlcpy(p_rarch->current_valid_extensions,
dummy_info.valid_extensions, sizeof(p_rarch->current_valid_extensions));
info->library_name = p_rarch->current_library_name;
info->library_version = p_rarch->current_library_version;
info->valid_extensions = p_rarch->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(
struct rarch_state *p_rarch,
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(p_rarch, 1, "init_libretro_symbols()");
}
RARCH_LOG("[Core]: Loading dynamic libretro core from: \"%s\"\n",
path);
if (!(p_rarch->lib_handle = load_dynamic_core(
p_rarch,
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 = p_rarch->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;
case CORE_TYPE_GONG:
#ifdef HAVE_GONG
CORE_SYMBOLS(SYMBOL_GONG);
#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(
struct rarch_state *p_rarch,
enum rarch_core_type type,
struct retro_core_t *current_core)
{
/* Load symbols */
if (!init_libretro_symbols_custom(p_rarch,
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. */
p_rarch->last_core_type = type;
#endif
return true;
}
bool libretro_get_shared_context(void)
{
struct rarch_state *p_rarch = &rarch_st;
bool core_set_shared_context = p_rarch->core_set_shared_context;
return core_set_shared_context;
}
/**
* uninit_libretro_sym:
*
* Frees libretro core.
*
* Frees all core options,
* associated state, and
* unbind all libretro callback symbols.
**/
static void uninit_libretro_symbols(
struct rarch_state *p_rarch,
struct retro_core_t *current_core)
{
#ifdef HAVE_DYNAMIC
if (p_rarch->lib_handle)
dylib_close(p_rarch->lib_handle);
p_rarch->lib_handle = NULL;
#endif
memset(current_core, 0, sizeof(struct retro_core_t));
p_rarch->core_set_shared_context = false;
if (runloop_state.core_options)
{
retroarch_deinit_core_options(
runloop_state.game_options_active,
path_get(RARCH_PATH_CORE_OPTIONS),
runloop_state.core_options);
runloop_state.game_options_active = false;
runloop_state.folder_options_active = false;
runloop_state.core_options = NULL;
}
retroarch_system_info_free(&runloop_state);
p_rarch->audio_callback.callback = NULL;
p_rarch->audio_callback.set_state = NULL;
retroarch_frame_time_free();
retroarch_audio_buffer_status_free();
retroarch_game_focus_free(&p_rarch->game_focus_state);
retroarch_fastmotion_override_free(p_rarch, &runloop_state);
retroarch_core_options_callback_free(&runloop_state);
p_rarch->camera_driver_active = false;
p_rarch->location_driver_active = false;
/* Core has finished utilising the input driver;
* reset 'analog input requested' flags */
memset(&p_rarch->input_driver_analog_requested, 0,
sizeof(p_rarch->input_driver_analog_requested));
/* Performance counters no longer valid. */
p_rarch->perf_ptr_libretro = 0;
memset(p_rarch->perf_counters_libretro, 0,
sizeof(p_rarch->perf_counters_libretro));
}
#if defined(HAVE_RUNAHEAD)
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(
struct rarch_state *p_rarch,
const retro_ctx_load_content_info_t *ctx)
{
free_retro_ctx_load_content_info(p_rarch->load_content_info);
free(p_rarch->load_content_info);
p_rarch->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);
}
static void secondary_core_destroy(struct rarch_state *p_rarch)
{
if (!p_rarch || !p_rarch->secondary_lib_handle)
return;
/* unload game from core */
if (p_rarch->secondary_core.retro_unload_game)
p_rarch->secondary_core.retro_unload_game();
p_rarch->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
/* deinit */
if (p_rarch->secondary_core.retro_deinit)
p_rarch->secondary_core.retro_deinit();
memset(&p_rarch->secondary_core, 0, sizeof(struct retro_core_t));
dylib_close(p_rarch->secondary_lib_handle);
p_rarch->secondary_lib_handle = NULL;
filestream_delete(p_rarch->secondary_library_path);
if (p_rarch->secondary_library_path)
free(p_rarch->secondary_library_path);
p_rarch->secondary_library_path = NULL;
}
static bool secondary_core_ensure_exists(struct rarch_state *p_rarch,
settings_t *settings)
{
if (!p_rarch->secondary_lib_handle)
if (!secondary_core_create(p_rarch, settings))
return false;
return true;
}
#if defined(HAVE_RUNAHEAD) && defined(HAVE_DYNAMIC)
static bool secondary_core_deserialize(
struct rarch_state *p_rarch,
settings_t *settings,
const void *buffer, int size)
{
if (secondary_core_ensure_exists(p_rarch, settings))
return p_rarch->secondary_core.retro_unserialize(buffer, size);
secondary_core_destroy(p_rarch);
return false;
}
#endif
static void remember_controller_port_device(
struct rarch_state *p_rarch,
long port, long device)
{
if (port >= 0 && port < MAX_USERS)
p_rarch->port_map[port] = (int)device;
if ( p_rarch->secondary_lib_handle
&& p_rarch->secondary_core.retro_set_controller_port_device)
p_rarch->secondary_core.retro_set_controller_port_device((unsigned)port, (unsigned)device);
}
static void clear_controller_port_map(struct rarch_state *p_rarch)
{
unsigned port;
for (port = 0; port < MAX_USERS; port++)
p_rarch->port_map[port] = -1;
}
static char *get_temp_directory_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 *retroarch_tmp_path, const void* data, ssize_t dataSize)
{
int ext_len;
unsigned i;
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, retroarch_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 *dir_libretro)
{
char retroarch_tmp_path[PATH_MAX_LENGTH];
bool failed = false;
char *tmp_directory = NULL;
char *tmp_dll_path = NULL;
void *dll_file_data = NULL;
int64_t dll_file_size = 0;
const char *core_path = path_get(RARCH_PATH_CORE);
const char *core_base_name = path_basename_nocompression(core_path);
if (strlen(core_base_name) == 0)
return NULL;
tmp_directory = get_temp_directory_alloc(dir_libretro);
if (!tmp_directory)
return NULL;
retroarch_tmp_path[0] = '\0';
fill_pathname_join(retroarch_tmp_path,
tmp_directory, "retroarch_temp",
sizeof(retroarch_tmp_path));
if (!path_mkdir(retroarch_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, retroarch_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,
retroarch_tmp_path, dll_file_data, dll_file_size))
failed = true;
}
end:
if (tmp_directory)
free(tmp_directory);
if (dll_file_data)
free(dll_file_data);
tmp_directory = 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 rarch_environment_secondary_core_hook(
unsigned cmd, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
bool result = rarch_environment_cb(cmd, data);
if (p_rarch->has_variable_update)
{
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
{
bool *bool_p = (bool*)data;
*bool_p = true;
p_rarch->has_variable_update = false;
return true;
}
else if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE)
p_rarch->has_variable_update = false;
}
return result;
}
static bool secondary_core_create(struct rarch_state *p_rarch,
settings_t *settings)
{
unsigned port;
bool contentless = false;
bool is_inited = false;
const enum rarch_core_type
last_core_type = p_rarch->last_core_type;
rarch_system_info_t *info = &runloop_state.system;
unsigned num_active_users = settings->uints.input_max_users;
if ( last_core_type != CORE_TYPE_PLAIN ||
!p_rarch->load_content_info ||
p_rarch->load_content_info->special)
return false;
if (p_rarch->secondary_library_path)
free(p_rarch->secondary_library_path);
p_rarch->secondary_library_path = NULL;
p_rarch->secondary_library_path = copy_core_to_temp_file(
settings->paths.directory_libretro);
if (!p_rarch->secondary_library_path)
return false;
/* Load Core */
if (!init_libretro_symbols_custom(p_rarch,
CORE_TYPE_PLAIN, &p_rarch->secondary_core,
p_rarch->secondary_library_path,
&p_rarch->secondary_lib_handle))
return false;
p_rarch->secondary_core.symbols_inited = true;
p_rarch->secondary_core.retro_set_environment(
rarch_environment_secondary_core_hook);
#ifdef HAVE_RUNAHEAD
p_rarch->has_variable_update = true;
#endif
p_rarch->secondary_core.retro_init();
content_get_status(&contentless, &is_inited);
p_rarch->secondary_core.inited = is_inited;
/* Load Content */
/* disabled due to crashes */
if ( !p_rarch->load_content_info ||
p_rarch->load_content_info->special)
return false;
if ( (p_rarch->load_content_info->content->size > 0) &&
p_rarch->load_content_info->content->elems[0].data)
{
p_rarch->secondary_core.game_loaded = p_rarch->secondary_core.retro_load_game(
p_rarch->load_content_info->info);
if (!p_rarch->secondary_core.game_loaded)
goto error;
}
else if (contentless)
{
p_rarch->secondary_core.game_loaded = p_rarch->secondary_core.retro_load_game(NULL);
if (!p_rarch->secondary_core.game_loaded)
goto error;
}
else
p_rarch->secondary_core.game_loaded = false;
if (!p_rarch->secondary_core.inited)
goto error;
core_set_default_callbacks(&p_rarch->secondary_callbacks);
p_rarch->secondary_core.retro_set_video_refresh(p_rarch->secondary_callbacks.frame_cb);
p_rarch->secondary_core.retro_set_audio_sample(p_rarch->secondary_callbacks.sample_cb);
p_rarch->secondary_core.retro_set_audio_sample_batch(p_rarch->secondary_callbacks.sample_batch_cb);
p_rarch->secondary_core.retro_set_input_state(p_rarch->secondary_callbacks.state_cb);
p_rarch->secondary_core.retro_set_input_poll(p_rarch->secondary_callbacks.poll_cb);
if (info)
for (port = 0; port < MAX_USERS; port++)
{
if (port < info->ports.size)
{
unsigned device = (port < num_active_users) ?
p_rarch->port_map[port] : RETRO_DEVICE_NONE;
p_rarch->secondary_core.retro_set_controller_port_device(
port, device);
}
}
clear_controller_port_map(p_rarch);
return true;
error:
secondary_core_destroy(p_rarch);
return false;
}
static void secondary_core_input_poll_null(void) { }
static bool secondary_core_run_use_last_input(struct rarch_state *p_rarch)
{
retro_input_poll_t old_poll_function;
retro_input_state_t old_input_function;
if (!secondary_core_ensure_exists(p_rarch, p_rarch->configuration_settings))
{
secondary_core_destroy(p_rarch);
return false;
}
old_poll_function = p_rarch->secondary_callbacks.poll_cb;
old_input_function = p_rarch->secondary_callbacks.state_cb;
p_rarch->secondary_callbacks.poll_cb = secondary_core_input_poll_null;
p_rarch->secondary_callbacks.state_cb = input_state_get_last;
p_rarch->secondary_core.retro_set_input_poll(p_rarch->secondary_callbacks.poll_cb);
p_rarch->secondary_core.retro_set_input_state(p_rarch->secondary_callbacks.state_cb);
p_rarch->secondary_core.retro_run();
p_rarch->secondary_callbacks.poll_cb = old_poll_function;
p_rarch->secondary_callbacks.state_cb = old_input_function;
p_rarch->secondary_core.retro_set_input_poll(p_rarch->secondary_callbacks.poll_cb);
p_rarch->secondary_core.retro_set_input_state(p_rarch->secondary_callbacks.state_cb);
return true;
}
#else
static void secondary_core_destroy(struct rarch_state *p_rarch) { }
static void remember_controller_port_device(
struct rarch_state *p_rarch,
long port, long device) { }
static void clear_controller_port_map(struct rarch_state *p_rarch) { }
#endif
#endif
/* BLUETOOTH DRIVER */
/**
* config_get_bluetooth_driver_options:
*
* Get an enumerated list of all bluetooth driver names,
* separated by '|'.
*
* Returns: string listing of all bluetooth driver names,
* separated by '|'.
**/
const char* config_get_bluetooth_driver_options(void)
{
return char_list_new_special(STRING_LIST_BLUETOOTH_DRIVERS, NULL);
}
void driver_bluetooth_scan(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( (p_rarch->bluetooth_driver_active) &&
(p_rarch->bluetooth_driver->scan) )
p_rarch->bluetooth_driver->scan(p_rarch->bluetooth_data);
}
void driver_bluetooth_get_devices(struct string_list* devices)
{
struct rarch_state *p_rarch = &rarch_st;
if ( (p_rarch->bluetooth_driver_active) &&
(p_rarch->bluetooth_driver->get_devices) )
p_rarch->bluetooth_driver->get_devices(p_rarch->bluetooth_data, devices);
}
bool driver_bluetooth_device_is_connected(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
if ( (p_rarch->bluetooth_driver_active) &&
(p_rarch->bluetooth_driver->device_is_connected) )
return p_rarch->bluetooth_driver->device_is_connected(p_rarch->bluetooth_data, i);
return false;
}
void driver_bluetooth_device_get_sublabel(char *s, unsigned i, size_t len)
{
struct rarch_state *p_rarch = &rarch_st;
if ( (p_rarch->bluetooth_driver_active) &&
(p_rarch->bluetooth_driver->device_get_sublabel) )
p_rarch->bluetooth_driver->device_get_sublabel(p_rarch->bluetooth_data, s, i, len);
}
bool driver_bluetooth_connect_device(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->bluetooth_driver_active)
return p_rarch->bluetooth_driver->connect_device(p_rarch->bluetooth_data, i);
return false;
}
bool bluetooth_driver_ctl(enum rarch_bluetooth_ctl_state state, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
switch (state)
{
case RARCH_BLUETOOTH_CTL_DESTROY:
p_rarch->bluetooth_driver = NULL;
p_rarch->bluetooth_data = NULL;
p_rarch->bluetooth_driver_active = false;
break;
case RARCH_BLUETOOTH_CTL_FIND_DRIVER:
{
const char *prefix = "bluetooth driver";
int i = (int)driver_find_index(
"bluetooth_driver",
settings->arrays.bluetooth_driver);
if (i >= 0)
p_rarch->bluetooth_driver = (const bluetooth_driver_t*)bluetooth_drivers[i];
else
{
if (verbosity_is_enabled())
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.bluetooth_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; bluetooth_drivers[d]; d++)
RARCH_LOG_OUTPUT("\t%s\n", bluetooth_drivers[d]->ident);
RARCH_WARN("Going to default to first %s...\n", prefix);
}
p_rarch->bluetooth_driver = (const bluetooth_driver_t*)bluetooth_drivers[0];
if (!p_rarch->bluetooth_driver)
retroarch_fail(p_rarch, 1, "find_bluetooth_driver()");
}
}
break;
case RARCH_BLUETOOTH_CTL_DEINIT:
if (p_rarch->bluetooth_data && p_rarch->bluetooth_driver)
{
if (p_rarch->bluetooth_driver->free)
p_rarch->bluetooth_driver->free(p_rarch->bluetooth_data);
}
p_rarch->bluetooth_data = NULL;
p_rarch->bluetooth_driver_active = false;
break;
case RARCH_BLUETOOTH_CTL_INIT:
/* Resource leaks will follow if bluetooth is initialized twice. */
if (p_rarch->bluetooth_data)
return false;
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_FIND_DRIVER, NULL);
if (p_rarch->bluetooth_driver && p_rarch->bluetooth_driver->init)
{
p_rarch->bluetooth_driver_active = true;
p_rarch->bluetooth_data = p_rarch->bluetooth_driver->init();
if (!p_rarch->bluetooth_data)
{
RARCH_ERR("Failed to initialize bluetooth driver. Will continue without bluetooth.\n");
p_rarch->bluetooth_driver_active = false;
}
} else {
p_rarch->bluetooth_driver_active = false;
}
break;
default:
break;
}
return false;
}
/* WIFI DRIVER */
/**
* config_get_wifi_driver_options:
*
* Get an enumerated list of all wifi driver names,
* separated by '|'.
*
* Returns: string listing of all wifi driver names,
* separated by '|'.
**/
const char* config_get_wifi_driver_options(void)
{
return char_list_new_special(STRING_LIST_WIFI_DRIVERS, NULL);
}
void driver_wifi_scan(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->wifi_driver->scan(p_rarch->wifi_data);
}
bool driver_wifi_enable(bool enabled)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->wifi_driver->enable(p_rarch->wifi_data, enabled);
}
bool driver_wifi_connection_info(wifi_network_info_t *netinfo)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->wifi_driver->connection_info(p_rarch->wifi_data, netinfo);
}
wifi_network_scan_t* driver_wifi_get_ssids()
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->wifi_driver->get_ssids(p_rarch->wifi_data);
}
bool driver_wifi_ssid_is_online(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->wifi_driver->ssid_is_online(p_rarch->wifi_data, i);
}
bool driver_wifi_connect_ssid(const wifi_network_info_t* net)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->wifi_driver->connect_ssid(p_rarch->wifi_data, net);
}
bool driver_wifi_disconnect_ssid(const wifi_network_info_t* net)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->wifi_driver->disconnect_ssid(p_rarch->wifi_data, net);
}
void driver_wifi_tether_start_stop(bool start, char* configfile)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->wifi_driver->tether_start_stop(p_rarch->wifi_data, start, configfile);
}
bool wifi_driver_ctl(enum rarch_wifi_ctl_state state, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
switch (state)
{
case RARCH_WIFI_CTL_DESTROY:
p_rarch->wifi_driver_active = false;
p_rarch->wifi_driver = NULL;
p_rarch->wifi_data = NULL;
break;
case RARCH_WIFI_CTL_SET_ACTIVE:
p_rarch->wifi_driver_active = true;
break;
case RARCH_WIFI_CTL_FIND_DRIVER:
{
const char *prefix = "wifi driver";
int i = (int)driver_find_index(
"wifi_driver",
settings->arrays.wifi_driver);
if (i >= 0)
p_rarch->wifi_driver = (const wifi_driver_t*)wifi_drivers[i];
else
{
if (verbosity_is_enabled())
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.wifi_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; wifi_drivers[d]; d++)
RARCH_LOG_OUTPUT("\t%s\n", wifi_drivers[d]->ident);
RARCH_WARN("Going to default to first %s...\n", prefix);
}
p_rarch->wifi_driver = (const wifi_driver_t*)wifi_drivers[0];
if (!p_rarch->wifi_driver)
retroarch_fail(p_rarch, 1, "find_wifi_driver()");
}
}
break;
case RARCH_WIFI_CTL_UNSET_ACTIVE:
p_rarch->wifi_driver_active = false;
break;
case RARCH_WIFI_CTL_IS_ACTIVE:
return p_rarch->wifi_driver_active;
case RARCH_WIFI_CTL_DEINIT:
if (p_rarch->wifi_data && p_rarch->wifi_driver)
{
if (p_rarch->wifi_driver->free)
p_rarch->wifi_driver->free(p_rarch->wifi_data);
}
p_rarch->wifi_data = NULL;
break;
case RARCH_WIFI_CTL_STOP:
if ( p_rarch->wifi_driver
&& p_rarch->wifi_driver->stop
&& p_rarch->wifi_data)
p_rarch->wifi_driver->stop(p_rarch->wifi_data);
break;
case RARCH_WIFI_CTL_START:
if ( p_rarch->wifi_driver
&& p_rarch->wifi_data
&& p_rarch->wifi_driver->start)
{
bool wifi_allow = settings->bools.wifi_allow;
if (wifi_allow)
return p_rarch->wifi_driver->start(p_rarch->wifi_data);
}
return false;
case RARCH_WIFI_CTL_INIT:
/* Resource leaks will follow if wifi is initialized twice. */
if (p_rarch->wifi_data)
return false;
wifi_driver_ctl(RARCH_WIFI_CTL_FIND_DRIVER, NULL);
if (p_rarch->wifi_driver && p_rarch->wifi_driver->init)
{
p_rarch->wifi_data = p_rarch->wifi_driver->init();
if (p_rarch->wifi_data)
{
p_rarch->wifi_driver->enable(p_rarch->wifi_data,
settings->bools.wifi_enabled);
}
else
{
RARCH_ERR("Failed to initialize wifi driver. Will continue without wifi.\n");
wifi_driver_ctl(RARCH_WIFI_CTL_UNSET_ACTIVE, NULL);
}
}
break;
default:
break;
}
return false;
}
/* UI COMPANION */
void ui_companion_set_foreground(unsigned enable)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->main_ui_companion_is_on_foreground = enable;
}
bool ui_companion_is_on_foreground(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->main_ui_companion_is_on_foreground;
}
void ui_companion_event_command(enum event_command action)
{
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_QT
bool qt_is_inited = p_rarch->qt_is_inited;
#endif
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (ui && ui->event_command)
ui->event_command(p_rarch->ui_companion_data, action);
#ifdef HAVE_QT
if (ui_companion_qt.toggle && qt_is_inited)
ui_companion_qt.event_command(
p_rarch->ui_companion_qt_data, action);
#endif
}
static void ui_companion_driver_deinit(struct rarch_state *p_rarch)
{
#ifdef HAVE_QT
bool qt_is_inited = p_rarch->qt_is_inited;
#endif
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (!ui)
return;
if (ui->deinit)
ui->deinit(p_rarch->ui_companion_data);
#ifdef HAVE_QT
if (qt_is_inited)
{
ui_companion_qt.deinit(p_rarch->ui_companion_qt_data);
p_rarch->ui_companion_qt_data = NULL;
}
#endif
p_rarch->ui_companion_data = NULL;
}
static void ui_companion_driver_toggle(
struct rarch_state *p_rarch,
bool desktop_menu_enable,
bool ui_companion_toggle,
bool force)
{
if (p_rarch->ui_companion && p_rarch->ui_companion->toggle)
p_rarch->ui_companion->toggle(p_rarch->ui_companion_data, false);
#ifdef HAVE_QT
if (desktop_menu_enable)
{
if ((ui_companion_toggle || force) && !p_rarch->qt_is_inited)
{
p_rarch->ui_companion_qt_data = ui_companion_qt.init();
p_rarch->qt_is_inited = true;
}
if (ui_companion_qt.toggle && p_rarch->qt_is_inited)
ui_companion_qt.toggle(p_rarch->ui_companion_qt_data, force);
}
#endif
}
static void ui_companion_driver_init_first(
settings_t *settings,
struct rarch_state *p_rarch)
{
#ifdef HAVE_QT
bool desktop_menu_enable = settings->bools.desktop_menu_enable;
bool ui_companion_toggle = settings->bools.ui_companion_toggle;
if (desktop_menu_enable && ui_companion_toggle)
{
p_rarch->ui_companion_qt_data = ui_companion_qt.init();
p_rarch->qt_is_inited = true;
}
#else
bool desktop_menu_enable = false;
bool ui_companion_toggle = false;
#endif
unsigned ui_companion_start_on_boot =
settings->bools.ui_companion_start_on_boot;
p_rarch->ui_companion = (ui_companion_driver_t*)ui_companion_drivers[0];
if (p_rarch->ui_companion)
if (ui_companion_start_on_boot)
{
if (p_rarch->ui_companion->init)
p_rarch->ui_companion_data = p_rarch->ui_companion->init();
ui_companion_driver_toggle(p_rarch,
desktop_menu_enable,
ui_companion_toggle,
false);
}
}
void ui_companion_driver_notify_refresh(void)
{
struct rarch_state *p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
#ifdef HAVE_QT
settings_t *settings = p_rarch->configuration_settings;
bool desktop_menu_enable = settings->bools.desktop_menu_enable;
bool qt_is_inited = p_rarch->qt_is_inited;
#endif
if (!ui)
return;
if (ui->notify_refresh)
ui->notify_refresh(p_rarch->ui_companion_data);
#ifdef HAVE_QT
if (desktop_menu_enable)
if (ui_companion_qt.notify_refresh && qt_is_inited)
ui_companion_qt.notify_refresh(p_rarch->ui_companion_qt_data);
#endif
}
void ui_companion_driver_notify_list_loaded(
file_list_t *list, file_list_t *menu_list)
{
struct rarch_state *p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (ui && ui->notify_list_loaded)
ui->notify_list_loaded(p_rarch->ui_companion_data, list, menu_list);
}
void ui_companion_driver_notify_content_loaded(void)
{
struct rarch_state *p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (ui && ui->notify_content_loaded)
ui->notify_content_loaded(p_rarch->ui_companion_data);
}
void ui_companion_driver_free(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->ui_companion = NULL;
}
const ui_msg_window_t *ui_companion_driver_get_msg_window_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (!ui)
return NULL;
return ui->msg_window;
}
const ui_window_t *ui_companion_driver_get_window_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (!ui)
return NULL;
return ui->window;
}
const ui_browser_window_t *ui_companion_driver_get_browser_window_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (!ui)
return NULL;
return ui->browser_window;
}
static void ui_companion_driver_msg_queue_push(
struct rarch_state *p_rarch,
const char *msg, unsigned priority, unsigned duration, bool flush)
{
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (ui && ui->msg_queue_push)
ui->msg_queue_push(p_rarch->ui_companion_data, msg, priority, duration, flush);
#ifdef HAVE_QT
{
settings_t *settings = p_rarch->configuration_settings;
bool qt_is_inited = p_rarch->qt_is_inited;
bool desktop_menu_enable = settings->bools.desktop_menu_enable;
if (desktop_menu_enable)
if (ui_companion_qt.msg_queue_push && qt_is_inited)
ui_companion_qt.msg_queue_push(
p_rarch->ui_companion_qt_data,
msg, priority, duration, flush);
}
#endif
}
void *ui_companion_driver_get_main_window(void)
{
struct rarch_state
*p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (!ui || !ui->get_main_window)
return NULL;
return ui->get_main_window(p_rarch->ui_companion_data);
}
const char *ui_companion_driver_get_ident(void)
{
struct rarch_state
*p_rarch = &rarch_st;
const ui_companion_driver_t *ui = p_rarch->ui_companion;
if (!ui)
return "null";
return ui->ident;
}
void ui_companion_driver_log_msg(const char *msg)
{
#ifdef HAVE_QT
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
bool qt_is_inited = p_rarch->qt_is_inited;
bool desktop_menu_enable = settings->bools.desktop_menu_enable;
bool window_is_active = p_rarch->ui_companion_qt_data && qt_is_inited
&& ui_companion_qt.is_active(p_rarch->ui_companion_qt_data);
if (desktop_menu_enable)
if (window_is_active)
ui_companion_qt.log_msg(p_rarch->ui_companion_qt_data, msg);
#endif
}
/* RECORDING */
/**
* config_get_record_driver_options:
*
* Get an enumerated list of all record driver names, separated by '|'.
*
* Returns: string listing of all record driver names, separated by '|'.
**/
const char* config_get_record_driver_options(void)
{
return char_list_new_special(STRING_LIST_RECORD_DRIVERS, NULL);
}
#if 0
/* TODO/FIXME - not used apparently */
static void find_record_driver(struct rarch_state *p_rarch, const char *prefix,
bool verbosity_enabled)
{
settings_t *settings = p_rarch->configuration_settings;
int i = (int)driver_find_index(
"record_driver",
settings->arrays.record_driver);
if (i >= 0)
p_rarch->recording_driver = (const record_driver_t*)record_drivers[i];
else
{
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("[recording] Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.record_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; record_drivers[d]; d++)
RARCH_LOG_OUTPUT("\t%s\n", record_drivers[d].ident);
RARCH_WARN("[recording] Going to default to first %s...\n", prefix);
}
p_rarch->recording_driver = (const record_driver_t*)record_drivers[0];
if (!p_rarch->recording_driver)
retroarch_fail(p_rarch, 1, "find_record_driver()");
}
}
/**
* ffemu_find_backend:
* @ident : Identifier of driver to find.
*
* Finds a recording driver with the name @ident.
*
* Returns: recording driver handle if successful, otherwise
* NULL.
**/
static const record_driver_t *ffemu_find_backend(const char *ident)
{
unsigned i;
for (i = 0; record_drivers[i]; i++)
{
if (string_is_equal(record_drivers[i]->ident, ident))
return record_drivers[i];
}
return NULL;
}
static void recording_driver_free_state(struct rarch_state *p_rarch)
{
/* TODO/FIXME - this is not being called anywhere */
p_rarch->recording_gpu_width = 0;
p_rarch->recording_gpu_height = 0;
p_rarch->recording_width = 0;
p_rarch->recording_height = 0;
}
#endif
/**
* gfx_ctx_init_first:
* @backend : Recording backend handle.
* @data : Recording data handle.
* @params : Recording info parameters.
*
* Finds first suitable recording context driver and initializes.
*
* Returns: true (1) if successful, otherwise false (0).
**/
static bool record_driver_init_first(
const record_driver_t **backend, void **data,
const struct record_params *params)
{
unsigned i;
for (i = 0; record_drivers[i]; i++)
{
void *handle = record_drivers[i]->init(params);
if (!handle)
continue;
*backend = record_drivers[i];
*data = handle;
return true;
}
return false;
}
static void recording_dump_frame(
struct rarch_state *p_rarch,
const void *data, unsigned width,
unsigned height, size_t pitch, bool is_idle)
{
struct record_video_data ffemu_data;
ffemu_data.data = data;
ffemu_data.width = width;
ffemu_data.height = height;
ffemu_data.pitch = (int)pitch;
ffemu_data.is_dupe = false;
if (p_rarch->video_driver_record_gpu_buffer)
{
struct video_viewport vp;
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
{
RARCH_WARN("[recording] %s \n",
msg_hash_to_str(MSG_VIEWPORT_SIZE_CALCULATION_FAILED));
video_driver_gpu_record_deinit(p_rarch);
recording_dump_frame(p_rarch,
data, width, height, pitch, is_idle);
return;
}
/* User has resized. We kinda have a problem now. */
if ( vp.width != p_rarch->recording_gpu_width ||
vp.height != p_rarch->recording_gpu_height)
{
RARCH_WARN("[recording] %s\n",
msg_hash_to_str(MSG_RECORDING_TERMINATED_DUE_TO_RESIZE));
runloop_msg_queue_push(
msg_hash_to_str(MSG_RECORDING_TERMINATED_DUE_TO_RESIZE),
1, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
return;
}
/* Big bottleneck.
* Since we might need to do read-backs asynchronously,
* it might take 3-4 times before this returns true. */
if (!video_driver_read_viewport(p_rarch->video_driver_record_gpu_buffer, is_idle))
return;
ffemu_data.pitch = (int)(p_rarch->recording_gpu_width * 3);
ffemu_data.width = (unsigned)p_rarch->recording_gpu_width;
ffemu_data.height = (unsigned)p_rarch->recording_gpu_height;
ffemu_data.data = p_rarch->video_driver_record_gpu_buffer + (ffemu_data.height - 1) * ffemu_data.pitch;
ffemu_data.pitch = -ffemu_data.pitch;
}
else
ffemu_data.is_dupe = !data;
p_rarch->recording_driver->push_video(p_rarch->recording_data, &ffemu_data);
}
static bool recording_deinit(struct rarch_state *p_rarch)
{
if (!p_rarch->recording_data || !p_rarch->recording_driver)
return false;
if (p_rarch->recording_driver->finalize)
p_rarch->recording_driver->finalize(p_rarch->recording_data);
if (p_rarch->recording_driver->free)
p_rarch->recording_driver->free(p_rarch->recording_data);
p_rarch->recording_data = NULL;
p_rarch->recording_driver = NULL;
video_driver_gpu_record_deinit(p_rarch);
return true;
}
bool recording_is_enabled(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->recording_enable;
}
bool streaming_is_enabled(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->streaming_enable;
}
void streaming_set_state(bool state)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->streaming_enable = state;
}
static void video_driver_gpu_record_deinit(struct rarch_state *p_rarch)
{
if (p_rarch->video_driver_record_gpu_buffer)
free(p_rarch->video_driver_record_gpu_buffer);
p_rarch->video_driver_record_gpu_buffer = NULL;
}
/**
* recording_init:
*
* Initializes recording.
*
* Returns: true (1) if successful, otherwise false (0).
**/
static bool recording_init(
settings_t *settings,
struct rarch_state *p_rarch)
{
char output[PATH_MAX_LENGTH];
char buf[PATH_MAX_LENGTH];
struct record_params params = {0};
struct retro_system_av_info *av_info = &p_rarch->video_driver_av_info;
global_t *global = &p_rarch->g_extern;
bool video_gpu_record = settings->bools.video_gpu_record;
bool video_force_aspect = settings->bools.video_force_aspect;
const enum rarch_core_type
current_core_type = p_rarch->current_core_type;
const enum retro_pixel_format
video_driver_pix_fmt = p_rarch->video_driver_pix_fmt;
bool recording_enable = p_rarch->recording_enable;
if (!recording_enable)
return false;
output[0] = '\0';
if (current_core_type == CORE_TYPE_DUMMY)
{
RARCH_WARN("[recording] %s\n",
msg_hash_to_str(MSG_USING_LIBRETRO_DUMMY_CORE_RECORDING_SKIPPED));
return false;
}
if (!video_gpu_record && video_driver_is_hw_context())
{
RARCH_WARN("[recording] %s.\n",
msg_hash_to_str(MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING));
return false;
}
RARCH_LOG("[recording] %s: FPS: %.4f, Sample rate: %.4f\n",
msg_hash_to_str(MSG_CUSTOM_TIMING_GIVEN),
(float)av_info->timing.fps,
(float)av_info->timing.sample_rate);
if (!string_is_empty(global->record.path))
strlcpy(output, global->record.path, sizeof(output));
else
{
const char *stream_url = settings->paths.path_stream_url;
unsigned video_record_quality = settings->uints.video_record_quality;
unsigned video_stream_port = settings->uints.video_stream_port;
if (p_rarch->streaming_enable)
if (!string_is_empty(stream_url))
strlcpy(output, stream_url, sizeof(output));
else
/* Fallback, stream locally to 127.0.0.1 */
snprintf(output, sizeof(output), "udp://127.0.0.1:%u",
video_stream_port);
else
{
const char *game_name = path_basename(path_get(RARCH_PATH_BASENAME));
/* Fallback to core name if started without content */
if (string_is_empty(game_name))
game_name = runloop_state.system.info.library_name;
if (video_record_quality < RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST)
{
fill_str_dated_filename(buf, game_name,
"mkv", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else if (video_record_quality >= RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST
&& video_record_quality < RECORD_CONFIG_TYPE_RECORDING_GIF)
{
fill_str_dated_filename(buf, game_name,
"webm", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else if (video_record_quality >= RECORD_CONFIG_TYPE_RECORDING_GIF
&& video_record_quality < RECORD_CONFIG_TYPE_RECORDING_APNG)
{
fill_str_dated_filename(buf, game_name,
"gif", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else
{
fill_str_dated_filename(buf, game_name,
"png", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
}
}
params.audio_resampler = settings->arrays.audio_resampler;
params.video_gpu_record = settings->bools.video_gpu_record;
params.video_record_scale_factor = settings->uints.video_record_scale_factor;
params.video_stream_scale_factor = settings->uints.video_stream_scale_factor;
params.video_record_threads = settings->uints.video_record_threads;
params.streaming_mode = settings->uints.streaming_mode;
params.out_width = av_info->geometry.base_width;
params.out_height = av_info->geometry.base_height;
params.fb_width = av_info->geometry.max_width;
params.fb_height = av_info->geometry.max_height;
params.channels = 2;
params.filename = output;
params.fps = av_info->timing.fps;
params.samplerate = av_info->timing.sample_rate;
params.pix_fmt =
(video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888)
? FFEMU_PIX_ARGB8888
: FFEMU_PIX_RGB565;
params.config = NULL;
if (!string_is_empty(global->record.config))
params.config = global->record.config;
else
{
if (p_rarch->streaming_enable)
{
params.config = settings->paths.path_stream_config;
params.preset = (enum record_config_type)
settings->uints.video_stream_quality;
}
else
{
params.config = settings->paths.path_record_config;
params.preset = (enum record_config_type)
settings->uints.video_record_quality;
}
}
if (settings->bools.video_gpu_record
&& p_rarch->current_video->read_viewport)
{
unsigned gpu_size;
struct video_viewport vp;
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
{
RARCH_ERR("[recording] Failed to get viewport information from video driver. "
"Cannot start recording ...\n");
return false;
}
params.out_width = vp.width;
params.out_height = vp.height;
params.fb_width = next_pow2(vp.width);
params.fb_height = next_pow2(vp.height);
if (video_force_aspect &&
(p_rarch->video_driver_aspect_ratio > 0.0f))
params.aspect_ratio = p_rarch->video_driver_aspect_ratio;
else
params.aspect_ratio = (float)vp.width / vp.height;
params.pix_fmt = FFEMU_PIX_BGR24;
p_rarch->recording_gpu_width = vp.width;
p_rarch->recording_gpu_height = vp.height;
RARCH_LOG("[recording] %s %u x %u\n", msg_hash_to_str(MSG_DETECTED_VIEWPORT_OF),
vp.width, vp.height);
gpu_size = vp.width * vp.height * 3;
if (!(p_rarch->video_driver_record_gpu_buffer = (uint8_t*)malloc(gpu_size)))
return false;
}
else
{
if (p_rarch->recording_width || p_rarch->recording_height)
{
params.out_width = p_rarch->recording_width;
params.out_height = p_rarch->recording_height;
}
if (video_force_aspect &&
(p_rarch->video_driver_aspect_ratio > 0.0f))
params.aspect_ratio = p_rarch->video_driver_aspect_ratio;
else
params.aspect_ratio = (float)params.out_width / params.out_height;
#ifdef HAVE_VIDEO_FILTER
if (settings->bools.video_post_filter_record
&& !!p_rarch->video_driver_state_filter)
{
unsigned max_width = 0;
unsigned max_height = 0;
params.pix_fmt = FFEMU_PIX_RGB565;
if (p_rarch->video_driver_state_out_rgb32)
params.pix_fmt = FFEMU_PIX_ARGB8888;
rarch_softfilter_get_max_output_size(
p_rarch->video_driver_state_filter,
&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
}
#endif
}
RARCH_LOG("[recording] %s %s @ %ux%u. (FB size: %ux%u pix_fmt: %u)\n",
msg_hash_to_str(MSG_RECORDING_TO),
output,
params.out_width, params.out_height,
params.fb_width, params.fb_height,
(unsigned)params.pix_fmt);
if (!record_driver_init_first(
&p_rarch->recording_driver, &p_rarch->recording_data, &params))
{
RARCH_ERR("[recording] %s\n",
msg_hash_to_str(MSG_FAILED_TO_START_RECORDING));
video_driver_gpu_record_deinit(p_rarch);
return false;
}
return true;
}
void recording_driver_update_streaming_url(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
const char *youtube_url = "rtmp://a.rtmp.youtube.com/live2/";
const char *twitch_url = "rtmp://live.twitch.tv/app/";
const char *facebook_url = "rtmps://live-api-s.facebook.com:443/rtmp/";
if (!settings)
return;
switch (settings->uints.streaming_mode)
{
case STREAMING_MODE_TWITCH:
if (!string_is_empty(settings->arrays.twitch_stream_key))
{
strlcpy(settings->paths.path_stream_url,
twitch_url,
sizeof(settings->paths.path_stream_url));
strlcat(settings->paths.path_stream_url,
settings->arrays.twitch_stream_key,
sizeof(settings->paths.path_stream_url));
}
break;
case STREAMING_MODE_YOUTUBE:
if (!string_is_empty(settings->arrays.youtube_stream_key))
{
strlcpy(settings->paths.path_stream_url,
youtube_url,
sizeof(settings->paths.path_stream_url));
strlcat(settings->paths.path_stream_url,
settings->arrays.youtube_stream_key,
sizeof(settings->paths.path_stream_url));
}
break;
case STREAMING_MODE_LOCAL:
/* TODO: figure out default interface and bind to that instead */
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"udp://%s:%u", "127.0.0.1", settings->uints.video_stream_port);
break;
case STREAMING_MODE_CUSTOM:
default:
/* Do nothing, let the user input the URL */
break;
case STREAMING_MODE_FACEBOOK:
if (!string_is_empty(settings->arrays.facebook_stream_key))
{
strlcpy(settings->paths.path_stream_url,
facebook_url,
sizeof(settings->paths.path_stream_url));
strlcat(settings->paths.path_stream_url,
settings->arrays.facebook_stream_key,
sizeof(settings->paths.path_stream_url));
}
break;
}
}
#ifdef HAVE_BSV_MOVIE
/* BSV MOVIE */
static bool bsv_movie_init_playback(
bsv_movie_t *handle, const char *path)
{
uint32_t state_size = 0;
uint32_t content_crc = 0;
uint32_t header[4] = {0};
intfstream_t *file = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
{
RARCH_ERR("Could not open BSV file for playback, path : \"%s\".\n", path);
return false;
}
handle->file = file;
handle->playback = true;
intfstream_read(handle->file, header, sizeof(uint32_t) * 4);
/* Compatibility with old implementation that
* used incorrect documentation. */
if (swap_if_little32(header[MAGIC_INDEX]) != BSV_MAGIC
&& swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC)
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_BSV1_FILE));
return false;
}
content_crc = content_get_crc();
if (content_crc != 0)
if (swap_if_big32(header[CRC_INDEX]) != content_crc)
RARCH_WARN("%s.\n", msg_hash_to_str(MSG_CRC32_CHECKSUM_MISMATCH));
state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
#if 0
RARCH_ERR("----- debug %u -----\n", header[0]);
RARCH_ERR("----- debug %u -----\n", header[1]);
RARCH_ERR("----- debug %u -----\n", header[2]);
RARCH_ERR("----- debug %u -----\n", header[3]);
#endif
if (state_size)
{
retro_ctx_size_info_t info;
retro_ctx_serialize_info_t serial_info;
uint8_t *buf = (uint8_t*)malloc(state_size);
if (!buf)
return false;
handle->state = buf;
handle->state_size = state_size;
if (intfstream_read(handle->file,
handle->state, state_size) != state_size)
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE));
return false;
}
core_serialize_size( &info);
if (info.size == state_size)
{
serial_info.data_const = handle->state;
serial_info.size = state_size;
core_unserialize(&serial_info);
}
else
RARCH_WARN("%s\n",
msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION));
}
handle->min_file_pos = sizeof(header) + state_size;
return true;
}
static bool bsv_movie_init_record(
bsv_movie_t *handle, const char *path)
{
retro_ctx_size_info_t info;
uint32_t state_size = 0;
uint32_t content_crc = 0;
uint32_t header[4] = {0};
intfstream_t *file = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
{
RARCH_ERR("Could not open BSV file for recording, path : \"%s\".\n", path);
return false;
}
handle->file = file;
content_crc = content_get_crc();
/* This value is supposed to show up as
* BSV1 in a HEX editor, big-endian. */
header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC);
header[CRC_INDEX] = swap_if_big32(content_crc);
core_serialize_size(&info);
state_size = (unsigned)info.size;
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
intfstream_write(handle->file, header, 4 * sizeof(uint32_t));
handle->min_file_pos = sizeof(header) + state_size;
handle->state_size = state_size;
if (state_size)
{
retro_ctx_serialize_info_t serial_info;
uint8_t *st = (uint8_t*)malloc(state_size);
if (!st)
return false;
handle->state = st;
serial_info.data = handle->state;
serial_info.size = state_size;
core_serialize(&serial_info);
intfstream_write(handle->file,
handle->state, state_size);
}
return true;
}
static void bsv_movie_free(bsv_movie_t *handle)
{
if (!handle)
return;
intfstream_close(handle->file);
free(handle->file);
free(handle->state);
free(handle->frame_pos);
free(handle);
}
static bsv_movie_t *bsv_movie_init_internal(const char *path,
enum rarch_movie_type type)
{
size_t *frame_pos = NULL;
bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle));
if (!handle)
return NULL;
if (type == RARCH_MOVIE_PLAYBACK)
{
if (!bsv_movie_init_playback(handle, path))
goto error;
}
else if (!bsv_movie_init_record(handle, path))
goto error;
/* Just pick something really large
* ~1 million frames rewind should do the trick. */
if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
goto error;
handle->frame_pos = frame_pos;
handle->frame_pos[0] = handle->min_file_pos;
handle->frame_mask = (1 << 20) - 1;
return handle;
error:
bsv_movie_free(handle);
return NULL;
}
void bsv_movie_frame_rewind(void)
{
struct rarch_state *p_rarch = &rarch_st;
bsv_movie_t *handle = p_rarch->bsv_movie_state_handle;
if (!handle)
return;
handle->did_rewind = true;
if ( (handle->frame_ptr <= 1)
&& (handle->frame_pos[0] == handle->min_file_pos))
{
/* If we're at the beginning... */
handle->frame_ptr = 0;
intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
}
else
{
/* First time rewind is performed, the old frame is simply replayed.
* However, playing back that frame caused us to read data, and push
* data to the ring buffer.
*
* Sucessively rewinding frames, we need to rewind past the read data,
* plus another. */
handle->frame_ptr = (handle->frame_ptr -
(handle->first_rewind ? 1 : 2)) & handle->frame_mask;
intfstream_seek(handle->file,
(int)handle->frame_pos[handle->frame_ptr], SEEK_SET);
}
if (intfstream_tell(handle->file) <= (long)handle->min_file_pos)
{
/* We rewound past the beginning. */
if (!handle->playback)
{
retro_ctx_serialize_info_t serial_info;
/* If recording, we simply reset
* the starting point. Nice and easy. */
intfstream_seek(handle->file, 4 * sizeof(uint32_t), SEEK_SET);
serial_info.data = handle->state;
serial_info.size = handle->state_size;
core_serialize(&serial_info);
intfstream_write(handle->file, handle->state, handle->state_size);
}
else
intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
}
}
static bool bsv_movie_init(struct rarch_state *p_rarch)
{
bsv_movie_t *state = NULL;
if (p_rarch->bsv_movie_state.movie_start_playback)
{
if (!(state = bsv_movie_init_internal(
p_rarch->bsv_movie_state.movie_start_path,
RARCH_MOVIE_PLAYBACK)))
{
RARCH_ERR("%s: \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE),
p_rarch->bsv_movie_state.movie_start_path);
return false;
}
p_rarch->bsv_movie_state_handle = state;
p_rarch->bsv_movie_state.movie_playback = true;
runloop_msg_queue_push(msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK),
2, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("%s.\n", msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK));
return true;
}
else if (p_rarch->bsv_movie_state.movie_start_recording)
{
char msg[8192];
if (!(state = bsv_movie_init_internal(
p_rarch->bsv_movie_state.movie_start_path,
RARCH_MOVIE_RECORD)))
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD),
1, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_ERR("%s.\n",
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD));
return false;
}
p_rarch->bsv_movie_state_handle = state;
snprintf(msg, sizeof(msg),
"%s \"%s\".",
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
p_rarch->bsv_movie_state.movie_start_path);
runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
p_rarch->bsv_movie_state.movie_start_path);
return true;
}
return false;
}
static void bsv_movie_deinit(struct rarch_state *p_rarch)
{
if (p_rarch->bsv_movie_state_handle)
bsv_movie_free(p_rarch->bsv_movie_state_handle);
p_rarch->bsv_movie_state_handle = NULL;
}
static bool runloop_check_movie_init(struct rarch_state *p_rarch,
settings_t *settings)
{
char msg[16384], path[8192];
bsv_movie_t *state = NULL;
int state_slot = settings->ints.state_slot;
msg[0] = path[0] = '\0';
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
if (state_slot > 0)
snprintf(path, sizeof(path), "%s%d.bsv",
p_rarch->bsv_movie_state.movie_path,
state_slot);
else
snprintf(path, sizeof(path), "%s.bsv",
p_rarch->bsv_movie_state.movie_path);
snprintf(msg, sizeof(msg), "%s \"%s\".",
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
path);
state = bsv_movie_init_internal(path, RARCH_MOVIE_RECORD);
if (!state)
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD),
2, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD));
return false;
}
p_rarch->bsv_movie_state_handle = state;
runloop_msg_queue_push(msg, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
path);
return true;
}
static bool bsv_movie_check(struct rarch_state *p_rarch,
settings_t *settings)
{
if (!p_rarch->bsv_movie_state_handle)
return runloop_check_movie_init(p_rarch, settings);
if (p_rarch->bsv_movie_state.movie_playback)
{
/* Checks if movie is being played back. */
if (!p_rarch->bsv_movie_state.movie_end)
return false;
runloop_msg_queue_push(
msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED), 2, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("%s\n", msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED));
bsv_movie_deinit(p_rarch);
p_rarch->bsv_movie_state.movie_end = false;
p_rarch->bsv_movie_state.movie_playback = false;
return true;
}
/* Checks if movie is being recorded. */
if (!p_rarch->bsv_movie_state_handle)
return false;
runloop_msg_queue_push(
msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED), 2, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("%s\n", msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED));
bsv_movie_deinit(p_rarch);
return true;
}
#endif
/* INPUT OVERLAY */
#ifdef HAVE_OVERLAY
static bool video_driver_overlay_interface(
const video_overlay_interface_t **iface);
/* task_data = overlay_task_data_t* */
static void input_overlay_loaded(retro_task_t *task,
void *task_data, void *user_data, const char *err)
{
size_t i;
struct rarch_state *p_rarch = &rarch_st;
overlay_task_data_t *data = (overlay_task_data_t*)task_data;
input_overlay_t *ol = NULL;
const video_overlay_interface_t *iface = NULL;
settings_t *settings = p_rarch->configuration_settings;
bool input_overlay_show_mouse_cursor = settings->bools.input_overlay_show_mouse_cursor;
bool inp_overlay_auto_rotate = settings->bools.input_overlay_auto_rotate;
bool input_overlay_enable = settings->bools.input_overlay_enable;
if (err)
return;
if (data->overlay_enable)
{
#ifdef HAVE_MENU
/* We can't display when the menu is up */
if (data->hide_in_menu && p_rarch->menu_driver_alive)
goto abort_load;
#endif
/* If 'hide_when_gamepad_connected' is enabled,
* we can't display when a gamepad is connected */
if (data->hide_when_gamepad_connected &&
(input_config_get_device_name(0) != NULL))
goto abort_load;
}
if ( !data->overlay_enable ||
!video_driver_overlay_interface(&iface) ||
!iface)
{
RARCH_ERR("Overlay interface is not present in video driver,"
" or not enabled.\n");
goto abort_load;
}
ol = (input_overlay_t*)calloc(1, sizeof(*ol));
ol->overlays = data->overlays;
ol->size = data->size;
ol->active = data->active;
ol->iface = iface;
ol->iface_data = p_rarch->video_driver_data;
input_overlay_load_active(p_rarch->overlay_visibility, ol, data->overlay_opacity);
/* Enable or disable the overlay. */
ol->enable = data->overlay_enable;
if (ol->iface->enable)
ol->iface->enable(ol->iface_data, data->overlay_enable);
input_overlay_set_scale_factor(ol, &data->layout_desc,
p_rarch->video_driver_width, p_rarch->video_driver_height);
ol->next_index = (unsigned)((ol->index + 1) % ol->size);
ol->state = OVERLAY_STATUS_NONE;
ol->alive = true;
/* Due to the asynchronous nature of overlay loading
* it is possible for overlay_ptr to be non-NULL here
* > Ensure it is free()'d before assigning new pointer */
if (p_rarch->overlay_ptr)
{
input_overlay_free_overlays(p_rarch->overlay_ptr);
free(p_rarch->overlay_ptr);
}
p_rarch->overlay_ptr = ol;
free(data);
if (!input_overlay_show_mouse_cursor)
video_driver_hide_mouse();
/* Attempt to automatically rotate overlay, if required */
if (inp_overlay_auto_rotate)
input_overlay_auto_rotate_(
p_rarch->video_driver_width,
p_rarch->video_driver_height,
input_overlay_enable,
p_rarch->overlay_ptr);
return;
abort_load:
for (i = 0; i < data->size; i++)
input_overlay_free_overlay(&data->overlays[i]);
free(data->overlays);
free(data);
}
void input_overlay_set_visibility(int overlay_idx,
enum overlay_visibility vis)
{
struct rarch_state *p_rarch = &rarch_st;
input_overlay_t *ol = p_rarch->overlay_ptr;
if (!p_rarch->overlay_visibility)
{
unsigned i;
p_rarch->overlay_visibility = (enum overlay_visibility *)calloc(
MAX_VISIBILITY, sizeof(enum overlay_visibility));
for (i = 0; i < MAX_VISIBILITY; i++)
p_rarch->overlay_visibility[i] = OVERLAY_VISIBILITY_DEFAULT;
}
p_rarch->overlay_visibility[overlay_idx] = vis;
if (!ol)
return;
if (vis == OVERLAY_VISIBILITY_HIDDEN)
ol->iface->set_alpha(ol->iface_data, overlay_idx, 0.0);
}
/*
* input_poll_overlay:
*
* Poll pressed buttons/keys on currently active overlay.
**/
static void input_poll_overlay(
struct rarch_state *p_rarch,
settings_t *settings,
input_overlay_t *ol, float opacity,
unsigned analog_dpad_mode,
float axis_threshold)
{
input_overlay_state_t old_key_state;
unsigned i, j;
uint16_t key_mod = 0;
bool polled = false;
bool button_pressed = false;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
void *input_data = input_driver_st->current_data;
input_overlay_state_t *ol_state = &ol->overlay_state;
input_driver_t *current_input = input_driver_st->current_driver;
enum overlay_show_input_type
input_overlay_show_inputs = (enum overlay_show_input_type)
settings->uints.input_overlay_show_inputs;
unsigned input_overlay_show_inputs_port = settings->uints.input_overlay_show_inputs_port;
float touch_scale = (float)settings->uints.input_touch_scale;
if (!ol_state)
return;
memcpy(old_key_state.keys, ol_state->keys,
sizeof(ol_state->keys));
memset(ol_state, 0, sizeof(*ol_state));
if (current_input->input_state)
{
rarch_joypad_info_t joypad_info;
unsigned device = ol->active->full_screen
? RARCH_DEVICE_POINTER_SCREEN
: RETRO_DEVICE_POINTER;
const input_device_driver_t
*joypad = input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_driver_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
joypad_info.joy_idx = 0;
joypad_info.auto_binds = NULL;
joypad_info.axis_threshold = 0.0f;
for (i = 0;
current_input->input_state(
input_data,
joypad,
sec_joypad,
&joypad_info,
NULL,
p_rarch->keyboard_mapping_blocked,
0,
device,
i,
RETRO_DEVICE_ID_POINTER_PRESSED);
i++)
{
input_overlay_state_t polled_data;
int16_t x = current_input->input_state(
input_data,
joypad,
sec_joypad,
&joypad_info,
NULL,
p_rarch->keyboard_mapping_blocked,
0,
device,
i,
RETRO_DEVICE_ID_POINTER_X);
int16_t y = current_input->input_state(
input_data,
joypad,
sec_joypad,
&joypad_info,
NULL,
p_rarch->keyboard_mapping_blocked,
0,
device,
i,
RETRO_DEVICE_ID_POINTER_Y);
memset(&polled_data, 0, sizeof(struct input_overlay_state));
if (ol->enable)
input_overlay_poll(ol, &polled_data, x, y, touch_scale);
else
ol->blocked = false;
bits_or_bits(ol_state->buttons.data,
polled_data.buttons.data,
ARRAY_SIZE(polled_data.buttons.data));
for (j = 0; j < ARRAY_SIZE(ol_state->keys); j++)
ol_state->keys[j] |= polled_data.keys[j];
/* Fingers pressed later take priority and matched up
* with overlay poll priorities. */
for (j = 0; j < 4; j++)
if (polled_data.analog[j])
ol_state->analog[j] = polled_data.analog[j];
polled = true;
}
}
if ( OVERLAY_GET_KEY(ol_state, RETROK_LSHIFT) ||
OVERLAY_GET_KEY(ol_state, RETROK_RSHIFT))
key_mod |= RETROKMOD_SHIFT;
if (OVERLAY_GET_KEY(ol_state, RETROK_LCTRL) ||
OVERLAY_GET_KEY(ol_state, RETROK_RCTRL))
key_mod |= RETROKMOD_CTRL;
if ( OVERLAY_GET_KEY(ol_state, RETROK_LALT) ||
OVERLAY_GET_KEY(ol_state, RETROK_RALT))
key_mod |= RETROKMOD_ALT;
if ( OVERLAY_GET_KEY(ol_state, RETROK_LMETA) ||
OVERLAY_GET_KEY(ol_state, RETROK_RMETA))
key_mod |= RETROKMOD_META;
/* CAPSLOCK SCROLLOCK NUMLOCK */
for (i = 0; i < ARRAY_SIZE(ol_state->keys); i++)
{
if (ol_state->keys[i] != old_key_state.keys[i])
{
uint32_t orig_bits = old_key_state.keys[i];
uint32_t new_bits = ol_state->keys[i];
for (j = 0; j < 32; j++)
if ((orig_bits & (1 << j)) != (new_bits & (1 << j)))
input_keyboard_event(new_bits & (1 << j),
i * 32 + j, 0, key_mod, RETRO_DEVICE_POINTER);
}
}
/* Map "analog" buttons to analog axes like regular input drivers do. */
for (j = 0; j < 4; j++)
{
unsigned bind_plus = RARCH_ANALOG_LEFT_X_PLUS + 2 * j;
unsigned bind_minus = bind_plus + 1;
if (ol_state->analog[j])
continue;
if ((BIT256_GET(ol->overlay_state.buttons, bind_plus)))
ol_state->analog[j] += 0x7fff;
if ((BIT256_GET(ol->overlay_state.buttons, bind_minus)))
ol_state->analog[j] -= 0x7fff;
}
/* Check for analog_dpad_mode.
* Map analogs to d-pad buttons when configured. */
switch (analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
{
float analog_x, analog_y;
unsigned analog_base = 2;
if (analog_dpad_mode == ANALOG_DPAD_LSTICK)
analog_base = 0;
analog_x = (float)ol_state->analog[analog_base + 0] / 0x7fff;
analog_y = (float)ol_state->analog[analog_base + 1] / 0x7fff;
if (analog_x <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_LEFT);
if (analog_x >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_RIGHT);
if (analog_y <= -axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_UP);
if (analog_y >= axis_threshold)
BIT256_SET(ol_state->buttons, RETRO_DEVICE_ID_JOYPAD_DOWN);
break;
}
default:
break;
}
if (input_overlay_show_inputs != OVERLAY_SHOW_INPUT_NONE)
button_pressed = input_overlay_add_inputs(ol,
(input_overlay_show_inputs == OVERLAY_SHOW_INPUT_TOUCHED),
input_overlay_show_inputs_port);
if (button_pressed || polled)
input_overlay_post_poll(p_rarch->overlay_visibility, ol, button_pressed, opacity);
else
input_overlay_poll_clear(p_rarch->overlay_visibility, ol, opacity);
}
static void retroarch_overlay_deinit(struct rarch_state *p_rarch)
{
input_overlay_free(p_rarch->overlay_ptr);
p_rarch->overlay_ptr = NULL;
}
static void retroarch_overlay_init(struct rarch_state *p_rarch)
{
settings_t *settings = p_rarch->configuration_settings;
bool input_overlay_enable = settings->bools.input_overlay_enable;
bool input_overlay_auto_scale = settings->bools.input_overlay_auto_scale;
const char *path_overlay = settings->paths.path_overlay;
float overlay_opacity = settings->floats.input_overlay_opacity;
float overlay_scale_landscape = settings->floats.input_overlay_scale_landscape;
float overlay_aspect_adjust_landscape = settings->floats.input_overlay_aspect_adjust_landscape;
float overlay_x_separation_landscape = settings->floats.input_overlay_x_separation_landscape;
float overlay_y_separation_landscape = settings->floats.input_overlay_y_separation_landscape;
float overlay_x_offset_landscape = settings->floats.input_overlay_x_offset_landscape;
float overlay_y_offset_landscape = settings->floats.input_overlay_y_offset_landscape;
float overlay_scale_portrait = settings->floats.input_overlay_scale_portrait;
float overlay_aspect_adjust_portrait = settings->floats.input_overlay_aspect_adjust_portrait;
float overlay_x_separation_portrait = settings->floats.input_overlay_x_separation_portrait;
float overlay_y_separation_portrait = settings->floats.input_overlay_y_separation_portrait;
float overlay_x_offset_portrait = settings->floats.input_overlay_x_offset_portrait;
float overlay_y_offset_portrait = settings->floats.input_overlay_y_offset_portrait;
float overlay_touch_scale = (float)settings->uints.input_touch_scale;
bool load_enabled = input_overlay_enable;
#ifdef HAVE_MENU
bool overlay_hide_in_menu = settings->bools.input_overlay_hide_in_menu;
#else
bool overlay_hide_in_menu = false;
#endif
bool overlay_hide_when_gamepad_connected = settings->bools.input_overlay_hide_when_gamepad_connected;
#if defined(GEKKO)
/* Avoid a crash at startup or even when toggling overlay in rgui */
uint64_t memory_free = frontend_driver_get_free_memory();
if (memory_free < (3 * 1024 * 1024))
return;
#endif
retroarch_overlay_deinit(p_rarch);
#ifdef HAVE_MENU
/* Cancel load if 'hide_in_menu' is enabled and
* menu is currently active */
if (overlay_hide_in_menu)
load_enabled = load_enabled && !p_rarch->menu_driver_alive;
#endif
/* Cancel load if 'hide_when_gamepad_connected' is
* enabled and a gamepad is currently connected */
if (overlay_hide_when_gamepad_connected)
load_enabled = load_enabled && (input_config_get_device_name(0) == NULL);
if (load_enabled)
{
overlay_layout_desc_t layout_desc;
layout_desc.scale_landscape = overlay_scale_landscape;
layout_desc.aspect_adjust_landscape = overlay_aspect_adjust_landscape;
layout_desc.x_separation_landscape = overlay_x_separation_landscape;
layout_desc.y_separation_landscape = overlay_y_separation_landscape;
layout_desc.x_offset_landscape = overlay_x_offset_landscape;
layout_desc.y_offset_landscape = overlay_y_offset_landscape;
layout_desc.scale_portrait = overlay_scale_portrait;
layout_desc.aspect_adjust_portrait = overlay_aspect_adjust_portrait;
layout_desc.x_separation_portrait = overlay_x_separation_portrait;
layout_desc.y_separation_portrait = overlay_y_separation_portrait;
layout_desc.x_offset_portrait = overlay_x_offset_portrait;
layout_desc.y_offset_portrait = overlay_y_offset_portrait;
layout_desc.touch_scale = overlay_touch_scale;
layout_desc.auto_scale = input_overlay_auto_scale;
task_push_overlay_load_default(input_overlay_loaded,
path_overlay,
overlay_hide_in_menu,
overlay_hide_when_gamepad_connected,
input_overlay_enable,
overlay_opacity,
&layout_desc,
NULL);
}
}
#endif
/* INPUT */
void set_connection_listener(pad_connection_listener_t *listener)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->pad_connection_listener = listener;
}
/**
* config_get_input_driver_options:
*
* Get an enumerated list of all input driver names, separated by '|'.
*
* Returns: string listing of all input driver names, separated by '|'.
**/
const char* config_get_input_driver_options(void)
{
return char_list_new_special(STRING_LIST_INPUT_DRIVERS, NULL);
}
/******************************************************************************
* BEGIN helper functions for input_driver refactoring
*
* These functions have similar names and signatures to functions that now require
* an input_driver_state_t pointer to be passed to them. They essentially wrap
* the newer functions by grabbing pointer to the driver state struct and the
* settings struct.
******************************************************************************/
/**
* Retrieves the sensor state associated with the provided port and ID.
*
* @param port
* @param id Sensor ID
*
* @return The current state associated with the port and ID as a float
**/
float input_get_sensor_state(unsigned port, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
settings_t *settings = p_rarch->configuration_settings;
bool input_sensors_enable = settings->bools.input_sensors_enable;
return input_driver_get_sensor(input_driver_st, port, input_sensors_enable, id);
}
/**
* Sets the rumble state. Used by RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE.
*
* @param port User number.
* @param effect Rumble effect.
* @param strength Strength of rumble effect.
*
* @return true if the rumble state has been successfully set
**/
bool input_set_rumble_state(unsigned port,
enum retro_rumble_effect effect, uint16_t strength)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
settings_t *settings = p_rarch->configuration_settings;
unsigned joy_idx = settings->uints.input_joypad_index[port];
uint16_t scaled_strength = strength;
/* If gain setting is not suported, do software gain control */
if (input_driver_st && input_driver_st->primary_joypad)
{
if (!input_driver_st->primary_joypad->set_rumble_gain)
{
unsigned rumble_gain = settings->uints.input_rumble_gain;
scaled_strength = (rumble_gain * strength) / 100.0;
}
}
return input_driver_set_rumble(
input_driver_st,
port, joy_idx, effect, scaled_strength);
}
/**
* Sets the rumble gain. Used by MENU_ENUM_LABEL_INPUT_RUMBLE_GAIN.
*
* @param gain Rumble gain, 0-100 [%]
*
* @return true if the rumble gain has been successfully set
**/
bool input_set_rumble_gain(unsigned gain)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
settings_t *settings = p_rarch->configuration_settings;
if (input_driver_set_rumble_gain(
input_driver_st, gain, settings->uints.input_max_users))
return true;
else
return false;
}
/**
* Sets the sensor state. Used by RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE.
*
* @param port
* @param action
* @param rate
*
* @return true if the sensor state has been successfully set
**/
bool input_set_sensor_state(unsigned port,
enum retro_sensor_action action, unsigned rate)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
bool input_sensors_enable = settings->bools.input_sensors_enable;
return input_driver_set_sensor(
&p_rarch->input_driver_state,
port, input_sensors_enable, action, rate);
}
void input_set_nonblock_state(void)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
if (input_driver_st)
input_driver_st->nonblocking_flag = true;
}
void input_unset_nonblock_state(void)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
if (input_driver_st)
input_driver_st->nonblocking_flag = false;
}
void *input_driver_get_data(void)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
return input_driver_st->current_data;
}
/******************************************************************************
* END helper functions for input_driver refactoring
******************************************************************************/
const char *joypad_driver_name(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
if (!p_rarch || !input_driver_st->primary_joypad || !input_driver_st->primary_joypad->name)
return NULL;
return input_driver_st->primary_joypad->name(i);
}
void joypad_driver_reinit(void *data, const char *joypad_driver_name)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
if (!p_rarch)
return;
if (input_driver_st->primary_joypad)
{
const input_device_driver_t *tmp = input_driver_st->primary_joypad;
input_driver_st->primary_joypad = NULL;
tmp->destroy();
}
#ifdef HAVE_MFI
if (input_driver_st->secondary_joypad)
{
const input_device_driver_t *tmp = input_driver_st->secondary_joypad;
input_driver_st->secondary_joypad = NULL;
tmp->destroy();
}
#endif
if (!input_driver_st->primary_joypad)
input_driver_st->primary_joypad = input_joypad_init_driver(joypad_driver_name, data);
#ifdef HAVE_MFI
if (!input_driver_st->secondary_joypad)
input_driver_st->secondary_joypad = input_joypad_init_driver("mfi", data);
#endif
}
static uint64_t input_driver_get_capabilities(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->input_driver_state.current_driver || !p_rarch->input_driver_state.current_driver->get_capabilities)
return 0;
return p_rarch->input_driver_state.current_driver->get_capabilities(p_rarch->input_driver_state.current_data);
}
/**
* input_poll:
*
* Input polling callback function.
**/
static void input_driver_poll(void)
{
size_t i, j;
rarch_joypad_info_t joypad_info[MAX_USERS];
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t
*input_driver_st = &p_rarch->input_driver_state;
settings_t *settings = p_rarch->configuration_settings;
const input_device_driver_t
*joypad = input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_driver_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
#ifdef HAVE_OVERLAY
float input_overlay_opacity = settings->floats.input_overlay_opacity;
#endif
bool input_remap_binds_enable = settings->bools.input_remap_binds_enable;
uint8_t max_users = (uint8_t)settings->uints.input_max_users;
if ( joypad && joypad->poll)
joypad->poll();
if ( sec_joypad && sec_joypad->poll)
sec_joypad->poll();
if ( p_rarch->input_driver_state.current_driver
&& p_rarch->input_driver_state.current_driver->poll)
p_rarch->input_driver_state.current_driver->poll(p_rarch->input_driver_state.current_data);
p_rarch->input_driver_turbo_btns.count++;
if (p_rarch->input_driver_block_libretro_input)
{
for (i = 0; i < max_users; i++)
p_rarch->input_driver_turbo_btns.frame_enable[i] = 0;
return;
}
/* This rarch_joypad_info_t struct contains the device index + autoconfig binds for the
* controller to be queried, and also (for unknown reasons) the analog axis threshold
* when mapping analog stick to dpad input. */
for (i = 0; i < max_users; i++)
{
joypad_info[i].axis_threshold = settings->floats.input_axis_threshold;
joypad_info[i].joy_idx = settings->uints.input_joypad_index[i];
joypad_info[i].auto_binds = input_autoconf_binds[joypad_info[i].joy_idx];
p_rarch->input_driver_turbo_btns.frame_enable[i] = p_rarch->libretro_input_binds[i][RARCH_TURBO_ENABLE].valid ?
input_state_wrap(
p_rarch->input_driver_state.current_driver,
p_rarch->input_driver_state.current_data,
joypad,
sec_joypad,
&joypad_info[i],
p_rarch->libretro_input_binds,
p_rarch->keyboard_mapping_blocked,
(unsigned)i,
RETRO_DEVICE_JOYPAD,
0,
RARCH_TURBO_ENABLE) : 0;
}
#ifdef HAVE_OVERLAY
if (p_rarch->overlay_ptr && p_rarch->overlay_ptr->alive)
{
unsigned input_analog_dpad_mode = settings->uints.input_analog_dpad_mode[0];
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
{
unsigned mapped_port = settings->uints.input_remap_ports[0];
if (p_rarch->input_driver_analog_requested[mapped_port])
input_analog_dpad_mode = ANALOG_DPAD_NONE;
}
break;
case ANALOG_DPAD_LSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_RSTICK;
break;
default:
break;
}
input_poll_overlay(p_rarch,
settings,
p_rarch->overlay_ptr,
input_overlay_opacity,
input_analog_dpad_mode,
settings->floats.input_axis_threshold);
}
#endif
#ifdef HAVE_MENU
if (!p_rarch->menu_driver_alive)
#endif
if (input_remap_binds_enable)
{
#ifdef HAVE_OVERLAY
input_overlay_t *overlay_pointer = (input_overlay_t*)p_rarch->overlay_ptr;
bool poll_overlay = (p_rarch->overlay_ptr && p_rarch->overlay_ptr->alive);
#endif
input_mapper_t *handle = &p_rarch->input_driver_mapper;
float input_analog_deadzone = settings->floats.input_analog_deadzone;
float input_analog_sensitivity = settings->floats.input_analog_sensitivity;
for (i = 0; i < max_users; i++)
{
input_bits_t current_inputs;
unsigned mapped_port = settings->uints.input_remap_ports[i];
unsigned device = settings->uints.input_libretro_device[mapped_port]
& RETRO_DEVICE_MASK;
input_bits_t *p_new_state = (input_bits_t*)&current_inputs;
unsigned input_analog_dpad_mode = settings->uints.input_analog_dpad_mode[i];
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
if (p_rarch->input_driver_analog_requested[mapped_port])
input_analog_dpad_mode = ANALOG_DPAD_NONE;
break;
case ANALOG_DPAD_LSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_RSTICK;
break;
default:
break;
}
switch (device)
{
case RETRO_DEVICE_KEYBOARD:
case RETRO_DEVICE_JOYPAD:
case RETRO_DEVICE_ANALOG:
BIT256_CLEAR_ALL_PTR(&current_inputs);
if (joypad)
{
unsigned k, j;
int16_t ret = input_state_wrap(
p_rarch->input_driver_state.current_driver,
p_rarch->input_driver_state.current_data,
input_driver_st->primary_joypad,
sec_joypad,
&joypad_info[i],
p_rarch->libretro_input_binds,
p_rarch->keyboard_mapping_blocked,
(unsigned)i, RETRO_DEVICE_JOYPAD,
0, RETRO_DEVICE_ID_JOYPAD_MASK);
for (k = 0; k < RARCH_FIRST_CUSTOM_BIND; k++)
{
if (ret & (1 << k))
{
bool valid_bind =
p_rarch->libretro_input_binds[i][k].valid;
if (valid_bind)
{
int16_t val =
input_joypad_analog_button(
input_analog_deadzone,
input_analog_sensitivity,
joypad,
&joypad_info[i],
k,
&p_rarch->libretro_input_binds[i][k]
);
if (val)
p_new_state->analog_buttons[k] = val;
}
BIT256_SET_PTR(p_new_state, k);
}
}
/* This is the analog joypad index -
* handles only the two analog axes */
for (k = 0; k < 2; k++)
{
/* This is the analog joypad ident */
for (j = 0; j < 2; j++)
{
unsigned offset = 0 + (k * 4) + (j * 2);
int16_t val = input_joypad_analog_axis(
input_analog_dpad_mode,
input_analog_deadzone,
input_analog_sensitivity,
joypad,
&joypad_info[i],
k,
j,
p_rarch->libretro_input_binds[i]);
if (val >= 0)
p_new_state->analogs[offset] = val;
else
p_new_state->analogs[offset+1] = val;
}
}
}
break;
default:
break;
}
/* mapper */
switch (device)
{
/* keyboard to gamepad remapping */
case RETRO_DEVICE_KEYBOARD:
for (j = 0; j < RARCH_CUSTOM_BIND_LIST_END; j++)
{
unsigned current_button_value;
unsigned remap_key =
settings->uints.input_keymapper_ids[i][j];
if (remap_key == RETROK_UNKNOWN)
continue;
if (j >= RARCH_FIRST_CUSTOM_BIND && j < RARCH_ANALOG_BIND_LIST_END)
{
int16_t current_axis_value = p_new_state->analogs[j - RARCH_FIRST_CUSTOM_BIND];
current_button_value = abs(current_axis_value) >
settings->floats.input_axis_threshold
* 32767;
}
else
{
current_button_value =
BIT256_GET_PTR(p_new_state, j);
}
#ifdef HAVE_OVERLAY
if (poll_overlay && i == 0)
{
input_overlay_state_t *ol_state =
overlay_pointer
? &overlay_pointer->overlay_state
: NULL;
if (ol_state)
current_button_value |=
BIT256_GET(ol_state->buttons, j);
}
#endif
/* Press */
if ((current_button_value == 1)
&& !MAPPER_GET_KEY(handle, remap_key))
{
handle->key_button[remap_key] = (unsigned)j;
MAPPER_SET_KEY(handle, remap_key);
input_keyboard_event(true,
remap_key,
0, 0, RETRO_DEVICE_KEYBOARD);
}
/* Release */
else if ((current_button_value == 0)
&& MAPPER_GET_KEY(handle, remap_key))
{
if (handle->key_button[remap_key] != j)
continue;
input_keyboard_event(false,
remap_key,
0, 0, RETRO_DEVICE_KEYBOARD);
MAPPER_UNSET_KEY(handle, remap_key);
}
}
break;
/* gamepad remapping */
case RETRO_DEVICE_JOYPAD:
case RETRO_DEVICE_ANALOG:
/* this loop iterates on all users and all buttons,
* and checks if a pressed button is assigned to any
* other button than the default one, then it sets
* the bit on the mapper input bitmap, later on the
* original input is cleared in input_state */
BIT256_CLEAR_ALL(handle->buttons[i]);
for (j = 0; j < 8; j++)
handle->analog_value[i][j] = 0;
for (j = 0; j < RARCH_FIRST_CUSTOM_BIND; j++)
{
bool remap_valid;
unsigned remap_button =
settings->uints.input_remap_ids[i][j];
unsigned current_button_value =
BIT256_GET_PTR(p_new_state, j);
#ifdef HAVE_OVERLAY
if (poll_overlay && i == 0)
{
input_overlay_state_t *ol_state =
overlay_pointer
? &overlay_pointer->overlay_state
: NULL;
if (ol_state)
current_button_value |=
BIT256_GET(ol_state->buttons, j);
}
#endif
remap_valid =
(current_button_value == 1) &&
(j != remap_button) &&
(remap_button != RARCH_UNMAPPED);
#ifdef HAVE_ACCESSIBILITY
/* gamepad override */
if (i == 0 &&
p_rarch->gamepad_input_override & (1 << j))
{
BIT256_SET(handle->buttons[i], j);
}
#endif
if (remap_valid)
{
if (remap_button < RARCH_FIRST_CUSTOM_BIND)
{
BIT256_SET(handle->buttons[i], remap_button);
}
else
{
int invert = 1;
if (remap_button % 2 != 0)
invert = -1;
handle->analog_value[i][
remap_button - RARCH_FIRST_CUSTOM_BIND] =
(p_new_state->analog_buttons[j]
? p_new_state->analog_buttons[j]
: 32767) * invert;
}
}
}
for (j = 0; j < 8; j++)
{
unsigned k = (unsigned)j + RARCH_FIRST_CUSTOM_BIND;
int16_t current_axis_value = p_new_state->analogs[j];
unsigned remap_axis = settings->uints.input_remap_ids[i][k];
if (
(abs(current_axis_value) > 0 &&
(k != remap_axis) &&
(remap_axis != RARCH_UNMAPPED)
))
{
if (remap_axis < RARCH_FIRST_CUSTOM_BIND &&
abs(current_axis_value) >
settings->floats.input_axis_threshold
* 32767)
{
BIT256_SET(handle->buttons[i], remap_axis);
}
else
{
unsigned remap_axis_bind =
remap_axis - RARCH_FIRST_CUSTOM_BIND;
if (remap_axis_bind < sizeof(handle->analog_value[i]))
{
int invert = 1;
if ( (k % 2 == 0 && remap_axis % 2 != 0) ||
(k % 2 != 0 && remap_axis % 2 == 0)
)
invert = -1;
handle->analog_value[i][
remap_axis_bind] =
current_axis_value * invert;
}
}
}
}
break;
default:
break;
}
}
}
#ifdef HAVE_COMMAND
for (i = 0; i < ARRAY_SIZE(p_rarch->input_driver_command); i++)
{
if (p_rarch->input_driver_command[i])
{
memset(p_rarch->input_driver_command[i]->state,
0, sizeof(p_rarch->input_driver_command[i]->state));
p_rarch->input_driver_command[i]->poll(
p_rarch->input_driver_command[i]);
}
}
#endif
#ifdef HAVE_NETWORKGAMEPAD
/* Poll remote */
if (p_rarch->input_driver_remote)
{
unsigned user;
for (user = 0; user < max_users; user++)
{
if (settings->bools.network_remote_enable_user[user])
{
#if defined(HAVE_NETWORKING) && defined(HAVE_NETWORKGAMEPAD)
fd_set fds;
ssize_t ret;
struct remote_message msg;
if (p_rarch->input_driver_remote->net_fd[user] < 0)
return;
FD_ZERO(&fds);
FD_SET(p_rarch->input_driver_remote->net_fd[user], &fds);
ret = recvfrom(p_rarch->input_driver_remote->net_fd[user],
(char*)&msg,
sizeof(msg), 0, NULL, NULL);
if (ret == sizeof(msg))
input_remote_parse_packet(&p_rarch->remote_st_ptr, &msg, user);
else if ((ret != -1) || ((errno != EAGAIN) && (errno != ENOENT)))
#endif
{
input_remote_state_t *input_state = &p_rarch->remote_st_ptr;
input_state->buttons[user] = 0;
input_state->analog[0][user] = 0;
input_state->analog[1][user] = 0;
input_state->analog[2][user] = 0;
input_state->analog[3][user] = 0;
}
}
}
}
#endif
}
static int16_t input_state_device(
struct rarch_state *p_rarch,
settings_t *settings,
input_mapper_t *handle,
unsigned input_analog_dpad_mode,
int16_t ret,
unsigned port, unsigned device,
unsigned idx, unsigned id,
bool button_mask)
{
int16_t res = 0;
switch (device)
{
case RETRO_DEVICE_JOYPAD:
if (id < RARCH_FIRST_META_KEY)
{
#ifdef HAVE_NETWORKGAMEPAD
/* Don't process binds if input is coming from Remote RetroPad */
if ( p_rarch->input_driver_remote
&& INPUT_REMOTE_KEY_PRESSED(p_rarch, id, port))
res |= 1;
else
#endif
{
bool bind_valid = p_rarch->libretro_input_binds[port]
&& p_rarch->libretro_input_binds[port][id].valid;
unsigned remap_button = settings->uints.input_remap_ids[port][id];
/* TODO/FIXME: What on earth is this code doing...? */
if (!
( bind_valid
&& id != remap_button
)
)
{
if (button_mask)
{
if (ret & (1 << id))
res |= (1 << id);
}
else
res = ret;
}
if (BIT256_GET(handle->buttons[port], id))
res = 1;
#ifdef HAVE_OVERLAY
/* Check if overlay is active and button
* corresponding to 'id' has been pressed */
if ((port == 0) &&
p_rarch->overlay_ptr &&
p_rarch->overlay_ptr->alive &&
BIT256_GET(p_rarch->overlay_ptr->overlay_state.buttons, id))
{
#ifdef HAVE_MENU
bool menu_driver_alive = p_rarch->menu_driver_alive;
#else
bool menu_driver_alive = false;
#endif
bool input_remap_binds_enable = settings->bools.input_remap_binds_enable;
/* This button has already been processed
* inside input_driver_poll() if all the
* following are true:
* > Menu driver is not running
* > Input remaps are enabled
* > 'id' is not equal to remapped button index
* If these conditions are met, input here
* is ignored */
if ((menu_driver_alive || !input_remap_binds_enable) ||
(id == remap_button))
res |= 1;
}
#endif
}
/* Don't allow turbo for D-pad. */
if ( (id < RETRO_DEVICE_ID_JOYPAD_UP) ||
( (id > RETRO_DEVICE_ID_JOYPAD_RIGHT) &&
(id <= RETRO_DEVICE_ID_JOYPAD_R3)))
{
/*
* Apply turbo button if activated.
*/
unsigned turbo_mode = settings->uints.input_turbo_mode;
if (turbo_mode > INPUT_TURBO_MODE_CLASSIC)
{
/* Pressing turbo button toggles turbo mode on or off.
* Holding the button will
* pass through, else the pressed state will be modulated by a
* periodic pulse defined by the configured duty cycle.
*/
/* Avoid detecting the turbo button being held as multiple toggles */
if (!p_rarch->input_driver_turbo_btns.frame_enable[port])
p_rarch->input_driver_turbo_btns.turbo_pressed[port] &= ~(1 << 31);
else if (p_rarch->input_driver_turbo_btns.turbo_pressed[port]>=0)
{
p_rarch->input_driver_turbo_btns.turbo_pressed[port] |= (1 << 31);
/* Toggle turbo for selected buttons. */
if (p_rarch->input_driver_turbo_btns.enable[port]
!= (1 << settings->uints.input_turbo_default_button))
{
static const int button_map[]={
RETRO_DEVICE_ID_JOYPAD_B,
RETRO_DEVICE_ID_JOYPAD_Y,
RETRO_DEVICE_ID_JOYPAD_A,
RETRO_DEVICE_ID_JOYPAD_X,
RETRO_DEVICE_ID_JOYPAD_L,
RETRO_DEVICE_ID_JOYPAD_R,
RETRO_DEVICE_ID_JOYPAD_L2,
RETRO_DEVICE_ID_JOYPAD_R2,
RETRO_DEVICE_ID_JOYPAD_L3,
RETRO_DEVICE_ID_JOYPAD_R3};
p_rarch->input_driver_turbo_btns.enable[port] = 1 << button_map[
MIN(
ARRAY_SIZE(button_map) - 1,
settings->uints.input_turbo_default_button)];
}
p_rarch->input_driver_turbo_btns.mode1_enable[port] ^= 1;
}
if (p_rarch->input_driver_turbo_btns.turbo_pressed[port] & (1 << 31))
{
/* Avoid detecting buttons being held as multiple toggles */
if (!res)
p_rarch->input_driver_turbo_btns.turbo_pressed[port] &= ~(1 << id);
else if (!(p_rarch->input_driver_turbo_btns.turbo_pressed[port] & (1 << id)) &&
turbo_mode == INPUT_TURBO_MODE_SINGLEBUTTON)
{
uint16_t enable_new;
p_rarch->input_driver_turbo_btns.turbo_pressed[port] |= 1 << id;
/* Toggle turbo for pressed button but make
* sure at least one button has turbo */
enable_new = p_rarch->input_driver_turbo_btns.enable[port] ^ (1 << id);
if (enable_new)
p_rarch->input_driver_turbo_btns.enable[port] = enable_new;
}
}
else if (turbo_mode == INPUT_TURBO_MODE_SINGLEBUTTON_HOLD &&
p_rarch->input_driver_turbo_btns.enable[port] &&
p_rarch->input_driver_turbo_btns.mode1_enable[port])
{
/* Hold mode stops turbo on release */
p_rarch->input_driver_turbo_btns.mode1_enable[port] = 0;
}
if (!res && p_rarch->input_driver_turbo_btns.mode1_enable[port] &&
p_rarch->input_driver_turbo_btns.enable[port] & (1 << id))
{
/* if turbo button is enabled for this key ID */
res = ((p_rarch->input_driver_turbo_btns.count
% settings->uints.input_turbo_period)
< settings->uints.input_turbo_duty_cycle);
}
}
else
{
/* If turbo button is held, all buttons pressed except
* for D-pad will go into a turbo mode. Until the button is
* released again, the input state will be modulated by a
* periodic pulse defined by the configured duty cycle.
*/
if (res)
{
if (p_rarch->input_driver_turbo_btns.frame_enable[port])
p_rarch->input_driver_turbo_btns.enable[port] |= (1 << id);
if (p_rarch->input_driver_turbo_btns.enable[port] & (1 << id))
/* if turbo button is enabled for this key ID */
res = ((p_rarch->input_driver_turbo_btns.count
% settings->uints.input_turbo_period)
< settings->uints.input_turbo_duty_cycle);
}
else
p_rarch->input_driver_turbo_btns.enable[port] &= ~(1 << id);
}
}
}
break;
case RETRO_DEVICE_KEYBOARD:
res = ret;
if (id < RETROK_LAST)
{
#ifdef HAVE_OVERLAY
if (port == 0)
{
if (p_rarch->overlay_ptr && p_rarch->overlay_ptr->alive)
{
input_overlay_state_t
*ol_state = &p_rarch->overlay_ptr->overlay_state;
if (OVERLAY_GET_KEY(ol_state, id))
res |= 1;
}
}
#endif
if (MAPPER_GET_KEY(handle, id))
res |= 1;
}
break;
case RETRO_DEVICE_ANALOG:
{
#if defined(HAVE_NETWORKGAMEPAD) || defined(HAVE_OVERLAY)
#ifdef HAVE_NETWORKGAMEPAD
input_remote_state_t
*input_state = &p_rarch->remote_st_ptr;
#endif
unsigned base = (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)
? 2 : 0;
if (id == RETRO_DEVICE_ID_ANALOG_Y)
base += 1;
#ifdef HAVE_NETWORKGAMEPAD
if (p_rarch->input_driver_remote
&& input_state && input_state->analog[base][port])
res = input_state->analog[base][port];
else
#endif
#endif
{
if (id < RARCH_FIRST_META_KEY)
{
bool bind_valid = p_rarch->libretro_input_binds[port]
&& p_rarch->libretro_input_binds[port][id].valid;
if (bind_valid)
{
/* reset_state - used to reset input state of a button
* when the gamepad mapper is in action for that button*/
bool reset_state = false;
if (idx < 2 && id < 2)
{
unsigned offset = RARCH_FIRST_CUSTOM_BIND +
(idx * 4) + (id * 2);
if (settings->uints.input_remap_ids[port][offset] != offset)
reset_state = true;
else if (settings->uints.input_remap_ids[port][offset+1] != (offset+1))
reset_state = true;
}
if (reset_state)
res = 0;
else
{
res = ret;
#ifdef HAVE_OVERLAY
if (p_rarch->overlay_ptr &&
p_rarch->overlay_ptr->alive &&
(port == 0) &&
!(((input_analog_dpad_mode == ANALOG_DPAD_LSTICK) &&
(idx == RETRO_DEVICE_INDEX_ANALOG_LEFT)) ||
((input_analog_dpad_mode == ANALOG_DPAD_RSTICK) &&
(idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT))))
{
input_overlay_state_t *ol_state =
&p_rarch->overlay_ptr->overlay_state;
int16_t ol_analog =
ol_state->analog[base];
/* Analog values are an integer corresponding
* to the extent of the analog motion; these
* cannot be OR'd together, we must instead
* keep the value with the largest magnitude */
if (ol_analog)
{
if (res == 0)
res = ol_analog;
else
{
int16_t ol_analog_abs = (ol_analog >= 0) ?
ol_analog : -ol_analog;
int16_t res_abs = (res >= 0) ?
res : -res;
res = (ol_analog_abs > res_abs) ?
ol_analog : res;
}
}
}
#endif
}
}
}
}
if (idx < 2 && id < 2)
{
unsigned offset = 0 + (idx * 4) + (id * 2);
int val1 = handle->analog_value[port][offset];
int val2 = handle->analog_value[port][offset+1];
/* OR'ing these analog values is 100% incorrect,
* but I have no idea what this code is supposed
* to be doing (val1 and val2 always seem to be
* zero), so I will leave it alone... */
if (val1)
res |= val1;
else if (val2)
res |= val2;
}
}
break;
case RETRO_DEVICE_MOUSE:
case RETRO_DEVICE_LIGHTGUN:
case RETRO_DEVICE_POINTER:
if (id < RARCH_FIRST_META_KEY)
{
bool bind_valid = p_rarch->libretro_input_binds[port]
&& p_rarch->libretro_input_binds[port][id].valid;
if (bind_valid)
{
if (button_mask)
{
if (ret & (1 << id))
res |= (1 << id);
}
else
res = ret;
}
}
break;
}
return res;
}
int16_t input_state_internal(unsigned port, unsigned device,
unsigned idx, unsigned id)
{
rarch_joypad_info_t joypad_info;
unsigned mapped_port;
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
settings_t *settings = p_rarch->configuration_settings;
float input_analog_deadzone = settings->floats.input_analog_deadzone;
float input_analog_sensitivity = settings->floats.input_analog_sensitivity;
unsigned *input_remap_port_map = settings->uints.input_remap_port_map[port];
bool input_driver_analog_requested = p_rarch->input_driver_analog_requested[port];
const input_device_driver_t *joypad = input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t *sec_joypad = input_driver_st->secondary_joypad;
#else
const input_device_driver_t *sec_joypad = NULL;
#endif
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
bool input_blocked = (menu_st->input_driver_flushing_input > 0) ||
p_rarch->input_driver_block_libretro_input;
#else
bool input_blocked = p_rarch->input_driver_block_libretro_input;
#endif
bool bitmask_enabled = false;
unsigned max_users = settings->uints.input_max_users;
int16_t result = 0;
device &= RETRO_DEVICE_MASK;
bitmask_enabled = (device == RETRO_DEVICE_JOYPAD) &&
(id == RETRO_DEVICE_ID_JOYPAD_MASK);
joypad_info.axis_threshold = settings->floats.input_axis_threshold;
/* Loop over all 'physical' ports mapped to specified
* 'virtual' port index */
while ((mapped_port = *(input_remap_port_map++)) < MAX_USERS)
{
int16_t ret = 0;
int16_t port_result = 0;
unsigned input_analog_dpad_mode = settings->uints.input_analog_dpad_mode[mapped_port];
joypad_info.joy_idx = settings->uints.input_joypad_index[mapped_port];
joypad_info.auto_binds = input_autoconf_binds[joypad_info.joy_idx];
/* Skip disabled input devices */
if (mapped_port >= max_users)
continue;
/* If core has requested analog input, disable
* analog to dpad mapping (unless forced) */
switch (input_analog_dpad_mode)
{
case ANALOG_DPAD_LSTICK:
case ANALOG_DPAD_RSTICK:
if (input_driver_analog_requested)
input_analog_dpad_mode = ANALOG_DPAD_NONE;
break;
case ANALOG_DPAD_LSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_LSTICK;
break;
case ANALOG_DPAD_RSTICK_FORCED:
input_analog_dpad_mode = ANALOG_DPAD_RSTICK;
break;
default:
break;
}
/* TODO/FIXME: This code is gibberish - a mess of nested
* refactors that make no sense whatsoever. The entire
* thing needs to be rewritten from scratch... */
ret = input_state_wrap(
p_rarch->input_driver_state.current_driver,
p_rarch->input_driver_state.current_data,
joypad,
sec_joypad,
&joypad_info,
p_rarch->libretro_input_binds,
p_rarch->keyboard_mapping_blocked,
mapped_port, device, idx, id);
if ((device == RETRO_DEVICE_ANALOG) &&
(ret == 0))
{
if (p_rarch->libretro_input_binds[mapped_port])
{
if (idx == RETRO_DEVICE_INDEX_ANALOG_BUTTON)
{
if (id < RARCH_FIRST_CUSTOM_BIND)
{
bool valid_bind = p_rarch->libretro_input_binds[mapped_port][id].valid;
if (valid_bind)
{
if (sec_joypad)
ret = input_joypad_analog_button(
input_analog_deadzone,
input_analog_sensitivity,
sec_joypad, &joypad_info,
id,
&p_rarch->libretro_input_binds[mapped_port][id]);
if (joypad && (ret == 0))
ret = input_joypad_analog_button(
input_analog_deadzone,
input_analog_sensitivity,
joypad, &joypad_info,
id,
&p_rarch->libretro_input_binds[mapped_port][id]);
}
}
}
else
{
if (sec_joypad)
ret = input_joypad_analog_axis(
input_analog_dpad_mode,
input_analog_deadzone,
input_analog_sensitivity,
sec_joypad,
&joypad_info,
idx,
id,
p_rarch->libretro_input_binds[mapped_port]);
if (joypad && (ret == 0))
ret = input_joypad_analog_axis(
input_analog_dpad_mode,
input_analog_deadzone,
input_analog_sensitivity,
joypad,
&joypad_info,
idx,
id,
p_rarch->libretro_input_binds[mapped_port]);
}
}
}
if (!input_blocked)
{
input_mapper_t *handle = &p_rarch->input_driver_mapper;
if (bitmask_enabled)
{
unsigned i;
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
if (input_state_device(p_rarch, settings, handle,
input_analog_dpad_mode, ret, mapped_port,
device, idx, i, true))
port_result |= (1 << i);
}
else
port_result = input_state_device(p_rarch, settings, handle,
input_analog_dpad_mode, ret, mapped_port,
device, idx, id, false);
}
/* Digital values are represented by a bitmap;
* we can just perform the logical OR of
* successive samples.
* Analog values are an integer corresponding
* to the extent of the analog motion; these
* cannot be OR'd together, we must instead
* keep the value with the largest magnitude */
if (device == RETRO_DEVICE_ANALOG)
{
if (result == 0)
result = port_result;
else
{
int16_t port_result_abs = (port_result >= 0) ?
port_result : -port_result;
int16_t result_abs = (result >= 0) ?
result : -result;
if (port_result_abs > result_abs)
result = port_result;
}
}
else
result |= port_result;
}
#ifdef HAVE_BSV_MOVIE
/* Save input to BSV record, if enabled */
if (BSV_MOVIE_IS_PLAYBACK_OFF())
{
result = swap_if_big16(result);
intfstream_write(
p_rarch->bsv_movie_state_handle->file, &result, 2);
}
#endif
return result;
}
/**
* input_state:
* @port : user number.
* @device : device identifier of user.
* @idx : index value of user.
* @id : identifier of key pressed by user.
*
* Input state callback function.
*
* Returns: Non-zero if the given key (identified by @id)
* was pressed by the user (assigned to @port).
**/
static int16_t input_state(unsigned port, unsigned device,
unsigned idx, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
int16_t result = 0;
#ifdef HAVE_BSV_MOVIE
/* Load input from BSV record, if enabled */
if (BSV_MOVIE_IS_PLAYBACK_ON())
{
int16_t bsv_result = 0;
if (intfstream_read(
p_rarch->bsv_movie_state_handle->file,
&bsv_result, 2) == 2)
{
#ifdef HAVE_CHEEVOS
rcheevos_pause_hardcore();
#endif
return swap_if_big16(bsv_result);
}
p_rarch->bsv_movie_state.movie_end = true;
}
#endif
/* Read input state */
result = input_state_internal(port, device, idx, id);
/* Register any analog stick input requests for
* this 'virtual' (core) port */
if ( (device == RETRO_DEVICE_ANALOG) &&
( (idx == RETRO_DEVICE_INDEX_ANALOG_LEFT) ||
(idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)))
p_rarch->input_driver_analog_requested[port] = true;
#ifdef HAVE_BSV_MOVIE
/* Save input to BSV record, if enabled */
if (BSV_MOVIE_IS_PLAYBACK_OFF())
{
result = swap_if_big16(result);
intfstream_write(p_rarch->bsv_movie_state_handle->file, &result, 2);
}
#endif
return result;
}
/* MENU INPUT */
#ifdef HAVE_MENU
/*
* This function gets called in order to process all input events
* for the current frame.
*
* Sends input code to menu for one frame.
*
* It uses as input the local variables 'input' and 'trigger_input'.
*
* Mouse and touch input events get processed inside this function.
*
* NOTE: 'input' and 'trigger_input' is sourced from the keyboard and/or
* the gamepad. It does not contain input state derived from the mouse
* and/or touch - this gets dealt with separately within this function.
*
* TODO/FIXME - maybe needs to be overhauled so we can send multiple
* events per frame if we want to, and we shouldn't send the
* entire button state either but do a separate event per button
* state.
*/
static unsigned menu_event(
struct rarch_state *p_rarch,
settings_t *settings,
input_bits_t *p_input,
input_bits_t *p_trigger_input,
bool display_kb)
{
/* Used for key repeat */
static float delay_timer = 0.0f;
static float delay_count = 0.0f;
static bool initial_held = true;
static bool first_held = false;
static unsigned ok_old = 0;
unsigned ret = MENU_ACTION_NOOP;
bool set_scroll = false;
size_t new_scroll_accel = 0;
struct menu_state *menu_st = menu_state_get_ptr();
menu_input_t *menu_input = &menu_st->input_state;
input_driver_state_t *input_driver_st =
&p_rarch->input_driver_state;
input_driver_t *current_input =
input_driver_st->current_driver;
const input_device_driver_t
*joypad = input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t *sec_joypad =
input_driver_st->secondary_joypad;
#else
const input_device_driver_t *sec_joypad = NULL;
#endif
gfx_display_t *p_disp = &p_rarch->dispgfx;
menu_input_pointer_hw_state_t *pointer_hw_state = &menu_st->input_pointer_hw_state;
menu_handle_t *menu = menu_st->driver_data;
bool keyboard_mapping_blocked = p_rarch->keyboard_mapping_blocked;
bool menu_mouse_enable = settings->bools.menu_mouse_enable;
bool menu_pointer_enable = settings->bools.menu_pointer_enable;
bool swap_ok_cancel_btns = settings->bools.input_menu_swap_ok_cancel_buttons;
bool menu_scroll_fast = settings->bools.menu_scroll_fast;
bool pointer_enabled = settings->bools.menu_pointer_enable;
unsigned input_touch_scale = settings->uints.input_touch_scale;
unsigned menu_scroll_delay =
settings->uints.menu_scroll_delay;
#ifdef HAVE_OVERLAY
bool input_overlay_enable = settings->bools.input_overlay_enable;
bool overlay_active = input_overlay_enable && p_rarch->overlay_ptr
&& p_rarch->overlay_ptr->alive;
#else
bool input_overlay_enable = false;
bool overlay_active = false;
#endif
unsigned menu_ok_btn = swap_ok_cancel_btns ?
RETRO_DEVICE_ID_JOYPAD_B : RETRO_DEVICE_ID_JOYPAD_A;
unsigned menu_cancel_btn = swap_ok_cancel_btns ?
RETRO_DEVICE_ID_JOYPAD_A : RETRO_DEVICE_ID_JOYPAD_B;
unsigned ok_current = BIT256_GET_PTR(p_input, menu_ok_btn);
unsigned ok_trigger = ok_current & ~ok_old;
unsigned i = 0;
static unsigned navigation_initial = 0;
unsigned navigation_current = 0;
unsigned navigation_buttons[6] =
{
RETRO_DEVICE_ID_JOYPAD_UP,
RETRO_DEVICE_ID_JOYPAD_DOWN,
RETRO_DEVICE_ID_JOYPAD_LEFT,
RETRO_DEVICE_ID_JOYPAD_RIGHT,
RETRO_DEVICE_ID_JOYPAD_L,
RETRO_DEVICE_ID_JOYPAD_R
};
ok_old = ok_current;
/* Get pointer (mouse + touchscreen) input
* Note: Must be done regardless of menu screensaver
* state */
/* > If pointer input is disabled, do nothing */
if (!menu_mouse_enable && !menu_pointer_enable)
menu_input->pointer.type = MENU_POINTER_DISABLED;
else
{
menu_input_pointer_hw_state_t mouse_hw_state = {0};
menu_input_pointer_hw_state_t touchscreen_hw_state = {0};
/* Read mouse */
if (menu_mouse_enable)
menu_input_get_mouse_hw_state(
p_disp,
menu,
input_driver_st,
current_input,
joypad,
sec_joypad,
keyboard_mapping_blocked,
menu_mouse_enable,
input_overlay_enable,
overlay_active,
&mouse_hw_state);
/* Read touchscreen
* Note: Could forgo this if mouse is currently active,
* but this is 'cleaner' code... (if performance is a
* concern - and it isn't - user can just disable touch
* screen support) */
if (menu_pointer_enable)
menu_input_get_touchscreen_hw_state(
p_disp,
menu,
input_driver_st,
current_input,
joypad,
sec_joypad,
keyboard_mapping_blocked,
overlay_active,
pointer_enabled,
input_touch_scale,
&touchscreen_hw_state);
/* Mouse takes precedence */
if (mouse_hw_state.active)
menu_input->pointer.type = MENU_POINTER_MOUSE;
else if (touchscreen_hw_state.active)
menu_input->pointer.type = MENU_POINTER_TOUCHSCREEN;
/* Copy input from the current device */
if (menu_input->pointer.type == MENU_POINTER_MOUSE)
memcpy(pointer_hw_state, &mouse_hw_state, sizeof(menu_input_pointer_hw_state_t));
else if (menu_input->pointer.type == MENU_POINTER_TOUCHSCREEN)
memcpy(pointer_hw_state, &touchscreen_hw_state, sizeof(menu_input_pointer_hw_state_t));
if (pointer_hw_state->active)
menu_st->input_last_time_us = menu_st->current_time_us;
}
/* Populate menu_input_state
* Note: dx, dy, ptr, y_accel, etc. entries are set elsewhere */
menu_input->pointer.x = pointer_hw_state->x;
menu_input->pointer.y = pointer_hw_state->y;
if (menu_input->select_inhibit || menu_input->cancel_inhibit)
{
menu_input->pointer.active = false;
menu_input->pointer.pressed = false;
}
else
{
menu_input->pointer.active = pointer_hw_state->active;
menu_input->pointer.pressed = pointer_hw_state->select_pressed;
}
/* If menu screensaver is active, any input
* is intercepted and used to switch it off */
if (menu_st->screensaver_active)
{
/* Check pointer input */
bool input_active = (menu_input->pointer.type != MENU_POINTER_DISABLED) &&
menu_input->pointer.active;
/* Check regular input */
if (!input_active)
input_active = bits_any_set(p_input->data, ARRAY_SIZE(p_input->data));
if (!input_active)
input_active = bits_any_set(p_trigger_input->data, ARRAY_SIZE(p_trigger_input->data));
/* Disable screensaver if required */
if (input_active)
{
menu_ctx_environment_t menu_environ;
menu_environ.type = MENU_ENVIRON_DISABLE_SCREENSAVER;
menu_environ.data = NULL;
menu_st->screensaver_active = false;
menu_st->input_last_time_us = menu_st->current_time_us;
menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
}
/* Annul received input */
menu_input->pointer.active = false;
menu_input->pointer.pressed = false;
menu_input->select_inhibit = true;
menu_input->cancel_inhibit = true;
pointer_hw_state->up_pressed = false;
pointer_hw_state->down_pressed = false;
pointer_hw_state->left_pressed = false;
pointer_hw_state->right_pressed = false;
return MENU_ACTION_NOOP;
}
/* Accelerate only navigation buttons */
for (i = 0; i < 6; i++)
{
if (BIT256_GET_PTR(p_input, navigation_buttons[i]))
navigation_current |= (1 << navigation_buttons[i]);
}
if (navigation_current)
{
if (!first_held)
{
/* Store first direction in order to block "diagonals" */
if (!navigation_initial)
navigation_initial = navigation_current;
/* don't run anything first frame, only capture held inputs
* for old_input_state. */
first_held = true;
if (initial_held)
delay_timer = menu_scroll_delay;
else
delay_timer = menu_scroll_fast ? 100 : 20;
delay_count = 0;
}
if (delay_count >= delay_timer)
{
uint32_t input_repeat = 0;
for (i = 0; i < 6; i++)
BIT32_SET(input_repeat, navigation_buttons[i]);
set_scroll = true;
first_held = false;
p_trigger_input->data[0] |= p_input->data[0] & input_repeat;
new_scroll_accel = menu_st->scroll.acceleration;
if (menu_scroll_fast)
new_scroll_accel = MIN(new_scroll_accel + 1, 64);
else
new_scroll_accel = MIN(new_scroll_accel + 1, 5);
}
initial_held = false;
}
else
{
set_scroll = true;
first_held = false;
initial_held = true;
navigation_initial = 0;
}
if (set_scroll)
menu_st->scroll.acceleration = (unsigned)(new_scroll_accel);
delay_count += anim_get_ptr()->delta_time;
if (display_kb)
{
bool show_osk_symbols = input_event_osk_show_symbol_pages(menu_st->driver_data);
input_event_osk_iterate(p_rarch->osk_grid, p_rarch->osk_idx);
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_DOWN))
{
menu_st->input_last_time_us = menu_st->current_time_us;
if (p_rarch->osk_ptr < 33)
p_rarch->osk_ptr += OSK_CHARS_PER_LINE;
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_UP))
{
menu_st->input_last_time_us = menu_st->current_time_us;
if (p_rarch->osk_ptr >= OSK_CHARS_PER_LINE)
p_rarch->osk_ptr -= OSK_CHARS_PER_LINE;
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_RIGHT))
{
menu_st->input_last_time_us = menu_st->current_time_us;
if (p_rarch->osk_ptr < 43)
p_rarch->osk_ptr += 1;
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_LEFT))
{
menu_st->input_last_time_us = menu_st->current_time_us;
if (p_rarch->osk_ptr >= 1)
p_rarch->osk_ptr -= 1;
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_L))
{
menu_st->input_last_time_us = menu_st->current_time_us;
if (p_rarch->osk_idx > OSK_TYPE_UNKNOWN + 1)
p_rarch->osk_idx = ((enum osk_type)
(p_rarch->osk_idx - 1));
else
p_rarch->osk_idx = ((enum osk_type)(show_osk_symbols
? OSK_TYPE_LAST - 1
: OSK_SYMBOLS_PAGE1));
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_R))
{
menu_st->input_last_time_us = menu_st->current_time_us;
if (p_rarch->osk_idx < (show_osk_symbols
? OSK_TYPE_LAST - 1
: OSK_SYMBOLS_PAGE1))
p_rarch->osk_idx = ((enum osk_type)(
p_rarch->osk_idx + 1));
else
p_rarch->osk_idx = ((enum osk_type)(OSK_TYPE_UNKNOWN + 1));
}
if (BIT256_GET_PTR(p_trigger_input, menu_ok_btn))
{
if (p_rarch->osk_ptr >= 0)
input_event_osk_append(
&p_rarch->keyboard_line,
&p_rarch->osk_idx,
&p_rarch->osk_last_codepoint,
&p_rarch->osk_last_codepoint_len,
p_rarch->osk_ptr,
show_osk_symbols,
p_rarch->osk_grid[p_rarch->osk_ptr]);
}
if (BIT256_GET_PTR(p_trigger_input, menu_cancel_btn))
input_keyboard_event(true, '\x7f', '\x7f',
0, RETRO_DEVICE_KEYBOARD);
/* send return key to close keyboard input window */
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_START))
input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
BIT256_CLEAR_ALL_PTR(p_trigger_input);
}
else
{
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_UP))
{
if (navigation_initial == (1 << RETRO_DEVICE_ID_JOYPAD_UP))
ret = MENU_ACTION_UP;
}
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_DOWN))
{
if (navigation_initial == (1 << RETRO_DEVICE_ID_JOYPAD_DOWN))
ret = MENU_ACTION_DOWN;
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_LEFT))
{
if (navigation_initial == (1 << RETRO_DEVICE_ID_JOYPAD_LEFT))
ret = MENU_ACTION_LEFT;
}
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_RIGHT))
{
if (navigation_initial == (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT))
ret = MENU_ACTION_RIGHT;
}
if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_L))
ret = MENU_ACTION_SCROLL_UP;
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_R))
ret = MENU_ACTION_SCROLL_DOWN;
else if (ok_trigger)
ret = MENU_ACTION_OK;
else if (BIT256_GET_PTR(p_trigger_input, menu_cancel_btn))
ret = MENU_ACTION_CANCEL;
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_X))
ret = MENU_ACTION_SEARCH;
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_Y))
ret = MENU_ACTION_SCAN;
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_START))
ret = MENU_ACTION_START;
else if (BIT256_GET_PTR(p_trigger_input, RETRO_DEVICE_ID_JOYPAD_SELECT))
ret = MENU_ACTION_INFO;
else if (BIT256_GET_PTR(p_trigger_input, RARCH_MENU_TOGGLE))
ret = MENU_ACTION_TOGGLE;
if (ret != MENU_ACTION_NOOP)
menu_st->input_last_time_us = menu_st->current_time_us;
}
return ret;
}
static int menu_input_pointer_post_iterate(
struct rarch_state *p_rarch,
gfx_display_t *p_disp,
retro_time_t current_time,
menu_file_list_cbs_t *cbs,
menu_entry_t *entry, unsigned action)
{
static retro_time_t start_time = 0;
static int16_t start_x = 0;
static int16_t start_y = 0;
static int16_t last_x = 0;
static int16_t last_y = 0;
static uint16_t dx_start_right_max = 0;
static uint16_t dx_start_left_max = 0;
static uint16_t dy_start_up_max = 0;
static uint16_t dy_start_down_max = 0;
static bool last_select_pressed = false;
static bool last_cancel_pressed = false;
static bool last_left_pressed = false;
static bool last_right_pressed = false;
static retro_time_t last_left_action_time = 0;
static retro_time_t last_right_action_time = 0;
static retro_time_t last_press_direction_time = 0;
bool attenuate_y_accel = true;
bool osk_active = menu_input_dialog_get_display_kb();
bool messagebox_active = false;
int ret = 0;
struct menu_state *menu_st = menu_state_get_ptr();
menu_input_pointer_hw_state_t *pointer_hw_state = &menu_st->input_pointer_hw_state;
menu_input_t *menu_input = &menu_st->input_state;
menu_handle_t *menu = menu_st->driver_data;
/* Check whether a message box is currently
* being shown
* > Note: This ignores input bind dialogs,
* since input binding overrides normal input
* and must be handled separately... */
if (menu)
messagebox_active = BIT64_GET(
menu->state, MENU_STATE_RENDER_MESSAGEBOX) &&
!string_is_empty(menu->menu_state_msg);
/* If onscreen keyboard is shown and we currently have
* active mouse input, highlight key under mouse cursor */
if (osk_active &&
(menu_input->pointer.type == MENU_POINTER_MOUSE) &&
pointer_hw_state->active)
{
menu_ctx_pointer_t point;
point.x = pointer_hw_state->x;
point.y = pointer_hw_state->y;
point.ptr = 0;
point.cbs = NULL;
point.entry = NULL;
point.action = 0;
point.gesture = MENU_INPUT_GESTURE_NONE;
point.retcode = 0;
menu_driver_ctl(RARCH_MENU_CTL_OSK_PTR_AT_POS, &point);
if (point.retcode > -1)
p_rarch->osk_ptr = point.retcode;
}
/* Select + X/Y position */
if (!menu_input->select_inhibit)
{
if (pointer_hw_state->select_pressed)
{
int16_t x = pointer_hw_state->x;
int16_t y = pointer_hw_state->y;
static float accel0 = 0.0f;
static float accel1 = 0.0f;
/* Transition from select unpressed to select pressed */
if (!last_select_pressed)
{
menu_ctx_pointer_t point;
/* Initialise variables */
start_time = current_time;
start_x = x;
start_y = y;
last_x = x;
last_y = y;
dx_start_right_max = 0;
dx_start_left_max = 0;
dy_start_up_max = 0;
dy_start_down_max = 0;
accel0 = 0.0f;
accel1 = 0.0f;
last_press_direction_time = 0;
/* If we are not currently showing the onscreen
* keyboard or a message box, trigger a 'pointer
* down' event */
if (!osk_active && !messagebox_active)
{
point.x = x;
point.y = y;
/* Note: menu_input->ptr is meaningless here when
* using a touchscreen... */
point.ptr = menu_input->ptr;
point.cbs = cbs;
point.entry = entry;
point.action = action;
point.gesture = MENU_INPUT_GESTURE_NONE;
menu_driver_ctl(RARCH_MENU_CTL_POINTER_DOWN, &point);
ret = point.retcode;
}
}
else
{
/* Pointer is being held down
* (i.e. for more than one frame) */
float dpi = menu ? menu_input_get_dpi(menu, p_disp,
p_rarch->video_driver_width, p_rarch->video_driver_height) : 0.0f;
/* > Update deltas + acceleration & detect press direction
* Note: We only do this if the pointer has moved above
* a certain threshold - this requires dpi info */
if (dpi > 0.0f)
{
uint16_t dpi_threshold_drag =
(uint16_t)((dpi * MENU_INPUT_DPI_THRESHOLD_DRAG) + 0.5f);
int16_t dx_start = x - start_x;
int16_t dy_start = y - start_y;
uint16_t dx_start_abs = dx_start < 0 ? dx_start * -1 : dx_start;
uint16_t dy_start_abs = dy_start < 0 ? dy_start * -1 : dy_start;
if ((dx_start_abs > dpi_threshold_drag) ||
(dy_start_abs > dpi_threshold_drag))
{
uint16_t dpi_threshold_press_direction_min =
(uint16_t)((dpi * MENU_INPUT_DPI_THRESHOLD_PRESS_DIRECTION_MIN) + 0.5f);
uint16_t dpi_threshold_press_direction_max =
(uint16_t)((dpi * MENU_INPUT_DPI_THRESHOLD_PRESS_DIRECTION_MAX) + 0.5f);
uint16_t dpi_threshold_press_direction_tangent =
(uint16_t)((dpi * MENU_INPUT_DPI_THRESHOLD_PRESS_DIRECTION_TANGENT) + 0.5f);
enum menu_input_pointer_press_direction
press_direction = MENU_INPUT_PRESS_DIRECTION_NONE;
float press_direction_amplitude = 0.0f;
retro_time_t press_direction_delay = MENU_INPUT_PRESS_DIRECTION_DELAY_MAX;
/* Pointer has moved a sufficient distance to
* trigger a 'dragged' state */
menu_input->pointer.dragged = true;
/* Here we diverge:
* > If onscreen keyboard or a message box is
* active, pointer deltas, acceleration and
* press direction must be inhibited
* > If not, input is processed normally */
if (osk_active || messagebox_active)
{
/* Inhibit normal pointer input */
menu_input->pointer.dx = 0;
menu_input->pointer.dy = 0;
menu_input->pointer.y_accel = 0.0f;
menu_input->pointer.press_direction = MENU_INPUT_PRESS_DIRECTION_NONE;
accel0 = 0.0f;
accel1 = 0.0f;
attenuate_y_accel = false;
}
else
{
/* Assign current deltas */
menu_input->pointer.dx = x - last_x;
menu_input->pointer.dy = y - last_y;
/* Update maximum start->current deltas */
if (dx_start > 0)
dx_start_right_max = (dx_start_abs > dx_start_right_max) ?
dx_start_abs : dx_start_right_max;
else
dx_start_left_max = (dx_start_abs > dx_start_left_max) ?
dx_start_abs : dx_start_left_max;
if (dy_start > 0)
dy_start_down_max = (dy_start_abs > dy_start_down_max) ?
dy_start_abs : dy_start_down_max;
else
dy_start_up_max = (dy_start_abs > dy_start_up_max) ?
dy_start_abs : dy_start_up_max;
/* Magic numbers... */
menu_input->pointer.y_accel = (accel0 + accel1 + (float)menu_input->pointer.dy) / 3.0f;
accel0 = accel1;
accel1 = menu_input->pointer.y_accel;
/* Acceleration decays over time - but if the value
* has been set on this frame, attenuation should
* be skipped */
attenuate_y_accel = false;
/* Check if pointer is being held in a particular
* direction */
menu_input->pointer.press_direction = MENU_INPUT_PRESS_DIRECTION_NONE;
/* > Press directions are actually triggered as a pulse train,
* since a continuous direction prevents fine control in the
* context of menu actions (i.e. it would be the same
* as always holding down a cursor key all the time - too fast
* to control). We therefore apply a low pass filter, with
* a variable frequency based upon the distance the user has
* dragged the pointer */
/* > Horizontal */
if ((dx_start_abs >= dpi_threshold_press_direction_min) &&
(dy_start_abs < dpi_threshold_press_direction_tangent))
{
press_direction = (dx_start > 0) ?
MENU_INPUT_PRESS_DIRECTION_RIGHT : MENU_INPUT_PRESS_DIRECTION_LEFT;
/* Get effective amplitude of press direction offset */
press_direction_amplitude =
(float)(dx_start_abs - dpi_threshold_press_direction_min) /
(float)(dpi_threshold_press_direction_max - dpi_threshold_press_direction_min);
}
/* > Vertical */
else if ((dy_start_abs >= dpi_threshold_press_direction_min) &&
(dx_start_abs < dpi_threshold_press_direction_tangent))
{
press_direction = (dy_start > 0) ?
MENU_INPUT_PRESS_DIRECTION_DOWN : MENU_INPUT_PRESS_DIRECTION_UP;
/* Get effective amplitude of press direction offset */
press_direction_amplitude =
(float)(dy_start_abs - dpi_threshold_press_direction_min) /
(float)(dpi_threshold_press_direction_max - dpi_threshold_press_direction_min);
}
if (press_direction != MENU_INPUT_PRESS_DIRECTION_NONE)
{
/* > Update low pass filter frequency */
if (press_direction_amplitude > 1.0f)
press_direction_delay = MENU_INPUT_PRESS_DIRECTION_DELAY_MIN;
else
press_direction_delay = MENU_INPUT_PRESS_DIRECTION_DELAY_MIN +
((MENU_INPUT_PRESS_DIRECTION_DELAY_MAX - MENU_INPUT_PRESS_DIRECTION_DELAY_MIN)*
(1.0f - press_direction_amplitude));
/* > Apply low pass filter */
if (current_time - last_press_direction_time > press_direction_delay)
{
menu_input->pointer.press_direction = press_direction;
last_press_direction_time = current_time;
}
}
}
}
else
{
/* Pointer is stationary */
menu_input->pointer.dx = 0;
menu_input->pointer.dy = 0;
menu_input->pointer.press_direction = MENU_INPUT_PRESS_DIRECTION_NONE;
/* Standard behaviour (on Android, at least) is to stop
* scrolling when the user touches the screen. We should
* therefore 'reset' y acceleration whenever the pointer
* is stationary - with two caveats:
* - We only disable scrolling if the pointer *remains*
* stationary. If the pointer is held down then
* subsequently moves, normal scrolling should resume
* - Halting the scroll immediately produces a very
* unpleasant 'jerky' user experience. To avoid this,
* we add a small delay between detecting a pointer
* down event and forcing y acceleration to zero
* NOTE: Of course, we must also 'reset' y acceleration
* whenever the onscreen keyboard or a message box is
* shown */
if ((!menu_input->pointer.dragged &&
(menu_input->pointer.press_duration > MENU_INPUT_Y_ACCEL_RESET_DELAY)) ||
(osk_active || messagebox_active))
{
menu_input->pointer.y_accel = 0.0f;
accel0 = 0.0f;
accel1 = 0.0f;
attenuate_y_accel = false;
}
}
}
else
{
/* No dpi info - just fallback to zero... */
menu_input->pointer.dx = 0;
menu_input->pointer.dy = 0;
menu_input->pointer.y_accel = 0.0f;
menu_input->pointer.press_direction = MENU_INPUT_PRESS_DIRECTION_NONE;
accel0 = 0.0f;
accel1 = 0.0f;
attenuate_y_accel = false;
}
/* > Update remaining variables */
menu_input->pointer.press_duration = current_time - start_time;
last_x = x;
last_y = y;
}
}
else if (last_select_pressed)
{
/* Transition from select pressed to select unpressed */
int16_t x;
int16_t y;
menu_ctx_pointer_t point;
if (menu_input->pointer.dragged)
{
/* Pointer has moved.
* When using a touchscreen, releasing a press
* resets the x/y position - so cannot use
* current hardware x/y values. Instead, use
* previous position from last time that a
* press was active */
x = last_x;
y = last_y;
}
else
{
/* Pointer is considered stationary,
* so use start position */
x = start_x;
y = start_y;
}
point.x = x;
point.y = y;
point.ptr = menu_input->ptr;
point.cbs = cbs;
point.entry = entry;
point.action = action;
point.gesture = MENU_INPUT_GESTURE_NONE;
/* On screen keyboard overrides normal menu input... */
if (osk_active)
{
/* If pointer has been 'dragged', then it counts as
* a miss. Only register 'release' event if pointer
* has remained stationary */
if (!menu_input->pointer.dragged)
{
menu_driver_ctl(RARCH_MENU_CTL_OSK_PTR_AT_POS, &point);
if (point.retcode > -1)
{
bool show_osk_symbols = input_event_osk_show_symbol_pages(menu_st->driver_data);
p_rarch->osk_ptr = point.retcode;
input_event_osk_append(
&p_rarch->keyboard_line,
&p_rarch->osk_idx,
&p_rarch->osk_last_codepoint,
&p_rarch->osk_last_codepoint_len,
point.retcode,
show_osk_symbols,
p_rarch->osk_grid[p_rarch->osk_ptr]);
}
}
}
/* Message boxes override normal menu input...
* > If a message box is shown, any kind of pointer
* gesture should close it */
else if (messagebox_active)
menu_input_pointer_close_messagebox(
menu_st);
/* Normal menu input */
else
{
/* Detect gesture type */
if (!menu_input->pointer.dragged)
{
/* Pointer hasn't moved - check press duration */
if (menu_input->pointer.press_duration
< MENU_INPUT_PRESS_TIME_SHORT)
point.gesture = MENU_INPUT_GESTURE_TAP;
else if (menu_input->pointer.press_duration
< MENU_INPUT_PRESS_TIME_LONG)
point.gesture = MENU_INPUT_GESTURE_SHORT_PRESS;
else
point.gesture = MENU_INPUT_GESTURE_LONG_PRESS;
}
else
{
/* Pointer has moved - check if this is a swipe */
float dpi = menu ? menu_input_get_dpi(menu, p_disp, p_rarch->video_driver_width, p_rarch->video_driver_height) : 0.0f;
if ((dpi > 0.0f)
&&
(menu_input->pointer.press_duration <
MENU_INPUT_SWIPE_TIMEOUT))
{
uint16_t dpi_threshold_swipe =
(uint16_t)((dpi * MENU_INPUT_DPI_THRESHOLD_SWIPE) + 0.5f);
uint16_t dpi_threshold_swipe_tangent =
(uint16_t)((dpi * MENU_INPUT_DPI_THRESHOLD_SWIPE_TANGENT) + 0.5f);
int16_t dx_start = x - start_x;
int16_t dy_start = y - start_y;
uint16_t dx_start_right_final = 0;
uint16_t dx_start_left_final = 0;
uint16_t dy_start_up_final = 0;
uint16_t dy_start_down_final = 0;
/* Get final deltas */
if (dx_start > 0)
dx_start_right_final = (uint16_t)dx_start;
else
dx_start_left_final = (uint16_t)
(dx_start * -1);
if (dy_start > 0)
dy_start_down_final = (uint16_t)dy_start;
else
dy_start_up_final = (uint16_t)
(dy_start * -1);
/* Swipe right */
if ( (dx_start_right_final > dpi_threshold_swipe)
&& (dx_start_left_max < dpi_threshold_swipe_tangent)
&& (dy_start_up_max < dpi_threshold_swipe_tangent)
&& (dy_start_down_max < dpi_threshold_swipe_tangent)
)
point.gesture = MENU_INPUT_GESTURE_SWIPE_RIGHT;
/* Swipe left */
else if (
(dx_start_right_max < dpi_threshold_swipe_tangent)
&& (dx_start_left_final > dpi_threshold_swipe)
&& (dy_start_up_max < dpi_threshold_swipe_tangent)
&& (dy_start_down_max < dpi_threshold_swipe_tangent)
)
point.gesture = MENU_INPUT_GESTURE_SWIPE_LEFT;
/* Swipe up */
else if (
(dx_start_right_max < dpi_threshold_swipe_tangent)
&& (dx_start_left_max < dpi_threshold_swipe_tangent)
&& (dy_start_up_final > dpi_threshold_swipe)
&& (dy_start_down_max < dpi_threshold_swipe_tangent)
)
point.gesture = MENU_INPUT_GESTURE_SWIPE_UP;
/* Swipe down */
else if (
(dx_start_right_max < dpi_threshold_swipe_tangent)
&& (dx_start_left_max < dpi_threshold_swipe_tangent)
&& (dy_start_up_max < dpi_threshold_swipe_tangent)
&& (dy_start_down_final > dpi_threshold_swipe)
)
point.gesture = MENU_INPUT_GESTURE_SWIPE_DOWN;
}
}
/* Trigger a 'pointer up' event */
menu_driver_ctl(RARCH_MENU_CTL_POINTER_UP, &point);
ret = point.retcode;
}
/* Reset variables */
start_x = 0;
start_y = 0;
last_x = 0;
last_y = 0;
dx_start_right_max = 0;
dx_start_left_max = 0;
dy_start_up_max = 0;
dy_start_down_max = 0;
last_press_direction_time = 0;
menu_input->pointer.press_duration = 0;
menu_input->pointer.press_direction = MENU_INPUT_PRESS_DIRECTION_NONE;
menu_input->pointer.dx = 0;
menu_input->pointer.dy = 0;
menu_input->pointer.dragged = false;
}
}
/* Adjust acceleration
* > If acceleration has not been set on this frame,
* apply normal attenuation */
if (attenuate_y_accel)
menu_input->pointer.y_accel *= MENU_INPUT_Y_ACCEL_DECAY_FACTOR;
/* If select has been released, disable any existing
* select inhibit */
if (!pointer_hw_state->select_pressed)
menu_input->select_inhibit = false;
/* Cancel */
if ( !menu_input->cancel_inhibit
&& pointer_hw_state->cancel_pressed
&& !last_cancel_pressed)
{
/* If currently showing a message box, close it */
if (messagebox_active)
menu_input_pointer_close_messagebox(menu_st);
/* If onscreen keyboard is shown, send a 'backspace' */
else if (osk_active)
input_keyboard_event(true, '\x7f', '\x7f',
0, RETRO_DEVICE_KEYBOARD);
/* ...otherwise, invoke standard MENU_ACTION_CANCEL
* action */
else
{
size_t selection = menu_st->selection_ptr;
ret = menu_entry_action(entry, selection, MENU_ACTION_CANCEL);
}
}
/* If cancel has been released, disable any existing
* cancel inhibit */
if (!pointer_hw_state->cancel_pressed)
menu_input->cancel_inhibit = false;
if (!messagebox_active)
{
/* Up/Down
* > Note 1: These always correspond to a mouse wheel, which
* handles differently from other inputs - i.e. we don't
* want a 'last pressed' check
* > Note 2: If a message box is currently shown, must
* inhibit input */
/* > Up */
if (pointer_hw_state->up_pressed)
{
size_t selection = menu_st->selection_ptr;
ret = menu_entry_action(
entry, selection, MENU_ACTION_UP);
}
/* > Down */
if (pointer_hw_state->down_pressed)
{
size_t selection = menu_st->selection_ptr;
ret = menu_entry_action(
entry, selection, MENU_ACTION_DOWN);
}
/* Left/Right
* > Note 1: These also always correspond to a mouse wheel...
* In this case, it's a mouse wheel *tilt* operation, which
* is incredibly annoying because holding a tilt direction
* rapidly toggles the input state. The repeat speed is so
* high that any sort of useable control is impossible - so
* we have to apply a 'low pass' filter by ignoring inputs
* that occur below a certain frequency...
* > Note 2: If a message box is currently shown, must
* inhibit input */
/* > Left */
if ( pointer_hw_state->left_pressed
&& !last_left_pressed)
{
if (current_time - last_left_action_time
> MENU_INPUT_HORIZ_WHEEL_DELAY)
{
size_t selection = menu_st->selection_ptr;
last_left_action_time = current_time;
ret = menu_entry_action(
entry, selection, MENU_ACTION_LEFT);
}
}
/* > Right */
if (
pointer_hw_state->right_pressed
&& !last_right_pressed)
{
if (current_time - last_right_action_time
> MENU_INPUT_HORIZ_WHEEL_DELAY)
{
size_t selection = menu_st->selection_ptr;
last_right_action_time = current_time;
ret = menu_entry_action(
entry, selection, MENU_ACTION_RIGHT);
}
}
}
last_select_pressed = pointer_hw_state->select_pressed;
last_cancel_pressed = pointer_hw_state->cancel_pressed;
last_left_pressed = pointer_hw_state->left_pressed;
last_right_pressed = pointer_hw_state->right_pressed;
return ret;
}
static int menu_input_post_iterate(
struct rarch_state *p_rarch,
gfx_display_t *p_disp,
struct menu_state *menu_st,
unsigned action,
retro_time_t current_time)
{
menu_entry_t entry;
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = menu_list ? MENU_LIST_GET_SELECTION(menu_list, (unsigned)0) : NULL;
size_t selection = menu_st->selection_ptr;
menu_file_list_cbs_t *cbs = selection_buf ?
(menu_file_list_cbs_t*)selection_buf->list[selection].actiondata
: NULL;
MENU_ENTRY_INIT(entry);
/* Note: If menu_input_pointer_post_iterate() is
* modified, will have to verify that these
* parameters remain unused... */
entry.rich_label_enabled = false;
entry.value_enabled = false;
entry.sublabel_enabled = false;
menu_entry_get(&entry, 0, selection, NULL, false);
return menu_input_pointer_post_iterate(p_rarch, p_disp,
current_time, cbs, &entry, action);
}
#endif
static INLINE bool input_keys_pressed_other_sources(
struct rarch_state *p_rarch,
unsigned i,
input_bits_t* p_new_state)
{
#ifdef HAVE_COMMAND
int j;
for (j = 0; j < ARRAY_SIZE(p_rarch->input_driver_command); j++)
if ((i < RARCH_BIND_LIST_END) && p_rarch->input_driver_command[j]
&& p_rarch->input_driver_command[j]->state[i])
return true;
#endif
#ifdef HAVE_OVERLAY
if (p_rarch->overlay_ptr &&
((BIT256_GET(p_rarch->overlay_ptr->overlay_state.buttons, i))))
return true;
#endif
#ifdef HAVE_NETWORKGAMEPAD
/* Only process key presses related to game input if using Remote RetroPad */
if (i < RARCH_CUSTOM_BIND_LIST_END
&& p_rarch->input_driver_remote
&& INPUT_REMOTE_KEY_PRESSED(p_rarch, i, 0))
return true;
#endif
return false;
}
/**
* input_keys_pressed:
*
* Grab an input sample for this frame.
*
* Returns: Input sample containing a mask of all pressed keys.
*/
static void input_keys_pressed(
unsigned port,
bool is_menu,
int input_hotkey_block_delay,
struct rarch_state *p_rarch,
input_bits_t *p_new_state,
const struct retro_keybind **binds,
const struct retro_keybind *binds_norm,
const struct retro_keybind *binds_auto,
const input_device_driver_t *joypad,
const input_device_driver_t *sec_joypad,
rarch_joypad_info_t *joypad_info)
{
unsigned i;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
if (CHECK_INPUT_DRIVER_BLOCK_HOTKEY(binds_norm, binds_auto))
{
if ( input_state_wrap(
input_driver_st->current_driver,
input_driver_st->current_data,
input_driver_st->primary_joypad,
sec_joypad,
joypad_info,
&binds[port],
p_rarch->keyboard_mapping_blocked,
port, RETRO_DEVICE_JOYPAD, 0,
RARCH_ENABLE_HOTKEY))
{
if (p_rarch->input_hotkey_block_counter < input_hotkey_block_delay)
p_rarch->input_hotkey_block_counter++;
else
p_rarch->input_driver_block_libretro_input = true;
}
else
{
p_rarch->input_hotkey_block_counter = 0;
p_rarch->input_driver_block_hotkey = true;
}
}
if ( !is_menu
&& binds[port][RARCH_GAME_FOCUS_TOGGLE].valid)
{
const struct retro_keybind *focus_binds_auto =
&input_autoconf_binds[port][RARCH_GAME_FOCUS_TOGGLE];
const struct retro_keybind *focus_normal =
&binds[port][RARCH_GAME_FOCUS_TOGGLE];
/* Allows rarch_focus_toggle hotkey to still work
* even though every hotkey is blocked */
if (CHECK_INPUT_DRIVER_BLOCK_HOTKEY(
focus_normal, focus_binds_auto))
{
if (input_state_wrap(
input_driver_st->current_driver,
input_driver_st->current_data,
input_driver_st->primary_joypad,
sec_joypad,
joypad_info,
&binds[port],
p_rarch->keyboard_mapping_blocked,
port,
RETRO_DEVICE_JOYPAD, 0, RARCH_GAME_FOCUS_TOGGLE))
p_rarch->input_driver_block_hotkey = false;
}
}
{
int16_t ret = 0;
/* Check the libretro input first */
if (!p_rarch->input_driver_block_libretro_input)
ret = input_state_wrap(
input_driver_st->current_driver,
input_driver_st->current_data,
input_driver_st->primary_joypad,
sec_joypad,
joypad_info, &binds[port],
p_rarch->keyboard_mapping_blocked,
port, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_MASK);
for (i = 0; i < RARCH_FIRST_META_KEY; i++)
{
if (
(ret & (UINT64_C(1) << i)) ||
input_keys_pressed_other_sources(p_rarch,
i, p_new_state))
{
BIT256_SET_PTR(p_new_state, i);
}
}
}
/* Check the hotkeys */
if (p_rarch->input_driver_block_hotkey)
{
for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
{
if (
BIT64_GET(lifecycle_state, i)
|| input_keys_pressed_other_sources(p_rarch, i, p_new_state))
{
BIT256_SET_PTR(p_new_state, i);
}
}
}
else
{
for (i = RARCH_FIRST_META_KEY; i < RARCH_BIND_LIST_END; i++)
{
bool bit_pressed = binds[port][i].valid
&& input_state_wrap(
input_driver_st->current_driver,
input_driver_st->current_data,
input_driver_st->primary_joypad,
sec_joypad,
joypad_info,
&binds[port],
p_rarch->keyboard_mapping_blocked,
port, RETRO_DEVICE_JOYPAD, 0, i);
if ( bit_pressed
|| BIT64_GET(lifecycle_state, i)
|| input_keys_pressed_other_sources(p_rarch, i, p_new_state))
{
BIT256_SET_PTR(p_new_state, i);
}
}
}
}
void input_driver_init_joypads(void)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
settings_t *settings = p_rarch->configuration_settings;
if (!input_driver_st->primary_joypad)
input_driver_st->primary_joypad = input_joypad_init_driver(
settings->arrays.input_joypad_driver,
input_driver_st->current_data);
#ifdef HAVE_MFI
if (!input_driver_st->secondary_joypad)
input_driver_st->secondary_joypad = input_joypad_init_driver(
"mfi",
input_driver_st->current_data);
#endif
}
#ifdef HAVE_COMMAND
static void input_driver_init_command(struct rarch_state *p_rarch,
settings_t *settings)
{
bool input_network_cmd_enable = settings->bools.network_cmd_enable;
unsigned network_cmd_port = settings->uints.network_cmd_port;
#ifdef HAVE_STDIN_CMD
bool input_stdin_cmd_enable = settings->bools.stdin_cmd_enable;
if (input_stdin_cmd_enable)
{
bool grab_stdin = p_rarch->input_driver_state.current_driver->grab_stdin &&
p_rarch->input_driver_state.current_driver->grab_stdin(p_rarch->input_driver_state.current_data);
if (grab_stdin)
{
RARCH_WARN("stdin command interface is desired, "
"but input driver has already claimed stdin.\n"
"Cannot use this command interface.\n");
}
else {
p_rarch->input_driver_command[0] = command_stdin_new();
if (!p_rarch->input_driver_command[1])
RARCH_ERR("Failed to initialize the stdin command interface.\n");
}
}
#endif
/* Initialize the network command interface */
#ifdef HAVE_NETWORK_CMD
if (input_network_cmd_enable)
{
p_rarch->input_driver_command[1] = command_network_new(network_cmd_port);
if (!p_rarch->input_driver_command[1])
RARCH_ERR("Failed to initialize the network command interface.\n");
}
#endif
#ifdef HAVE_LAKKA
p_rarch->input_driver_command[2] = command_uds_new();
if (!p_rarch->input_driver_command[2])
RARCH_ERR("Failed to initialize the UDS command interface.\n");
#endif
}
static void input_driver_deinit_command(struct rarch_state *p_rarch)
{
int i;
for (i = 0; i < ARRAY_SIZE(p_rarch->input_driver_command); i++)
{
if (p_rarch->input_driver_command[i])
p_rarch->input_driver_command[i]->destroy(
p_rarch->input_driver_command[i]);
p_rarch->input_driver_command[i] = NULL;
}
}
#endif
/**
* config_get_joypad_driver_options:
*
* Get an enumerated list of all joypad driver names, separated by '|'.
*
* Returns: string listing of all joypad driver names, separated by '|'.
**/
const char* config_get_joypad_driver_options(void)
{
return char_list_new_special(STRING_LIST_INPUT_JOYPAD_DRIVERS, NULL);
}
bool input_key_pressed(int key, bool keyboard_pressed)
{
/* If a keyboard key is pressed then immediately return
* true, otherwise call button_is_pressed to determine
* if the input comes from another input device */
if (!(
(key < RARCH_BIND_LIST_END)
&& keyboard_pressed
)
)
{
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
settings_t *settings = p_rarch->configuration_settings;
const input_device_driver_t
*joypad = (const input_device_driver_t*)
input_driver_st->primary_joypad;
const uint64_t bind_joykey = input_config_binds[0][key].joykey;
const uint64_t bind_joyaxis = input_config_binds[0][key].joyaxis;
const uint64_t autobind_joykey = input_autoconf_binds[0][key].joykey;
const uint64_t autobind_joyaxis= input_autoconf_binds[0][key].joyaxis;
uint16_t port = 0;
float axis_threshold = settings->floats.input_axis_threshold;
const uint64_t joykey = (bind_joykey != NO_BTN)
? bind_joykey : autobind_joykey;
const uint32_t joyaxis = (bind_joyaxis != AXIS_NONE)
? bind_joyaxis : autobind_joyaxis;
if ((uint16_t)joykey != NO_BTN && joypad->button(
port, (uint16_t)joykey))
return true;
if (joyaxis != AXIS_NONE &&
((float)abs(joypad->axis(port, joyaxis))
/ 0x8000) > axis_threshold)
return true;
return false;
}
return true;
}
bool input_mouse_grabbed(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->input_driver_grab_mouse_state;
}
void input_pad_connect(unsigned port, input_device_driver_t *driver)
{
struct rarch_state *p_rarch = &rarch_st;
if (port >= MAX_USERS || !driver)
{
RARCH_ERR("[Input]: input_pad_connect: bad parameters\n");
return;
}
if (p_rarch->pad_connection_listener)
p_rarch->pad_connection_listener->connected(port, driver);
input_autoconfigure_connect(driver->name(port), NULL, driver->ident,
port, 0, 0);
}
#ifdef HAVE_HID
const void *hid_driver_get_data(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->hid_data;
}
/* This is only to be called after we've invoked free() on the
* HID driver; the memory will have already been freed, so we need to
* reset the pointer.
*/
void hid_driver_reset_data(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->hid_data = NULL;
}
/**
* config_get_hid_driver_options:
*
* Get an enumerated list of all HID driver names, separated by '|'.
*
* Returns: string listing of all HID driver names, separated by '|'.
**/
const char* config_get_hid_driver_options(void)
{
return char_list_new_special(STRING_LIST_INPUT_HID_DRIVERS, NULL);
}
/**
* input_hid_init_first:
*
* Finds first suitable HID driver and initializes.
*
* Returns: HID driver if found, otherwise NULL.
**/
const hid_driver_t *input_hid_init_first(void)
{
unsigned i;
struct rarch_state *p_rarch = &rarch_st;
for (i = 0; hid_drivers[i]; i++)
{
p_rarch->hid_data = hid_drivers[i]->init();
if (p_rarch->hid_data)
{
RARCH_LOG("[Input]: Found HID driver: \"%s\".\n",
hid_drivers[i]->ident);
return hid_drivers[i];
}
}
return NULL;
}
#endif
/**
* input_keyboard_line_event:
* @state : Input keyboard line handle.
* @character : Inputted character.
*
* Called on every keyboard character event.
*
* Returns: true (1) on success, otherwise false (0).
**/
static bool input_keyboard_line_event(
struct rarch_state *p_rarch,
input_keyboard_line_t *state, uint32_t character)
{
char array[2];
bool ret = false;
const char *word = NULL;
char c = (character >= 128) ? '?' : character;
/* Treat extended chars as ? as we cannot support
* printable characters for unicode stuff. */
if (c == '\r' || c == '\n')
{
state->cb(state->userdata, state->buffer);
array[0] = c;
array[1] = 0;
ret = true;
word = array;
}
else if (c == '\b' || c == '\x7f') /* 0x7f is ASCII for del */
{
if (state->ptr)
{
unsigned i;
for (i = 0; i < p_rarch->osk_last_codepoint_len; i++)
{
memmove(state->buffer + state->ptr - 1,
state->buffer + state->ptr,
state->size - state->ptr + 1);
state->ptr--;
state->size--;
}
word = state->buffer;
}
}
else if (ISPRINT(c))
{
/* Handle left/right here when suitable */
char *newbuf = (char*)
realloc(state->buffer, state->size + 2);
if (!newbuf)
return false;
memmove(newbuf + state->ptr + 1,
newbuf + state->ptr,
state->size - state->ptr + 1);
newbuf[state->ptr] = c;
state->ptr++;
state->size++;
newbuf[state->size] = '\0';
state->buffer = newbuf;
array[0] = c;
array[1] = 0;
word = array;
}
/* OSK - update last character */
if (word)
osk_update_last_codepoint(
&p_rarch->osk_last_codepoint,
&p_rarch->osk_last_codepoint_len,
word);
return ret;
}
#if defined(HAVE_MENU) && defined(HAVE_ACCESSIBILITY)
static const char *accessibility_lut_name(char key)
{
switch (key)
{
#if 0
/* TODO/FIXME - overlaps with tilde */
case '`':
return "left quote";
#endif
case '`':
return "tilde";
case '!':
return "exclamation point";
case '@':
return "at sign";
case '#':
return "hash sign";
case '$':
return "dollar sign";
case '%':
return "percent sign";
case '^':
return "carrot";
case '&':
return "ampersand";
case '*':
return "asterisk";
case '(':
return "left bracket";
case ')':
return "right bracket";
case '-':
return "minus";
case '_':
return "underscore";
case '=':
return "equals";
case '+':
return "plus";
case '[':
return "left square bracket";
case '{':
return "left curl bracket";
case ']':
return "right square bracket";
case '}':
return "right curl bracket";
case '\\':
return "back slash";
case '|':
return "pipe";
case ';':
return "semicolon";
case ':':
return "colon";
case '\'':
return "single quote";
case '\"':
return "double quote";
case ',':
return "comma";
case '<':
return "left angle bracket";
case '.':
return "period";
case '>':
return "right angle bracket";
case '/':
return "front slash";
case '?':
return "question mark";
case ' ':
return "space";
default:
break;
}
return NULL;
}
#endif
/**
* input_keyboard_event:
* @down : Keycode was pressed down?
* @code : Keycode.
* @character : Character inputted.
* @mod : TODO/FIXME: ???
*
* Keyboard event utils. Called by drivers when keyboard events are fired.
* This interfaces with the global system driver struct and libretro callbacks.
**/
void input_keyboard_event(bool down, unsigned code,
uint32_t character, uint16_t mod, unsigned device)
{
static bool deferred_wait_keys;
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = p_rarch->configuration_settings;
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
/* If screensaver is active, then it should be
* disabled if:
* - Key is down AND
* - OSK is active, OR:
* - Key is *not* mapped to RetroPad input (these
* inputs are handled in menu_event() - if we
* allow mapped RetroPad keys to toggle off
* the screensaver, then we end up with a 'duplicate'
* input that will trigger unwanted menu action)
* - For extra amusement, a number of keyboard keys
* are hard-coded to RetroPad inputs (while the menu
* is running) in such a way that they cannot be
* detected via the regular 'keyboard_mapping_bits'
* record. We therefore have to check each of these
* explicitly...
* Otherwise, input is ignored whenever screensaver
* is active */
if (menu_st->screensaver_active)
{
if (down &&
(code != RETROK_UNKNOWN) &&
(menu_input_dialog_get_display_kb() ||
!((code == RETROK_SPACE) || /* RETRO_DEVICE_ID_JOYPAD_START */
(code == RETROK_SLASH) || /* RETRO_DEVICE_ID_JOYPAD_X */
(code == RETROK_RSHIFT) || /* RETRO_DEVICE_ID_JOYPAD_SELECT */
(code == RETROK_RIGHT) || /* RETRO_DEVICE_ID_JOYPAD_RIGHT */
(code == RETROK_LEFT) || /* RETRO_DEVICE_ID_JOYPAD_LEFT */
(code == RETROK_DOWN) || /* RETRO_DEVICE_ID_JOYPAD_DOWN */
(code == RETROK_UP) || /* RETRO_DEVICE_ID_JOYPAD_UP */
(code == RETROK_PAGEUP) || /* RETRO_DEVICE_ID_JOYPAD_L */
(code == RETROK_PAGEDOWN) || /* RETRO_DEVICE_ID_JOYPAD_R */
(code == RETROK_BACKSPACE) || /* RETRO_DEVICE_ID_JOYPAD_B */
(code == RETROK_RETURN) || /* RETRO_DEVICE_ID_JOYPAD_A */
(code == RETROK_DELETE) || /* RETRO_DEVICE_ID_JOYPAD_Y */
BIT512_GET(p_rarch->keyboard_mapping_bits, code))))
{
menu_ctx_environment_t menu_environ;
menu_environ.type = MENU_ENVIRON_DISABLE_SCREENSAVER;
menu_environ.data = NULL;
menu_st->screensaver_active = false;
menu_st->input_last_time_us = menu_st->current_time_us;
menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
}
return;
}
if (down)
menu_st->input_last_time_us = menu_st->current_time_us;
#ifdef HAVE_ACCESSIBILITY
if (menu_input_dialog_get_display_kb()
&& down && is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
{
if (code != 303 && code != 0)
{
char* say_char = (char*)malloc(sizeof(char)+1);
if (say_char)
{
char c = (char) character;
*say_char = c;
say_char[1] = '\0';
if (character == 127 || character == 8)
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
"backspace", 10);
else
{
const char *lut_name = accessibility_lut_name(c);
if (lut_name)
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
lut_name, 10);
else if (character != 0)
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
say_char, 10);
}
free(say_char);
}
}
}
#endif
#endif
if (deferred_wait_keys)
{
if (down)
return;
p_rarch->keyboard_press_cb = NULL;
p_rarch->keyboard_press_data = NULL;
p_rarch->keyboard_mapping_blocked = false;
deferred_wait_keys = false;
}
else if (p_rarch->keyboard_press_cb)
{
if (!down || code == RETROK_UNKNOWN)
return;
if (p_rarch->keyboard_press_cb(p_rarch->keyboard_press_data, code))
return;
deferred_wait_keys = true;
}
else if (p_rarch->keyboard_line.enabled)
{
if (!down)
return;
switch (device)
{
case RETRO_DEVICE_POINTER:
if (code != 0x12d)
character = (char)code;
/* fall-through */
default:
if (!input_keyboard_line_event(p_rarch,
&p_rarch->keyboard_line, character))
return;
break;
}
/* Line is complete, can free it now. */
if (p_rarch->keyboard_line.buffer)
free(p_rarch->keyboard_line.buffer);
p_rarch->keyboard_line.buffer = NULL;
p_rarch->keyboard_line.ptr = 0;
p_rarch->keyboard_line.size = 0;
p_rarch->keyboard_line.cb = NULL;
p_rarch->keyboard_line.userdata = NULL;
p_rarch->keyboard_line.enabled = false;
/* Unblock all hotkeys. */
p_rarch->keyboard_mapping_blocked = false;
}
else
{
if (code == RETROK_UNKNOWN)
return;
/* Block hotkey + RetroPad mapped keyboard key events,
* but not with game focus, and from keyboard device type,
* and with 'enable_hotkey' modifier set and unpressed */
if (!p_rarch->game_focus_state.enabled &&
BIT512_GET(p_rarch->keyboard_mapping_bits, code))
{
input_mapper_t *handle = &p_rarch->input_driver_mapper;
struct retro_keybind hotkey = input_config_binds[0][RARCH_ENABLE_HOTKEY];
bool hotkey_pressed =
(p_rarch->input_hotkey_block_counter > 0) || (hotkey.key == code);
if (!(MAPPER_GET_KEY(handle, code)) &&
!(!hotkey_pressed && (
hotkey.key != RETROK_UNKNOWN ||
hotkey.joykey != NO_BTN ||
hotkey.joyaxis != AXIS_NONE
)))
return;
}
{
retro_keyboard_event_t *key_event = &runloop_state.key_event;
if (*key_event)
(*key_event)(down, code, character, mod);
}
}
}
/* input_device_info wrappers START */
unsigned input_config_get_device_count(void)
{
unsigned num_devices;
struct rarch_state *p_rarch = &rarch_st;
for (num_devices = 0; num_devices < MAX_INPUT_DEVICES; ++num_devices)
{
if (string_is_empty(p_rarch->input_device_info[num_devices].name))
break;
}
return num_devices;
}
/* Adds an index to devices with the same name,
* so they can be uniquely identified in the
* frontend */
static void input_config_reindex_device_names(struct rarch_state *p_rarch)
{
unsigned i;
unsigned j;
unsigned name_index;
/* Reset device name indices */
for (i = 0; i < MAX_INPUT_DEVICES; i++)
p_rarch->input_device_info[i].name_index = 0;
/* Scan device names */
for (i = 0; i < MAX_INPUT_DEVICES; i++)
{
const char *device_name = input_config_get_device_name(i);
/* If current device name is empty, or a non-zero
* name index has already been assigned, continue
* to the next device */
if (
string_is_empty(device_name)
|| p_rarch->input_device_info[i].name_index != 0)
continue;
/* > Uniquely named devices have a name index
* of 0
* > Devices with the same name have a name
* index starting from 1 */
name_index = 1;
/* Loop over all devices following the current
* selection */
for (j = i + 1; j < MAX_INPUT_DEVICES; j++)
{
const char *next_device_name = input_config_get_device_name(j);
if (string_is_empty(next_device_name))
continue;
/* Check if names match */
if (string_is_equal(device_name, next_device_name))
{
/* If this is the first match, set a starting
* index for the current device selection */
if (p_rarch->input_device_info[i].name_index == 0)
p_rarch->input_device_info[i].name_index = name_index++;
/* Set name index for the next device
* (will keep incrementing as more matches
* are found) */
p_rarch->input_device_info[j].name_index = name_index++;
}
}
}
}
/* > Get input_device_info */
const char *input_config_get_device_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(p_rarch->input_device_info[port].name))
return NULL;
return p_rarch->input_device_info[port].name;
}
const char *input_config_get_device_display_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(p_rarch->input_device_info[port].display_name))
return NULL;
return p_rarch->input_device_info[port].display_name;
}
const char *input_config_get_mouse_display_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(p_rarch->input_mouse_info[port].display_name))
return NULL;
return p_rarch->input_mouse_info[port].display_name;
}
const char *input_config_get_device_config_path(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(p_rarch->input_device_info[port].config_path))
return NULL;
return p_rarch->input_device_info[port].config_path;
}
const char *input_config_get_device_config_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(p_rarch->input_device_info[port].config_name))
return NULL;
return p_rarch->input_device_info[port].config_name;
}
const char *input_config_get_device_joypad_driver(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(p_rarch->input_device_info[port].joypad_driver))
return NULL;
return p_rarch->input_device_info[port].joypad_driver;
}
uint16_t input_config_get_device_vid(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->input_device_info[port].vid;
}
uint16_t input_config_get_device_pid(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->input_device_info[port].pid;
}
bool input_config_get_device_autoconfigured(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->input_device_info[port].autoconfigured;
}
unsigned input_config_get_device_name_index(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->input_device_info[port].name_index;
}
/* TODO/FIXME: This is required by linuxraw_joypad.c
* and parport_joypad.c. These input drivers should
* be refactored such that this dubious low-level
* access is not required */
char *input_config_get_device_name_ptr(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->input_device_info[port].name;
}
size_t input_config_get_device_name_size(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
return sizeof(p_rarch->input_device_info[port].name);
}
/* > Set input_device_info */
void input_config_set_device_name(unsigned port, const char *name)
{
struct rarch_state *p_rarch = &rarch_st;
if (string_is_empty(name))
return;
strlcpy(p_rarch->input_device_info[port].name, name,
sizeof(p_rarch->input_device_info[port].name));
input_config_reindex_device_names(p_rarch);
}
void input_config_set_device_display_name(unsigned port, const char *name)
{
struct rarch_state *p_rarch = &rarch_st;
if (!string_is_empty(name))
strlcpy(p_rarch->input_device_info[port].display_name, name,
sizeof(p_rarch->input_device_info[port].display_name));
}
void input_config_set_mouse_display_name(unsigned port, const char *name)
{
struct rarch_state *p_rarch = &rarch_st;
if (!string_is_empty(name))
strlcpy(p_rarch->input_mouse_info[port].display_name, name,
sizeof(p_rarch->input_mouse_info[port].display_name));
}
void input_config_set_device_config_path(unsigned port, const char *path)
{
if (!string_is_empty(path))
{
char parent_dir_name[128];
struct rarch_state *p_rarch = &rarch_st;
parent_dir_name[0] = '\0';
if (fill_pathname_parent_dir_name(parent_dir_name,
path, sizeof(parent_dir_name)))
fill_pathname_join(p_rarch->input_device_info[port].config_path,
parent_dir_name, path_basename(path),
sizeof(p_rarch->input_device_info[port].config_path));
}
}
void input_config_set_device_config_name(unsigned port, const char *name)
{
struct rarch_state *p_rarch = &rarch_st;
if (!string_is_empty(name))
strlcpy(p_rarch->input_device_info[port].config_name, name,
sizeof(p_rarch->input_device_info[port].config_name));
}
void input_config_set_device_joypad_driver(unsigned port, const char *driver)
{
struct rarch_state *p_rarch = &rarch_st;
if (!string_is_empty(driver))
strlcpy(p_rarch->input_device_info[port].joypad_driver, driver,
sizeof(p_rarch->input_device_info[port].joypad_driver));
}
void input_config_set_device_vid(unsigned port, uint16_t vid)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].vid = vid;
}
void input_config_set_device_pid(unsigned port, uint16_t pid)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].pid = pid;
}
void input_config_set_device_autoconfigured(unsigned port, bool autoconfigured)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].autoconfigured = autoconfigured;
}
void input_config_set_device_name_index(unsigned port, unsigned name_index)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].name_index = name_index;
}
/* > Clear input_device_info */
void input_config_clear_device_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].name[0] = '\0';
input_config_reindex_device_names(p_rarch);
}
void input_config_clear_device_display_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].display_name[0] = '\0';
}
void input_config_clear_device_config_path(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].config_path[0] = '\0';
}
void input_config_clear_device_config_name(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].config_name[0] = '\0';
}
void input_config_clear_device_joypad_driver(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_device_info[port].joypad_driver[0] = '\0';
}
/* input_device_info wrappers END */
unsigned *input_config_get_device_ptr(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
return &settings->uints.input_libretro_device[port];
}
unsigned input_config_get_device(unsigned port)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
return settings->uints.input_libretro_device[port];
}
void input_config_set_device(unsigned port, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
if (settings)
configuration_set_uint(settings,
settings->uints.input_libretro_device[port], id);
}
const struct retro_keybind *input_config_get_bind_auto(
unsigned port, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
unsigned joy_idx = settings->uints.input_joypad_index[port];
if (joy_idx < MAX_USERS)
return &input_autoconf_binds[joy_idx][id];
return NULL;
}
void input_config_reset(void)
{
unsigned i;
struct rarch_state *p_rarch = &rarch_st;
retro_assert(sizeof(input_config_binds[0]) >= sizeof(retro_keybinds_1));
retro_assert(sizeof(input_config_binds[1]) >= sizeof(retro_keybinds_rest));
memcpy(input_config_binds[0], retro_keybinds_1, sizeof(retro_keybinds_1));
for (i = 1; i < MAX_USERS; i++)
memcpy(input_config_binds[i], retro_keybinds_rest,
sizeof(retro_keybinds_rest));
for (i = 0; i < MAX_USERS; i++)
{
/* Note: Don't use input_config_clear_device_name()
* here, since this will re-index devices each time
* (not required - we are setting all 'name indices'
* to zero manually) */
p_rarch->input_device_info[i].name[0] = '\0';
p_rarch->input_device_info[i].display_name[0] = '\0';
p_rarch->input_device_info[i].config_path[0] = '\0';
p_rarch->input_device_info[i].config_name[0] = '\0';
p_rarch->input_device_info[i].joypad_driver[0] = '\0';
p_rarch->input_device_info[i].vid = 0;
p_rarch->input_device_info[i].pid = 0;
p_rarch->input_device_info[i].autoconfigured = false;
p_rarch->input_device_info[i].name_index = 0;
input_config_reset_autoconfig_binds(i);
p_rarch->libretro_input_binds[i] = input_config_binds[i];
}
}
void config_read_keybinds_conf(void *data)
{
unsigned i;
config_file_t *conf = (config_file_t*)data;
struct rarch_state *p_rarch = &rarch_st;
if (!conf)
return;
for (i = 0; i < MAX_USERS; i++)
{
unsigned j;
for (j = 0; input_config_bind_map_get_valid(j); j++)
{
char str[256];
const struct input_bind_map *keybind =
(const struct input_bind_map*)INPUT_CONFIG_BIND_MAP_GET(j);
struct retro_keybind *bind = &input_config_binds[i][j];
bool meta = false;
const char *prefix = NULL;
const char *btn = NULL;
struct config_entry_list
*entry = NULL;
if (!bind || !bind->valid || !keybind)
continue;
if (!keybind->valid)
continue;
meta = keybind->meta;
btn = keybind->base;
prefix = input_config_get_prefix(i, meta);
if (!btn || !prefix)
continue;
str[0] = '\0';
fill_pathname_join_delim(str, prefix, btn, '_', sizeof(str));
/* Clear old mapping bit */
BIT512_CLEAR_PTR(&p_rarch->keyboard_mapping_bits, bind->key);
entry = config_get_entry(conf, str);
if (entry && !string_is_empty(entry->value))
bind->key = input_config_translate_str_to_rk(
entry->value);
/* Store mapping bit */
BIT512_SET_PTR(&p_rarch->keyboard_mapping_bits, bind->key);
input_config_parse_joy_button (str, conf, prefix, btn, bind);
input_config_parse_joy_axis (str, conf, prefix, btn, bind);
input_config_parse_mouse_button(str, conf, prefix, btn, bind);
}
}
}
/* AUDIO */
static enum resampler_quality audio_driver_get_resampler_quality(
settings_t *settings)
{
if (settings)
return (enum resampler_quality)settings->uints.audio_resampler_quality;
return RESAMPLER_QUALITY_DONTCARE;
}
#ifdef HAVE_AUDIOMIXER
audio_mixer_stream_t *audio_driver_mixer_get_stream(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
if (i > (AUDIO_MIXER_MAX_SYSTEM_STREAMS-1))
return NULL;
return &p_rarch->audio_mixer_streams[i];
}
const char *audio_driver_mixer_get_stream_name(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
if (i > (AUDIO_MIXER_MAX_SYSTEM_STREAMS-1))
return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
if (!string_is_empty(p_rarch->audio_mixer_streams[i].name))
return p_rarch->audio_mixer_streams[i].name;
return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
}
static void audio_driver_mixer_deinit(struct rarch_state *p_rarch)
{
unsigned i;
p_rarch->audio_mixer_active = false;
for (i = 0; i < AUDIO_MIXER_MAX_SYSTEM_STREAMS; i++)
{
audio_driver_mixer_stop_stream(i);
audio_driver_mixer_remove_stream(i);
}
audio_mixer_done();
}
#endif
/**
* audio_compute_buffer_statistics:
*
* Computes audio buffer statistics.
*
**/
static bool audio_compute_buffer_statistics(
struct rarch_state *p_rarch,
audio_statistics_t *stats)
{
unsigned i, low_water_size, high_water_size, avg, stddev;
uint64_t accum = 0;
uint64_t accum_var = 0;
unsigned low_water_count = 0;
unsigned high_water_count = 0;
unsigned samples = MIN(
(unsigned)p_rarch->audio_driver_free_samples_count,
AUDIO_BUFFER_FREE_SAMPLES_COUNT);
if (samples < 3)
return false;
stats->samples = (unsigned)
p_rarch->audio_driver_free_samples_count;
#ifdef WARPUP
/* uint64 to double not implemented, fair chance
* signed int64 to double doesn't exist either */
/* https://forums.libretro.com/t/unsupported-platform-help/13903/ */
(void)stddev;
#elif defined(_MSC_VER) && _MSC_VER <= 1200
/* FIXME: error C2520: conversion from unsigned __int64
* to double not implemented, use signed __int64 */
(void)stddev;
#else
for (i = 1; i < samples; i++)
accum += p_rarch->audio_driver_free_samples_buf[i];
avg = (unsigned)accum / (samples - 1);
for (i = 1; i < samples; i++)
{
int diff = avg - p_rarch->audio_driver_free_samples_buf[i];
accum_var += diff * diff;
}
stddev = (unsigned)
sqrt((double)accum_var / (samples - 2));
stats->average_buffer_saturation = (1.0f - (float)avg
/ p_rarch->audio_driver_buffer_size) * 100.0;
stats->std_deviation_percentage = ((float)stddev
/ p_rarch->audio_driver_buffer_size) * 100.0;
#endif
low_water_size = (unsigned)(p_rarch->audio_driver_buffer_size * 3 / 4);
high_water_size = (unsigned)(p_rarch->audio_driver_buffer_size / 4);
for (i = 1; i < samples; i++)
{
if (p_rarch->audio_driver_free_samples_buf[i] >= low_water_size)
low_water_count++;
else if (p_rarch->audio_driver_free_samples_buf[i] <= high_water_size)
high_water_count++;
}
stats->close_to_underrun = (100.0f * low_water_count) / (samples - 1);
stats->close_to_blocking = (100.0f * high_water_count) / (samples - 1);
return true;
}
#ifdef DEBUG
static void report_audio_buffer_statistics(struct rarch_state *p_rarch)
{
audio_statistics_t audio_stats;
audio_stats.samples = 0;
audio_stats.average_buffer_saturation = 0.0f;
audio_stats.std_deviation_percentage = 0.0f;
audio_stats.close_to_underrun = 0.0f;
audio_stats.close_to_blocking = 0.0f;
if (!audio_compute_buffer_statistics(p_rarch, &audio_stats))
return;
RARCH_LOG("[Audio]: Average audio buffer saturation: %.2f %%,"
" standard deviation (percentage points): %.2f %%.\n"
"[Audio]: Amount of time spent close to underrun: %.2f %%."
" Close to blocking: %.2f %%.\n",
audio_stats.average_buffer_saturation,
audio_stats.std_deviation_percentage,
audio_stats.close_to_underrun,
audio_stats.close_to_blocking);
}
#endif
/**
* config_get_audio_driver_options:
*
* Get an enumerated list of all audio driver names, separated by '|'.
*
* Returns: string listing of all audio driver names, separated by '|'.
**/
const char *config_get_audio_driver_options(void)
{
return char_list_new_special(STRING_LIST_AUDIO_DRIVERS, NULL);
}
static void audio_driver_deinit_resampler(struct rarch_state *p_rarch)
{
if (p_rarch->audio_driver_resampler && p_rarch->audio_driver_resampler_data)
p_rarch->audio_driver_resampler->free(p_rarch->audio_driver_resampler_data);
p_rarch->audio_driver_resampler = NULL;
p_rarch->audio_driver_resampler_data = NULL;
p_rarch->audio_driver_resampler_ident[0] = '\0';
p_rarch->audio_driver_resampler_quality = RESAMPLER_QUALITY_DONTCARE;
}
static bool audio_driver_deinit_internal(struct rarch_state *p_rarch,
bool audio_enable)
{
if (p_rarch->current_audio && p_rarch->current_audio->free)
{
if (p_rarch->audio_driver_context_audio_data)
p_rarch->current_audio->free(
p_rarch->audio_driver_context_audio_data);
p_rarch->audio_driver_context_audio_data = NULL;
}
if (p_rarch->audio_driver_output_samples_conv_buf)
memalign_free(p_rarch->audio_driver_output_samples_conv_buf);
p_rarch->audio_driver_output_samples_conv_buf = NULL;
if (p_rarch->audio_driver_input_data)
memalign_free(p_rarch->audio_driver_input_data);
p_rarch->audio_driver_input_data = NULL;
p_rarch->audio_driver_data_ptr = 0;
#ifdef HAVE_REWIND
if (p_rarch->audio_driver_rewind_buf)
memalign_free(p_rarch->audio_driver_rewind_buf);
p_rarch->audio_driver_rewind_buf = NULL;
p_rarch->audio_driver_rewind_size = 0;
#endif
if (!audio_enable)
{
p_rarch->audio_driver_active = false;
return false;
}
audio_driver_deinit_resampler(p_rarch);
if (p_rarch->audio_driver_output_samples_buf)
memalign_free(p_rarch->audio_driver_output_samples_buf);
p_rarch->audio_driver_output_samples_buf = NULL;
#ifdef HAVE_DSP_FILTER
audio_driver_dsp_filter_free();
#endif
#ifdef DEBUG
report_audio_buffer_statistics(p_rarch);
#endif
return true;
}
static bool audio_driver_free_devices_list(struct rarch_state *p_rarch)
{
if (!p_rarch->current_audio || !p_rarch->current_audio->device_list_free
|| !p_rarch->audio_driver_context_audio_data)
return false;
p_rarch->current_audio->device_list_free(
p_rarch->audio_driver_context_audio_data,
p_rarch->audio_driver_devices_list);
p_rarch->audio_driver_devices_list = NULL;
return true;
}
static bool audio_driver_deinit(struct rarch_state *p_rarch,
settings_t *settings)
{
#ifdef HAVE_AUDIOMIXER
audio_driver_mixer_deinit(p_rarch);
#endif
audio_driver_free_devices_list(p_rarch);
return audio_driver_deinit_internal(p_rarch,
settings->bools.audio_enable);
}
static bool audio_driver_find_driver(struct rarch_state *p_rarch,
settings_t *settings,
const char *prefix,
bool verbosity_enabled)
{
int i = (int)driver_find_index(
"audio_driver",
settings->arrays.audio_driver);
if (i >= 0)
p_rarch->current_audio = (const audio_driver_t*)
audio_drivers[i];
else
{
const audio_driver_t *tmp = NULL;
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.audio_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; audio_drivers[d]; d++)
{
if (audio_drivers[d])
RARCH_LOG_OUTPUT("\t%s\n", audio_drivers[d]->ident);
}
RARCH_WARN("Going to default to first %s...\n", prefix);
}
tmp = (const audio_driver_t*)audio_drivers[0];
if (!tmp)
return false;
p_rarch->current_audio = tmp;
}
return true;
}
static bool audio_driver_init_internal(
struct rarch_state *p_rarch,
settings_t *settings,
bool audio_cb_inited)
{
unsigned new_rate = 0;
float *samples_buf = NULL;
size_t max_bufsamples = AUDIO_CHUNK_SIZE_NONBLOCKING * 2;
bool audio_enable = settings->bools.audio_enable;
bool audio_sync = settings->bools.audio_sync;
bool audio_rate_control = settings->bools.audio_rate_control;
float slowmotion_ratio = settings->floats.slowmotion_ratio;
unsigned audio_latency = (runloop_state.audio_latency > settings->uints.audio_latency) ?
runloop_state.audio_latency : settings->uints.audio_latency;
#ifdef HAVE_REWIND
int16_t *rewind_buf = NULL;
#endif
/* Accomodate rewind since at some point we might have two full buffers. */
size_t outsamples_max = AUDIO_CHUNK_SIZE_NONBLOCKING * 2 * AUDIO_MAX_RATIO * slowmotion_ratio;
int16_t *conv_buf = (int16_t*)memalign_alloc(64, outsamples_max * sizeof(int16_t));
float *audio_buf = (float*)memalign_alloc(64, AUDIO_CHUNK_SIZE_NONBLOCKING * 2 * sizeof(float));
bool verbosity_enabled = verbosity_is_enabled();
convert_s16_to_float_init_simd();
convert_float_to_s16_init_simd();
/* Used for recording even if audio isn't enabled. */
retro_assert(conv_buf != NULL);
retro_assert(audio_buf != NULL);
if (!conv_buf || !audio_buf)
goto error;
memset(audio_buf, 0, AUDIO_CHUNK_SIZE_NONBLOCKING * 2 * sizeof(float));
p_rarch->audio_driver_input_data = audio_buf;
p_rarch->audio_driver_output_samples_conv_buf = conv_buf;
p_rarch->audio_driver_chunk_block_size = AUDIO_CHUNK_SIZE_BLOCKING;
p_rarch->audio_driver_chunk_nonblock_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
p_rarch->audio_driver_chunk_size = p_rarch->audio_driver_chunk_block_size;
#ifdef HAVE_REWIND
/* Needs to be able to hold full content of a full max_bufsamples
* in addition to its own. */
rewind_buf = (int16_t*)memalign_alloc(64, max_bufsamples * sizeof(int16_t));
retro_assert(rewind_buf != NULL);
if (!rewind_buf)
goto error;
p_rarch->audio_driver_rewind_buf = rewind_buf;
p_rarch->audio_driver_rewind_size = max_bufsamples;
#endif
if (!audio_enable)
{
p_rarch->audio_driver_active = false;
return false;
}
if (!(audio_driver_find_driver(p_rarch, settings,
"audio driver", verbosity_enabled)))
retroarch_fail(p_rarch, 1, "audio_driver_find()");
if (!p_rarch->current_audio || !p_rarch->current_audio->init)
{
RARCH_ERR("Failed to initialize audio driver. Will continue without audio.\n");
p_rarch->audio_driver_active = false;
return false;
}
#ifdef HAVE_THREADS
if (audio_cb_inited)
{
RARCH_LOG("[Audio]: Starting threaded audio driver ...\n");
if (!audio_init_thread(
&p_rarch->current_audio,
&p_rarch->audio_driver_context_audio_data,
*settings->arrays.audio_device
? settings->arrays.audio_device : NULL,
settings->uints.audio_output_sample_rate, &new_rate,
audio_latency,
settings->uints.audio_block_frames,
p_rarch->current_audio))
{
RARCH_ERR("Cannot open threaded audio driver ... Exiting ...\n");
retroarch_fail(p_rarch, 1, "audio_driver_init_internal()");
}
}
else
#endif
{
p_rarch->audio_driver_context_audio_data =
p_rarch->current_audio->init(*settings->arrays.audio_device ?
settings->arrays.audio_device : NULL,
settings->uints.audio_output_sample_rate,
audio_latency,
settings->uints.audio_block_frames,
&new_rate);
}
if (new_rate != 0)
configuration_set_int(settings, settings->uints.audio_output_sample_rate, new_rate);
if (!p_rarch->audio_driver_context_audio_data)
{
RARCH_ERR("Failed to initialize audio driver. Will continue without audio.\n");
p_rarch->audio_driver_active = false;
}
p_rarch->audio_driver_use_float = false;
if ( p_rarch->audio_driver_active
&& p_rarch->current_audio->use_float(
p_rarch->audio_driver_context_audio_data))
p_rarch->audio_driver_use_float = true;
if (!audio_sync && p_rarch->audio_driver_active)
{
if (p_rarch->audio_driver_active &&
p_rarch->audio_driver_context_audio_data)
p_rarch->current_audio->set_nonblock_state(
p_rarch->audio_driver_context_audio_data, true);
p_rarch->audio_driver_chunk_size =
p_rarch->audio_driver_chunk_nonblock_size;
}
if (p_rarch->audio_driver_input <= 0.0f)
{
/* Should never happen. */
RARCH_WARN("[Audio]: Input rate is invalid (%.3f Hz)."
" Using output rate (%u Hz).\n",
p_rarch->audio_driver_input, settings->uints.audio_output_sample_rate);
p_rarch->audio_driver_input = settings->uints.audio_output_sample_rate;
}
p_rarch->audio_source_ratio_original =
p_rarch->audio_source_ratio_current =
(double)settings->uints.audio_output_sample_rate / p_rarch->audio_driver_input;
if (!string_is_empty(settings->arrays.audio_resampler))
strlcpy(p_rarch->audio_driver_resampler_ident,
settings->arrays.audio_resampler,
sizeof(p_rarch->audio_driver_resampler_ident));
else
p_rarch->audio_driver_resampler_ident[0] = '\0';
p_rarch->audio_driver_resampler_quality =
audio_driver_get_resampler_quality(settings);
if (!retro_resampler_realloc(
&p_rarch->audio_driver_resampler_data,
&p_rarch->audio_driver_resampler,
p_rarch->audio_driver_resampler_ident,
p_rarch->audio_driver_resampler_quality,
p_rarch->audio_source_ratio_original))
{
RARCH_ERR("Failed to initialize resampler \"%s\".\n",
p_rarch->audio_driver_resampler_ident);
p_rarch->audio_driver_active = false;
}
p_rarch->audio_driver_data_ptr = 0;
retro_assert(settings->uints.audio_output_sample_rate <
p_rarch->audio_driver_input * AUDIO_MAX_RATIO);
samples_buf = (float*)memalign_alloc(64, outsamples_max * sizeof(float));
retro_assert(samples_buf != NULL);
if (!samples_buf)
goto error;
p_rarch->audio_driver_output_samples_buf = (float*)samples_buf;
p_rarch->audio_driver_control = false;
if (
!audio_cb_inited
&& p_rarch->audio_driver_active
&& audio_rate_control
)
{
/* Audio rate control requires write_avail
* and buffer_size to be implemented. */
if (p_rarch->current_audio->buffer_size)
{
p_rarch->audio_driver_buffer_size =
p_rarch->current_audio->buffer_size(
p_rarch->audio_driver_context_audio_data);
p_rarch->audio_driver_control = true;
}
else
RARCH_WARN("[Audio]: Rate control was desired, but driver does not support needed features.\n");
}
command_event(CMD_EVENT_DSP_FILTER_INIT, NULL);
p_rarch->audio_driver_free_samples_count = 0;
#ifdef HAVE_AUDIOMIXER
audio_mixer_init(settings->uints.audio_output_sample_rate);
#endif
/* Threaded driver is initially stopped. */
if (
p_rarch->audio_driver_active
&& audio_cb_inited
)
audio_driver_start(p_rarch,
false);
return true;
error:
return audio_driver_deinit(p_rarch, settings);
}
/**
* audio_driver_flush:
* @data : pointer to audio buffer.
* @right : amount of samples to write.
*
* Writes audio samples to audio driver. Will first
* perform DSP processing (if enabled) and resampling.
**/
static void audio_driver_flush(
struct rarch_state *p_rarch,
float slowmotion_ratio,
bool audio_fastforward_mute,
const int16_t *data, size_t samples,
bool is_slowmotion, bool is_fastmotion)
{
struct resampler_data src_data;
float audio_volume_gain = (p_rarch->audio_driver_mute_enable ||
(audio_fastforward_mute && is_fastmotion)) ?
0.0f : p_rarch->audio_driver_volume_gain;
src_data.data_out = NULL;
src_data.output_frames = 0;
convert_s16_to_float(p_rarch->audio_driver_input_data, data, samples,
audio_volume_gain);
src_data.data_in = p_rarch->audio_driver_input_data;
src_data.input_frames = samples >> 1;
#ifdef HAVE_DSP_FILTER
if (p_rarch->audio_driver_dsp)
{
struct retro_dsp_data dsp_data;
dsp_data.input = NULL;
dsp_data.input_frames = 0;
dsp_data.output = NULL;
dsp_data.output_frames = 0;
dsp_data.input = p_rarch->audio_driver_input_data;
dsp_data.input_frames = (unsigned)(samples >> 1);
retro_dsp_filter_process(p_rarch->audio_driver_dsp, &dsp_data);
if (dsp_data.output)
{
src_data.data_in = dsp_data.output;
src_data.input_frames = dsp_data.output_frames;
}
}
#endif
src_data.data_out = p_rarch->audio_driver_output_samples_buf;
if (p_rarch->audio_driver_control)
{
/* Readjust the audio input rate. */
int half_size =
(int)(p_rarch->audio_driver_buffer_size / 2);
int avail =
(int)p_rarch->current_audio->write_avail(
p_rarch->audio_driver_context_audio_data);
int delta_mid = avail - half_size;
double direction = (double)delta_mid / half_size;
double adjust = 1.0 +
p_rarch->audio_driver_rate_control_delta * direction;
unsigned write_idx =
p_rarch->audio_driver_free_samples_count++ &
(AUDIO_BUFFER_FREE_SAMPLES_COUNT - 1);
p_rarch->audio_driver_free_samples_buf
[write_idx] = avail;
p_rarch->audio_source_ratio_current =
p_rarch->audio_source_ratio_original * adjust;
#if 0
if (verbosity_is_enabled())
{
RARCH_LOG_OUTPUT("[Audio]: Audio buffer is %u%% full\n",
(unsigned)(100 - (avail * 100) /
p_rarch->audio_driver_buffer_size));
RARCH_LOG_OUTPUT("[Audio]: New rate: %lf, Orig rate: %lf\n",
p_rarch->audio_source_ratio_current,
p_rarch->audio_source_ratio_original);
}
#endif
}
src_data.ratio = p_rarch->audio_source_ratio_current;
if (is_slowmotion)
src_data.ratio *= slowmotion_ratio;
/* Note: Ideally we would divide by the user-configured
* 'fastforward_ratio' when fast forward is enabled,
* but in practice this doesn't work:
* - 'fastforward_ratio' is only a limit. If the host
* cannot push frames fast enough, the actual ratio
* will be lower - and crackling will ensue
* - Most of the time 'fastforward_ratio' will be
* zero (unlimited)
* So what we would need to do is measure the time since
* the last audio flush operation, and calculate a 'real'
* fast-forward ratio - but this doesn't work either.
* The measurement is inaccurate and the frame-by-frame
* fluctuations are too large, so crackling is unavoidable.
* Since it's going to crackle anyway, there's no point
* trying to do anything. Just leave the ratio as-is,
* and hope for the best... */
p_rarch->audio_driver_resampler->process(
p_rarch->audio_driver_resampler_data, &src_data);
#ifdef HAVE_AUDIOMIXER
if (p_rarch->audio_mixer_active)
{
bool override = true;
float mixer_gain = 0.0f;
bool audio_driver_mixer_mute_enable =
p_rarch->audio_driver_mixer_mute_enable;
if (!audio_driver_mixer_mute_enable)
{
if (p_rarch->audio_driver_mixer_volume_gain == 1.0f)
override = false;
mixer_gain =
p_rarch->audio_driver_mixer_volume_gain;
}
audio_mixer_mix(
p_rarch->audio_driver_output_samples_buf,
src_data.output_frames, mixer_gain, override);
}
#endif
{
const void *output_data = p_rarch->audio_driver_output_samples_buf;
unsigned output_frames = (unsigned)src_data.output_frames;
if (p_rarch->audio_driver_use_float)
output_frames *= sizeof(float);
else
{
convert_float_to_s16(p_rarch->audio_driver_output_samples_conv_buf,
(const float*)output_data, output_frames * 2);
output_data = p_rarch->audio_driver_output_samples_conv_buf;
output_frames *= sizeof(int16_t);
}
p_rarch->current_audio->write(
p_rarch->audio_driver_context_audio_data,
output_data, output_frames * 2);
}
}
/**
* audio_driver_sample:
* @left : value of the left audio channel.
* @right : value of the right audio channel.
*
* Audio sample render callback function.
**/
static void audio_driver_sample(int16_t left, int16_t right)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->audio_suspended)
return;
p_rarch->audio_driver_output_samples_conv_buf[p_rarch->audio_driver_data_ptr++] = left;
p_rarch->audio_driver_output_samples_conv_buf[p_rarch->audio_driver_data_ptr++] = right;
if (p_rarch->audio_driver_data_ptr < p_rarch->audio_driver_chunk_size)
return;
if ( p_rarch->recording_data &&
p_rarch->recording_driver &&
p_rarch->recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = p_rarch->audio_driver_output_samples_conv_buf;
ffemu_data.frames = p_rarch->audio_driver_data_ptr / 2;
p_rarch->recording_driver->push_audio(p_rarch->recording_data, &ffemu_data);
}
if (!(runloop_state.paused ||
!p_rarch->audio_driver_active ||
!p_rarch->audio_driver_output_samples_buf))
audio_driver_flush(
p_rarch,
p_rarch->configuration_settings->floats.slowmotion_ratio,
p_rarch->configuration_settings->bools.audio_fastforward_mute,
p_rarch->audio_driver_output_samples_conv_buf,
p_rarch->audio_driver_data_ptr,
runloop_state.slowmotion,
runloop_state.fastmotion);
p_rarch->audio_driver_data_ptr = 0;
}
#ifdef HAVE_MENU
static void audio_driver_menu_sample(void)
{
static int16_t samples_buf[1024] = {0};
struct rarch_state *p_rarch = &rarch_st;
struct retro_system_av_info *av_info = &p_rarch->video_driver_av_info;
const struct retro_system_timing *info =
(const struct retro_system_timing*)&av_info->timing;
unsigned sample_count = (info->sample_rate / info->fps) * 2;
bool check_flush = !(
runloop_state.paused ||
!p_rarch->audio_driver_active ||
!p_rarch->audio_driver_output_samples_buf);
if (p_rarch->audio_suspended)
check_flush = false;
while (sample_count > 1024)
{
if ( p_rarch->recording_data &&
p_rarch->recording_driver &&
p_rarch->recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = samples_buf;
ffemu_data.frames = 1024 / 2;
p_rarch->recording_driver->push_audio(
p_rarch->recording_data, &ffemu_data);
}
if (check_flush)
audio_driver_flush(
p_rarch,
p_rarch->configuration_settings->floats.slowmotion_ratio,
p_rarch->configuration_settings->bools.audio_fastforward_mute,
samples_buf,
1024,
runloop_state.slowmotion,
runloop_state.fastmotion);
sample_count -= 1024;
}
if ( p_rarch->recording_data &&
p_rarch->recording_driver &&
p_rarch->recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = samples_buf;
ffemu_data.frames = sample_count / 2;
p_rarch->recording_driver->push_audio(
p_rarch->recording_data, &ffemu_data);
}
if (check_flush)
audio_driver_flush(
p_rarch,
p_rarch->configuration_settings->floats.slowmotion_ratio,
p_rarch->configuration_settings->bools.audio_fastforward_mute,
samples_buf,
sample_count,
runloop_state.slowmotion,
runloop_state.fastmotion);
}
#endif
/**
* audio_driver_sample_batch:
* @data : pointer to audio buffer.
* @frames : amount of audio frames to push.
*
* Batched audio sample render callback function.
*
* Returns: amount of frames sampled. Will be equal to @frames
* unless @frames exceeds (AUDIO_CHUNK_SIZE_NONBLOCKING / 2).
**/
static size_t audio_driver_sample_batch(const int16_t *data, size_t frames)
{
struct rarch_state *p_rarch = &rarch_st;
if (frames > (AUDIO_CHUNK_SIZE_NONBLOCKING >> 1))
frames = AUDIO_CHUNK_SIZE_NONBLOCKING >> 1;
if (p_rarch->audio_suspended)
return frames;
if ( p_rarch->recording_data &&
p_rarch->recording_driver &&
p_rarch->recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = data;
ffemu_data.frames = (frames << 1) / 2;
p_rarch->recording_driver->push_audio(
p_rarch->recording_data, &ffemu_data);
}
if (!(
runloop_state.paused ||
!p_rarch->audio_driver_active ||
!p_rarch->audio_driver_output_samples_buf))
audio_driver_flush(
p_rarch,
p_rarch->configuration_settings->floats.slowmotion_ratio,
p_rarch->configuration_settings->bools.audio_fastforward_mute,
data,
frames << 1,
runloop_state.slowmotion,
runloop_state.fastmotion);
return frames;
}
#ifdef HAVE_REWIND
/**
* audio_driver_sample_rewind:
* @left : value of the left audio channel.
* @right : value of the right audio channel.
*
* Audio sample render callback function (rewind version).
* This callback function will be used instead of
* audio_driver_sample when rewinding is activated.
**/
static void audio_driver_sample_rewind(int16_t left, int16_t right)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->audio_driver_rewind_ptr == 0)
return;
p_rarch->audio_driver_rewind_buf[--p_rarch->audio_driver_rewind_ptr] = right;
p_rarch->audio_driver_rewind_buf[--p_rarch->audio_driver_rewind_ptr] = left;
}
/**
* audio_driver_sample_batch_rewind:
* @data : pointer to audio buffer.
* @frames : amount of audio frames to push.
*
* Batched audio sample render callback function (rewind version).
*
* This callback function will be used instead of
* audio_driver_sample_batch when rewinding is activated.
*
* Returns: amount of frames sampled. Will be equal to @frames
* unless @frames exceeds (AUDIO_CHUNK_SIZE_NONBLOCKING / 2).
**/
static size_t audio_driver_sample_batch_rewind(
const int16_t *data, size_t frames)
{
size_t i;
struct rarch_state *p_rarch = &rarch_st;
size_t samples = frames << 1;
for (i = 0; i < samples; i++)
{
if (p_rarch->audio_driver_rewind_ptr > 0)
p_rarch->audio_driver_rewind_buf[--p_rarch->audio_driver_rewind_ptr] = data[i];
}
return frames;
}
#endif
#ifdef HAVE_DSP_FILTER
void audio_driver_dsp_filter_free(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->audio_driver_dsp)
retro_dsp_filter_free(p_rarch->audio_driver_dsp);
p_rarch->audio_driver_dsp = NULL;
}
bool audio_driver_dsp_filter_init(const char *device)
{
retro_dsp_filter_t *audio_driver_dsp = NULL;
struct rarch_state *p_rarch = &rarch_st;
struct string_list *plugs = NULL;
#if defined(HAVE_DYLIB) && !defined(HAVE_FILTERS_BUILTIN)
char basedir[PATH_MAX_LENGTH];
char ext_name[PATH_MAX_LENGTH];
basedir[0] = ext_name[0] = '\0';
fill_pathname_basedir(basedir, device, sizeof(basedir));
if (!frontend_driver_get_core_extension(ext_name, sizeof(ext_name)))
return false;
plugs = dir_list_new(basedir, ext_name, false, true, false, false);
if (!plugs)
return false;
#endif
audio_driver_dsp = retro_dsp_filter_new(
device, plugs, p_rarch->audio_driver_input);
if (!audio_driver_dsp)
return false;
p_rarch->audio_driver_dsp = audio_driver_dsp;
return true;
}
#endif
void audio_driver_set_buffer_size(size_t bufsize)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_driver_buffer_size = bufsize;
}
static float audio_driver_monitor_adjust_system_rates(
double input_sample_rate,
double input_fps,
float video_refresh_rate,
unsigned video_swap_interval,
float audio_max_timing_skew)
{
float inp_sample_rate = input_sample_rate;
const float target_video_sync_rate = video_refresh_rate
/ video_swap_interval;
float timing_skew =
fabs(1.0f - input_fps / target_video_sync_rate);
if (timing_skew <= audio_max_timing_skew)
return (inp_sample_rate * target_video_sync_rate / input_fps);
return inp_sample_rate;
}
#ifdef HAVE_REWIND
void audio_driver_setup_rewind(void)
{
unsigned i;
struct rarch_state *p_rarch = &rarch_st;
/* Push audio ready to be played. */
p_rarch->audio_driver_rewind_ptr = p_rarch->audio_driver_rewind_size;
for (i = 0; i < p_rarch->audio_driver_data_ptr; i += 2)
{
if (p_rarch->audio_driver_rewind_ptr > 0)
p_rarch->audio_driver_rewind_buf[
--p_rarch->audio_driver_rewind_ptr] =
p_rarch->audio_driver_output_samples_conv_buf[i + 1];
if (p_rarch->audio_driver_rewind_ptr > 0)
p_rarch->audio_driver_rewind_buf[--p_rarch->audio_driver_rewind_ptr] =
p_rarch->audio_driver_output_samples_conv_buf[i + 0];
}
p_rarch->audio_driver_data_ptr = 0;
}
#endif
bool audio_driver_get_devices_list(void **data)
{
struct rarch_state *p_rarch = &rarch_st;
struct string_list**ptr = (struct string_list**)data;
if (!ptr)
return false;
*ptr = p_rarch->audio_driver_devices_list;
return true;
}
#ifdef HAVE_AUDIOMIXER
bool audio_driver_mixer_extension_supported(const char *ext)
{
unsigned i;
struct string_list str_list;
union string_list_elem_attr attr;
bool ret = false;
attr.i = 0;
if (!string_list_initialize(&str_list))
return false;
#ifdef HAVE_STB_VORBIS
string_list_append(&str_list, "ogg", attr);
#endif
#ifdef HAVE_IBXM
string_list_append(&str_list, "mod", attr);
string_list_append(&str_list, "s3m", attr);
string_list_append(&str_list, "xm", attr);
#endif
#ifdef HAVE_DR_FLAC
string_list_append(&str_list, "flac", attr);
#endif
#ifdef HAVE_DR_MP3
string_list_append(&str_list, "mp3", attr);
#endif
string_list_append(&str_list, "wav", attr);
for (i = 0; i < str_list.size; i++)
{
const char *str_ext = str_list.elems[i].data;
if (string_is_equal_noncase(str_ext, ext))
{
ret = true;
break;
}
}
string_list_deinitialize(&str_list);
return ret;
}
static int audio_mixer_find_index(
struct rarch_state *p_rarch,
audio_mixer_sound_t *sound)
{
unsigned i;
for (i = 0; i < AUDIO_MIXER_MAX_SYSTEM_STREAMS; i++)
{
audio_mixer_sound_t *handle = p_rarch->audio_mixer_streams[i].handle;
if (handle == sound)
return i;
}
return -1;
}
static void audio_mixer_play_stop_cb(
audio_mixer_sound_t *sound, unsigned reason)
{
struct rarch_state *p_rarch = &rarch_st;
int idx = audio_mixer_find_index(p_rarch, sound);
switch (reason)
{
case AUDIO_MIXER_SOUND_FINISHED:
audio_mixer_destroy(sound);
if (idx >= 0)
{
unsigned i = (unsigned)idx;
if (!string_is_empty(p_rarch->audio_mixer_streams[i].name))
free(p_rarch->audio_mixer_streams[i].name);
p_rarch->audio_mixer_streams[i].name = NULL;
p_rarch->audio_mixer_streams[i].state = AUDIO_STREAM_STATE_NONE;
p_rarch->audio_mixer_streams[i].volume = 0.0f;
p_rarch->audio_mixer_streams[i].buf = NULL;
p_rarch->audio_mixer_streams[i].stop_cb = NULL;
p_rarch->audio_mixer_streams[i].handle = NULL;
p_rarch->audio_mixer_streams[i].voice = NULL;
}
break;
case AUDIO_MIXER_SOUND_STOPPED:
break;
case AUDIO_MIXER_SOUND_REPEATED:
break;
}
}
static void audio_mixer_menu_stop_cb(
audio_mixer_sound_t *sound, unsigned reason)
{
struct rarch_state *p_rarch = &rarch_st;
int idx = audio_mixer_find_index(p_rarch, sound);
switch (reason)
{
case AUDIO_MIXER_SOUND_FINISHED:
if (idx >= 0)
{
unsigned i = (unsigned)idx;
p_rarch->audio_mixer_streams[i].state = AUDIO_STREAM_STATE_STOPPED;
p_rarch->audio_mixer_streams[i].volume = 0.0f;
}
break;
case AUDIO_MIXER_SOUND_STOPPED:
break;
case AUDIO_MIXER_SOUND_REPEATED:
break;
}
}
static void audio_mixer_play_stop_sequential_cb(
audio_mixer_sound_t *sound, unsigned reason)
{
struct rarch_state *p_rarch = &rarch_st;
int idx = audio_mixer_find_index(p_rarch, sound);
switch (reason)
{
case AUDIO_MIXER_SOUND_FINISHED:
audio_mixer_destroy(sound);
if (idx >= 0)
{
unsigned i = (unsigned)idx;
if (!string_is_empty(p_rarch->audio_mixer_streams[i].name))
free(p_rarch->audio_mixer_streams[i].name);
if (i < AUDIO_MIXER_MAX_STREAMS)
p_rarch->audio_mixer_streams[i].stream_type = AUDIO_STREAM_TYPE_USER;
else
p_rarch->audio_mixer_streams[i].stream_type = AUDIO_STREAM_TYPE_SYSTEM;
p_rarch->audio_mixer_streams[i].name = NULL;
p_rarch->audio_mixer_streams[i].state = AUDIO_STREAM_STATE_NONE;
p_rarch->audio_mixer_streams[i].volume = 0.0f;
p_rarch->audio_mixer_streams[i].buf = NULL;
p_rarch->audio_mixer_streams[i].stop_cb = NULL;
p_rarch->audio_mixer_streams[i].handle = NULL;
p_rarch->audio_mixer_streams[i].voice = NULL;
i++;
for (; i < AUDIO_MIXER_MAX_SYSTEM_STREAMS; i++)
{
if (p_rarch->audio_mixer_streams[i].state
== AUDIO_STREAM_STATE_STOPPED)
{
audio_driver_mixer_play_stream_sequential(i);
break;
}
}
}
break;
case AUDIO_MIXER_SOUND_STOPPED:
break;
case AUDIO_MIXER_SOUND_REPEATED:
break;
}
}
static bool audio_driver_mixer_get_free_stream_slot(
unsigned *id, enum audio_mixer_stream_type type)
{
unsigned i = AUDIO_MIXER_MAX_STREAMS;
unsigned count = AUDIO_MIXER_MAX_SYSTEM_STREAMS;
struct rarch_state *p_rarch = &rarch_st;
if (type == AUDIO_STREAM_TYPE_USER)
{
i = 0;
count = AUDIO_MIXER_MAX_STREAMS;
}
for (; i < count; i++)
{
if (p_rarch->audio_mixer_streams[i].state == AUDIO_STREAM_STATE_NONE)
{
*id = i;
return true;
}
}
return false;
}
bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params)
{
struct rarch_state *p_rarch = &rarch_st;
unsigned free_slot = 0;
audio_mixer_voice_t *voice = NULL;
audio_mixer_sound_t *handle = NULL;
audio_mixer_stop_cb_t stop_cb = audio_mixer_play_stop_cb;
bool looped = false;
void *buf = NULL;
if (params->stream_type == AUDIO_STREAM_TYPE_NONE)
return false;
switch (params->slot_selection_type)
{
case AUDIO_MIXER_SLOT_SELECTION_MANUAL:
free_slot = params->slot_selection_idx;
/* If we are using a manually specified
* slot, must free any existing stream
* before assigning the new one */
audio_driver_mixer_stop_stream(free_slot);
audio_driver_mixer_remove_stream(free_slot);
break;
case AUDIO_MIXER_SLOT_SELECTION_AUTOMATIC:
default:
if (!audio_driver_mixer_get_free_stream_slot(
&free_slot, params->stream_type))
return false;
break;
}
if (params->state == AUDIO_STREAM_STATE_NONE)
return false;
buf = malloc(params->bufsize);
if (!buf)
return false;
memcpy(buf, params->buf, params->bufsize);
switch (params->type)
{
case AUDIO_MIXER_TYPE_WAV:
handle = audio_mixer_load_wav(buf, (int32_t)params->bufsize,
p_rarch->audio_driver_resampler_ident,
p_rarch->audio_driver_resampler_quality);
/* WAV is a special case - input buffer is not
* free()'d when sound playback is complete (it is
* converted to a PCM buffer, which is free()'d instead),
* so have to do it here */
free(buf);
buf = NULL;
break;
case AUDIO_MIXER_TYPE_OGG:
handle = audio_mixer_load_ogg(buf, (int32_t)params->bufsize);
break;
case AUDIO_MIXER_TYPE_MOD:
handle = audio_mixer_load_mod(buf, (int32_t)params->bufsize);
break;
case AUDIO_MIXER_TYPE_FLAC:
#ifdef HAVE_DR_FLAC
handle = audio_mixer_load_flac(buf, (int32_t)params->bufsize);
#endif
break;
case AUDIO_MIXER_TYPE_MP3:
#ifdef HAVE_DR_MP3
handle = audio_mixer_load_mp3(buf, (int32_t)params->bufsize);
#endif
break;
case AUDIO_MIXER_TYPE_NONE:
break;
}
if (!handle)
{
free(buf);
return false;
}
switch (params->state)
{
case AUDIO_STREAM_STATE_PLAYING_LOOPED:
looped = true;
voice = audio_mixer_play(handle, looped, params->volume,
p_rarch->audio_driver_resampler_ident,
p_rarch->audio_driver_resampler_quality, stop_cb);
break;
case AUDIO_STREAM_STATE_PLAYING:
voice = audio_mixer_play(handle, looped, params->volume,
p_rarch->audio_driver_resampler_ident,
p_rarch->audio_driver_resampler_quality, stop_cb);
break;
case AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL:
stop_cb = audio_mixer_play_stop_sequential_cb;
voice = audio_mixer_play(handle, looped, params->volume,
p_rarch->audio_driver_resampler_ident,
p_rarch->audio_driver_resampler_quality, stop_cb);
break;
default:
break;
}
p_rarch->audio_mixer_active = true;
p_rarch->audio_mixer_streams[free_slot].name =
!string_is_empty(params->basename) ? strdup(params->basename) : NULL;
p_rarch->audio_mixer_streams[free_slot].buf = buf;
p_rarch->audio_mixer_streams[free_slot].handle = handle;
p_rarch->audio_mixer_streams[free_slot].voice = voice;
p_rarch->audio_mixer_streams[free_slot].stream_type = params->stream_type;
p_rarch->audio_mixer_streams[free_slot].type = params->type;
p_rarch->audio_mixer_streams[free_slot].state = params->state;
p_rarch->audio_mixer_streams[free_slot].volume = params->volume;
p_rarch->audio_mixer_streams[free_slot].stop_cb = stop_cb;
return true;
}
enum audio_mixer_state audio_driver_mixer_get_stream_state(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
if (i >= AUDIO_MIXER_MAX_SYSTEM_STREAMS)
return AUDIO_STREAM_STATE_NONE;
return p_rarch->audio_mixer_streams[i].state;
}
static void audio_driver_mixer_play_stream_internal(
struct rarch_state *p_rarch,
unsigned i, unsigned type)
{
if (i >= AUDIO_MIXER_MAX_SYSTEM_STREAMS)
return;
switch (p_rarch->audio_mixer_streams[i].state)
{
case AUDIO_STREAM_STATE_STOPPED:
p_rarch->audio_mixer_streams[i].voice =
audio_mixer_play(p_rarch->audio_mixer_streams[i].handle,
(type == AUDIO_STREAM_STATE_PLAYING_LOOPED) ? true : false,
1.0f, p_rarch->audio_driver_resampler_ident,
p_rarch->audio_driver_resampler_quality,
p_rarch->audio_mixer_streams[i].stop_cb);
p_rarch->audio_mixer_streams[i].state = (enum audio_mixer_state)type;
break;
case AUDIO_STREAM_STATE_PLAYING:
case AUDIO_STREAM_STATE_PLAYING_LOOPED:
case AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL:
case AUDIO_STREAM_STATE_NONE:
break;
}
}
static void audio_driver_load_menu_bgm_callback(retro_task_t *task,
void *task_data, void *user_data, const char *error)
{
bool contentless = false;
bool is_inited = false;
content_get_status(&contentless, &is_inited);
if (!is_inited)
audio_driver_mixer_play_menu_sound_looped(AUDIO_MIXER_SYSTEM_SLOT_BGM);
}
void audio_driver_load_system_sounds(void)
{
char sounds_path[PATH_MAX_LENGTH];
char sounds_fallback_path[PATH_MAX_LENGTH];
char basename_noext[PATH_MAX_LENGTH];
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
const char *dir_assets = settings->paths.directory_assets;
const bool audio_enable_menu = settings->bools.audio_enable_menu;
const bool audio_enable_menu_ok = audio_enable_menu && settings->bools.audio_enable_menu_ok;
const bool audio_enable_menu_cancel = audio_enable_menu && settings->bools.audio_enable_menu_cancel;
const bool audio_enable_menu_notice = audio_enable_menu && settings->bools.audio_enable_menu_notice;
const bool audio_enable_menu_bgm = audio_enable_menu && settings->bools.audio_enable_menu_bgm;
const bool audio_enable_cheevo_unlock = settings->bools.cheevos_unlock_sound_enable;
const char *path_ok = NULL;
const char *path_cancel = NULL;
const char *path_notice = NULL;
const char *path_bgm = NULL;
const char *path_cheevo_unlock = NULL;
struct string_list *list = NULL;
struct string_list *list_fallback = NULL;
unsigned i = 0;
if (!audio_enable_menu && !audio_enable_cheevo_unlock)
goto end;
sounds_path[0] = sounds_fallback_path[0] =
basename_noext[0] ='\0';
fill_pathname_join(
sounds_fallback_path,
dir_assets,
"sounds",
sizeof(sounds_fallback_path));
fill_pathname_application_special(
sounds_path,
sizeof(sounds_path),
APPLICATION_SPECIAL_DIRECTORY_ASSETS_SOUNDS);
list = dir_list_new(sounds_path, MENU_SOUND_FORMATS, false, false, false, false);
list_fallback = dir_list_new(sounds_fallback_path, MENU_SOUND_FORMATS, false, false, false, false);
if (!list)
{
list = list_fallback;
list_fallback = NULL;
}
if (!list || list->size == 0)
goto end;
if (list_fallback && list_fallback->size > 0)
{
for (i = 0; i < list_fallback->size; i++)
{
if (list->size == 0 || !string_list_find_elem(list, list_fallback->elems[i].data))
{
union string_list_elem_attr attr = {0};
string_list_append(list, list_fallback->elems[i].data, attr);
}
}
}
for (i = 0; i < list->size; i++)
{
const char *path = list->elems[i].data;
const char *ext = path_get_extension(path);
if (audio_driver_mixer_extension_supported(ext))
{
basename_noext[0] = '\0';
fill_pathname_base_noext(basename_noext, path, sizeof(basename_noext));
if (string_is_equal_noncase(basename_noext, "ok"))
path_ok = path;
else if (string_is_equal_noncase(basename_noext, "cancel"))
path_cancel = path;
else if (string_is_equal_noncase(basename_noext, "notice"))
path_notice = path;
else if (string_is_equal_noncase(basename_noext, "bgm"))
path_bgm = path;
else if (string_is_equal_noncase(basename_noext, "unlock"))
path_cheevo_unlock = path;
}
}
if (path_ok && audio_enable_menu_ok)
task_push_audio_mixer_load(path_ok, NULL, NULL, true, AUDIO_MIXER_SLOT_SELECTION_MANUAL, AUDIO_MIXER_SYSTEM_SLOT_OK);
if (path_cancel && audio_enable_menu_cancel)
task_push_audio_mixer_load(path_cancel, NULL, NULL, true, AUDIO_MIXER_SLOT_SELECTION_MANUAL, AUDIO_MIXER_SYSTEM_SLOT_CANCEL);
if (path_notice && audio_enable_menu_notice)
task_push_audio_mixer_load(path_notice, NULL, NULL, true, AUDIO_MIXER_SLOT_SELECTION_MANUAL, AUDIO_MIXER_SYSTEM_SLOT_NOTICE);
if (path_bgm && audio_enable_menu_bgm)
task_push_audio_mixer_load(path_bgm, audio_driver_load_menu_bgm_callback, NULL, true, AUDIO_MIXER_SLOT_SELECTION_MANUAL, AUDIO_MIXER_SYSTEM_SLOT_BGM);
if (path_cheevo_unlock && audio_enable_cheevo_unlock)
task_push_audio_mixer_load(path_cheevo_unlock, NULL, NULL, true, AUDIO_MIXER_SLOT_SELECTION_MANUAL, AUDIO_MIXER_SYSTEM_SLOT_ACHIEVEMENT_UNLOCK);
end:
if (list)
string_list_free(list);
if (list_fallback)
string_list_free(list_fallback);
}
void audio_driver_mixer_play_stream(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_mixer_streams[i].stop_cb = audio_mixer_play_stop_cb;
audio_driver_mixer_play_stream_internal(p_rarch,
i, AUDIO_STREAM_STATE_PLAYING);
}
void audio_driver_mixer_play_menu_sound_looped(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_mixer_streams[i].stop_cb = audio_mixer_menu_stop_cb;
audio_driver_mixer_play_stream_internal(p_rarch,
i, AUDIO_STREAM_STATE_PLAYING_LOOPED);
}
void audio_driver_mixer_play_menu_sound(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_mixer_streams[i].stop_cb = audio_mixer_menu_stop_cb;
audio_driver_mixer_play_stream_internal(p_rarch,
i, AUDIO_STREAM_STATE_PLAYING);
}
void audio_driver_mixer_play_stream_looped(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_mixer_streams[i].stop_cb = audio_mixer_play_stop_cb;
audio_driver_mixer_play_stream_internal(p_rarch,
i, AUDIO_STREAM_STATE_PLAYING_LOOPED);
}
void audio_driver_mixer_play_stream_sequential(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_mixer_streams[i].stop_cb = audio_mixer_play_stop_sequential_cb;
audio_driver_mixer_play_stream_internal(p_rarch,
i, AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL);
}
float audio_driver_mixer_get_stream_volume(unsigned i)
{
struct rarch_state *p_rarch = &rarch_st;
if (i >= AUDIO_MIXER_MAX_SYSTEM_STREAMS)
return 0.0f;
return p_rarch->audio_mixer_streams[i].volume;
}
void audio_driver_mixer_set_stream_volume(unsigned i, float vol)
{
audio_mixer_voice_t *voice = NULL;
struct rarch_state *p_rarch = &rarch_st;
if (i >= AUDIO_MIXER_MAX_SYSTEM_STREAMS)
return;
p_rarch->audio_mixer_streams[i].volume = vol;
voice =
p_rarch->audio_mixer_streams[i].voice;
if (voice)
audio_mixer_voice_set_volume(voice, DB_TO_GAIN(vol));
}
void audio_driver_mixer_stop_stream(unsigned i)
{
bool set_state = false;
struct rarch_state *p_rarch = &rarch_st;
if (i >= AUDIO_MIXER_MAX_SYSTEM_STREAMS)
return;
switch (p_rarch->audio_mixer_streams[i].state)
{
case AUDIO_STREAM_STATE_PLAYING:
case AUDIO_STREAM_STATE_PLAYING_LOOPED:
case AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL:
set_state = true;
break;
case AUDIO_STREAM_STATE_STOPPED:
case AUDIO_STREAM_STATE_NONE:
break;
}
if (set_state)
{
audio_mixer_voice_t *voice = p_rarch->audio_mixer_streams[i].voice;
if (voice)
audio_mixer_stop(voice);
p_rarch->audio_mixer_streams[i].state = AUDIO_STREAM_STATE_STOPPED;
p_rarch->audio_mixer_streams[i].volume = 1.0f;
}
}
void audio_driver_mixer_remove_stream(unsigned i)
{
bool destroy = false;
struct rarch_state *p_rarch = &rarch_st;
if (i >= AUDIO_MIXER_MAX_SYSTEM_STREAMS)
return;
switch (p_rarch->audio_mixer_streams[i].state)
{
case AUDIO_STREAM_STATE_PLAYING:
case AUDIO_STREAM_STATE_PLAYING_LOOPED:
case AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL:
audio_driver_mixer_stop_stream(i);
destroy = true;
break;
case AUDIO_STREAM_STATE_STOPPED:
destroy = true;
break;
case AUDIO_STREAM_STATE_NONE:
break;
}
if (destroy)
{
audio_mixer_sound_t *handle = p_rarch->audio_mixer_streams[i].handle;
if (handle)
audio_mixer_destroy(handle);
if (!string_is_empty(p_rarch->audio_mixer_streams[i].name))
free(p_rarch->audio_mixer_streams[i].name);
p_rarch->audio_mixer_streams[i].state = AUDIO_STREAM_STATE_NONE;
p_rarch->audio_mixer_streams[i].stop_cb = NULL;
p_rarch->audio_mixer_streams[i].volume = 0.0f;
p_rarch->audio_mixer_streams[i].handle = NULL;
p_rarch->audio_mixer_streams[i].voice = NULL;
p_rarch->audio_mixer_streams[i].name = NULL;
}
}
#endif
bool audio_driver_enable_callback(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->audio_callback.callback)
return false;
if (p_rarch->audio_callback.set_state)
p_rarch->audio_callback.set_state(true);
return true;
}
bool audio_driver_disable_callback(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->audio_callback.callback)
return false;
if (p_rarch->audio_callback.set_state)
p_rarch->audio_callback.set_state(false);
return true;
}
bool audio_driver_callback(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
#ifdef HAVE_MENU
bool core_paused = runloop_state.paused || (settings->bools.menu_pause_libretro && p_rarch->menu_driver_alive);
#else
bool core_paused = runloop_state.paused;
#endif
if (!p_rarch->audio_callback.callback)
return false;
if (!core_paused && p_rarch->audio_callback.callback)
p_rarch->audio_callback.callback();
return true;
}
bool audio_driver_has_callback(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->audio_callback.callback)
return true;
return false;
}
#ifdef HAVE_AUDIOMIXER
bool audio_driver_mixer_toggle_mute(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->audio_driver_mixer_mute_enable =
!p_rarch->audio_driver_mixer_mute_enable;
return true;
}
#endif
static INLINE bool audio_driver_alive(struct rarch_state *p_rarch)
{
if ( p_rarch->current_audio
&& p_rarch->current_audio->alive
&& p_rarch->audio_driver_context_audio_data)
return p_rarch->current_audio->alive(p_rarch->audio_driver_context_audio_data);
return false;
}
static bool audio_driver_start(struct rarch_state *p_rarch,
bool is_shutdown)
{
if (!p_rarch->current_audio || !p_rarch->current_audio->start
|| !p_rarch->audio_driver_context_audio_data)
goto error;
if (!p_rarch->current_audio->start(
p_rarch->audio_driver_context_audio_data, is_shutdown))
goto error;
return true;
error:
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_FAILED_TO_START_AUDIO_DRIVER));
p_rarch->audio_driver_active = false;
return false;
}
static bool audio_driver_stop(struct rarch_state *p_rarch)
{
if ( !p_rarch->current_audio
|| !p_rarch->current_audio->stop
|| !p_rarch->audio_driver_context_audio_data
|| !audio_driver_alive(p_rarch)
)
return false;
return p_rarch->current_audio->stop(
p_rarch->audio_driver_context_audio_data);
}
#ifdef HAVE_REWIND
void audio_driver_frame_is_reverse(void)
{
struct rarch_state *p_rarch = &rarch_st;
/* We just rewound. Flush rewind audio buffer. */
if ( p_rarch->recording_data &&
p_rarch->recording_driver &&
p_rarch->recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = p_rarch->audio_driver_rewind_buf +
p_rarch->audio_driver_rewind_ptr;
ffemu_data.frames = (p_rarch->audio_driver_rewind_size -
p_rarch->audio_driver_rewind_ptr) / 2;
p_rarch->recording_driver->push_audio(p_rarch->recording_data, &ffemu_data);
}
if (!(
runloop_state.paused ||
!p_rarch->audio_driver_active ||
!p_rarch->audio_driver_output_samples_buf))
if (!p_rarch->audio_suspended)
audio_driver_flush(
p_rarch,
p_rarch->configuration_settings->floats.slowmotion_ratio,
p_rarch->configuration_settings->bools.audio_fastforward_mute,
p_rarch->audio_driver_rewind_buf +
p_rarch->audio_driver_rewind_ptr,
p_rarch->audio_driver_rewind_size -
p_rarch->audio_driver_rewind_ptr,
runloop_state.slowmotion,
runloop_state.fastmotion);
}
#endif
void audio_set_float(enum audio_action action, float val)
{
struct rarch_state *p_rarch = &rarch_st;
switch (action)
{
case AUDIO_ACTION_VOLUME_GAIN:
p_rarch->audio_driver_volume_gain = DB_TO_GAIN(val);
break;
case AUDIO_ACTION_MIXER_VOLUME_GAIN:
#ifdef HAVE_AUDIOMIXER
p_rarch->audio_driver_mixer_volume_gain = DB_TO_GAIN(val);
#endif
break;
case AUDIO_ACTION_RATE_CONTROL_DELTA:
p_rarch->audio_driver_rate_control_delta = val;
break;
case AUDIO_ACTION_NONE:
default:
break;
}
}
float *audio_get_float_ptr(enum audio_action action)
{
struct rarch_state *p_rarch = &rarch_st;
switch (action)
{
case AUDIO_ACTION_RATE_CONTROL_DELTA:
return &p_rarch->audio_driver_rate_control_delta;
case AUDIO_ACTION_NONE:
default:
break;
}
return NULL;
}
bool *audio_get_bool_ptr(enum audio_action action)
{
struct rarch_state *p_rarch = &rarch_st;
switch (action)
{
case AUDIO_ACTION_MIXER_MUTE_ENABLE:
#ifdef HAVE_AUDIOMIXER
return &p_rarch->audio_driver_mixer_mute_enable;
#else
break;
#endif
case AUDIO_ACTION_MUTE_ENABLE:
return &p_rarch->audio_driver_mute_enable;
case AUDIO_ACTION_NONE:
default:
break;
}
return NULL;
}
/* VIDEO */
const char *video_display_server_get_ident(void)
{
if (!current_display_server)
return FILE_PATH_UNKNOWN;
return current_display_server->ident;
}
void* video_display_server_init(enum rarch_display_type type)
{
struct rarch_state *p_rarch = &rarch_st;
video_display_server_destroy();
switch (type)
{
case RARCH_DISPLAY_WIN32:
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
current_display_server = &dispserv_win32;
#endif
break;
case RARCH_DISPLAY_X11:
#if defined(HAVE_X11)
current_display_server = &dispserv_x11;
#endif
break;
default:
#if defined(ANDROID)
current_display_server = &dispserv_android;
#else
current_display_server = &dispserv_null;
#endif
break;
}
if (current_display_server)
{
if (current_display_server->init)
p_rarch->current_display_server_data = current_display_server->init();
if (!string_is_empty(current_display_server->ident))
{
RARCH_LOG("[Video]: Found display server: %s\n",
current_display_server->ident);
}
}
p_rarch->initial_screen_orientation =
video_display_server_get_screen_orientation();
p_rarch->current_screen_orientation =
p_rarch->initial_screen_orientation;
return p_rarch->current_display_server_data;
}
void video_display_server_destroy(void)
{
struct rarch_state *p_rarch = &rarch_st;
const enum rotation initial_screen_orientation = p_rarch->initial_screen_orientation;
const enum rotation current_screen_orientation = p_rarch->current_screen_orientation;
if (initial_screen_orientation != current_screen_orientation)
video_display_server_set_screen_orientation(initial_screen_orientation);
if (current_display_server)
if (p_rarch->current_display_server_data)
current_display_server->destroy(p_rarch->current_display_server_data);
}
bool video_display_server_set_window_opacity(unsigned opacity)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->set_window_opacity)
return current_display_server->set_window_opacity(
p_rarch->current_display_server_data, opacity);
return false;
}
bool video_display_server_set_window_progress(int progress, bool finished)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->set_window_progress)
return current_display_server->set_window_progress(
p_rarch->current_display_server_data, progress, finished);
return false;
}
bool video_display_server_set_window_decorations(bool on)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->set_window_decorations)
return current_display_server->set_window_decorations(
p_rarch->current_display_server_data, on);
return false;
}
bool video_display_server_set_resolution(unsigned width, unsigned height,
int int_hz, float hz, int center, int monitor_index, int xoffset, int padjust)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->set_resolution)
return current_display_server->set_resolution(
p_rarch->current_display_server_data, width, height, int_hz,
hz, center, monitor_index, xoffset, padjust);
return false;
}
bool video_display_server_has_resolution_list(void)
{
return (current_display_server
&& current_display_server->get_resolution_list);
}
void *video_display_server_get_resolution_list(unsigned *size)
{
struct rarch_state *p_rarch = &rarch_st;
if (video_display_server_has_resolution_list())
return current_display_server->get_resolution_list(
p_rarch->current_display_server_data, size);
return NULL;
}
bool video_display_server_has_refresh_rate(float hz)
{
unsigned i, size = 0;
bool rate_exists = false;
struct video_display_config *video_list = (struct video_display_config*)
video_display_server_get_resolution_list(&size);
if (video_list)
{
struct rarch_state *p_rarch = &rarch_st;
unsigned video_driver_width = p_rarch->video_driver_width;
unsigned video_driver_height = p_rarch->video_driver_height;
for (i = 0; i < size && !rate_exists; i++)
{
if (video_list[i].width == video_driver_width &&
video_list[i].height == video_driver_height &&
video_list[i].refreshrate == floor(hz))
rate_exists = true;
}
free(video_list);
}
return rate_exists;
}
bool video_display_server_set_refresh_rate(float hz)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->set_resolution)
return current_display_server->set_resolution(
p_rarch->current_display_server_data, 0, 0, (int)hz,
hz, 0, 0, 0, 0);
return false;
}
void video_display_server_restore_refresh_rate(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
float refresh_rate_original = p_rarch->video_refresh_rate_original;
float refresh_rate_current = settings->floats.video_refresh_rate;
if (!refresh_rate_original || refresh_rate_current == refresh_rate_original)
return;
video_monitor_set_refresh_rate(refresh_rate_original);
video_display_server_set_refresh_rate(refresh_rate_original);
}
const char *video_display_server_get_output_options(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->get_output_options)
return current_display_server->get_output_options(p_rarch->current_display_server_data);
return NULL;
}
void video_display_server_set_screen_orientation(enum rotation rotation)
{
if (current_display_server && current_display_server->set_screen_orientation)
{
struct rarch_state *p_rarch = &rarch_st;
RARCH_LOG("[Video]: Setting screen orientation to %d.\n", rotation);
p_rarch->current_screen_orientation = rotation;
current_display_server->set_screen_orientation(p_rarch->current_display_server_data, rotation);
}
}
bool video_display_server_can_set_screen_orientation(void)
{
return (current_display_server && current_display_server->set_screen_orientation);
}
enum rotation video_display_server_get_screen_orientation(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (current_display_server && current_display_server->get_screen_orientation)
return current_display_server->get_screen_orientation(p_rarch->current_display_server_data);
return ORIENTATION_NORMAL;
}
bool video_display_server_get_flags(gfx_ctx_flags_t *flags)
{
struct rarch_state *p_rarch = &rarch_st;
if (!flags || !current_display_server || !current_display_server->get_flags)
return false;
flags->flags = current_display_server->get_flags(
p_rarch->current_display_server_data);
return true;
}
bool video_driver_started_fullscreen(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_started_fullscreen;
}
/* Stub functions */
static bool get_metrics_null(void *data, enum display_metric_types type,
float *value) { return false; }
/**
* config_get_video_driver_options:
*
* Get an enumerated list of all video driver names, separated by '|'.
*
* Returns: string listing of all video driver names, separated by '|'.
**/
const char* config_get_video_driver_options(void)
{
return char_list_new_special(STRING_LIST_VIDEO_DRIVERS, NULL);
}
bool video_driver_is_threaded(void)
{
#ifdef HAVE_THREADS
struct rarch_state *p_rarch = &rarch_st;
#endif
return VIDEO_DRIVER_IS_THREADED_INTERNAL();
}
bool *video_driver_get_threaded(void)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(__MACH__) && defined(__APPLE__)
/* TODO/FIXME - force threaded video to disabled on Apple for now
* until NSWindow/UIWindow concurrency issues are taken care of */
p_rarch->video_driver_threaded = false;
#endif
return &p_rarch->video_driver_threaded;
}
void video_driver_set_threaded(bool val)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(__MACH__) && defined(__APPLE__)
/* TODO/FIXME - force threaded video to disabled on Apple for now
* until NSWindow/UIWindow concurrency issues are taken care of */
p_rarch->video_driver_threaded = false;
#else
p_rarch->video_driver_threaded = val;
#endif
}
const char *video_driver_get_ident(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video)
return NULL;
#ifdef HAVE_THREADS
if (VIDEO_DRIVER_IS_THREADED_INTERNAL())
{
const thread_video_t *thr = (const thread_video_t*)p_rarch->video_driver_data;
if (!thr || !thr->driver)
return NULL;
return thr->driver->ident;
}
#endif
return p_rarch->current_video->ident;
}
static void video_context_driver_reset(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video_context.get_metrics)
p_rarch->current_video_context.get_metrics = get_metrics_null;
}
bool video_context_driver_set(const gfx_ctx_driver_t *data)
{
struct rarch_state *p_rarch = &rarch_st;
if (!data)
return false;
p_rarch->current_video_context = *data;
video_context_driver_reset();
return true;
}
static void video_context_driver_destroy_internal(
gfx_ctx_driver_t *ctx_driver)
{
if (!ctx_driver)
return;
ctx_driver->init = NULL;
ctx_driver->bind_api = NULL;
ctx_driver->swap_interval = NULL;
ctx_driver->set_video_mode = NULL;
ctx_driver->get_video_size = NULL;
ctx_driver->get_video_output_size = NULL;
ctx_driver->get_video_output_prev = NULL;
ctx_driver->get_video_output_next = NULL;
ctx_driver->get_metrics = get_metrics_null;
ctx_driver->translate_aspect = NULL;
ctx_driver->update_window_title = NULL;
ctx_driver->check_window = NULL;
ctx_driver->set_resize = NULL;
ctx_driver->suppress_screensaver = NULL;
ctx_driver->swap_buffers = NULL;
ctx_driver->input_driver = NULL;
ctx_driver->get_proc_address = NULL;
ctx_driver->image_buffer_init = NULL;
ctx_driver->image_buffer_write = NULL;
ctx_driver->show_mouse = NULL;
ctx_driver->ident = NULL;
ctx_driver->get_flags = NULL;
ctx_driver->set_flags = NULL;
ctx_driver->bind_hw_render = NULL;
ctx_driver->get_context_data = NULL;
ctx_driver->make_current = NULL;
}
void video_context_driver_destroy(void)
{
struct rarch_state *p_rarch = &rarch_st;
video_context_driver_destroy_internal(&p_rarch->current_video_context);
}
/**
* video_driver_get_current_framebuffer:
*
* Gets pointer to current hardware renderer framebuffer object.
* Used by RETRO_ENVIRONMENT_SET_HW_RENDER.
*
* Returns: pointer to hardware framebuffer object, otherwise 0.
**/
static uintptr_t video_driver_get_current_framebuffer(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->get_current_framebuffer)
return p_rarch->video_driver_poke->get_current_framebuffer(
p_rarch->video_driver_data);
return 0;
}
static retro_proc_address_t video_driver_get_proc_address(const char *sym)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->get_proc_address)
return p_rarch->video_driver_poke->get_proc_address(
p_rarch->video_driver_data, sym);
return NULL;
}
#ifdef HAVE_VIDEO_FILTER
static void video_driver_filter_free(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_state_filter)
rarch_softfilter_free(p_rarch->video_driver_state_filter);
p_rarch->video_driver_state_filter = NULL;
if (p_rarch->video_driver_state_buffer)
{
#ifdef _3DS
linearFree(p_rarch->video_driver_state_buffer);
#else
free(p_rarch->video_driver_state_buffer);
#endif
}
p_rarch->video_driver_state_buffer = NULL;
p_rarch->video_driver_state_scale = 0;
p_rarch->video_driver_state_out_bpp = 0;
p_rarch->video_driver_state_out_rgb32 = false;
}
#endif
#ifdef HAVE_VIDEO_FILTER
static void video_driver_init_filter(enum retro_pixel_format colfmt_int,
settings_t *settings)
{
unsigned pow2_x, pow2_y, maxsize;
void *buf = NULL;
struct rarch_state *p_rarch = &rarch_st;
struct retro_game_geometry *geom = &p_rarch->video_driver_av_info.geometry;
unsigned width = geom->max_width;
unsigned height = geom->max_height;
/* Deprecated format. Gets pre-converted. */
enum retro_pixel_format colfmt =
(colfmt_int == RETRO_PIXEL_FORMAT_0RGB1555) ?
RETRO_PIXEL_FORMAT_RGB565 : colfmt_int;
if (video_driver_is_hw_context())
{
RARCH_WARN("[Video]: Cannot use CPU filters when hardware rendering is used.\n");
return;
}
p_rarch->video_driver_state_filter = rarch_softfilter_new(
settings->paths.path_softfilter_plugin,
RARCH_SOFTFILTER_THREADS_AUTO, colfmt, width, height);
if (!p_rarch->video_driver_state_filter)
{
RARCH_ERR("[Video]: Failed to load filter.\n");
return;
}
rarch_softfilter_get_max_output_size(
p_rarch->video_driver_state_filter,
&width, &height);
pow2_x = next_pow2(width);
pow2_y = next_pow2(height);
maxsize = MAX(pow2_x, pow2_y);
#ifdef _3DS
/* On 3DS, video is disabled if the output resolution
* exceeds 2048x2048. To avoid the user being presented
* with a black screen, we therefore have to check that
* the filter upscaling buffer fits within this limit. */
if (maxsize >= 2048)
{
RARCH_ERR("[Video]: Softfilter initialization failed."
" Upscaling buffer exceeds hardware limitations.\n");
video_driver_filter_free();
return;
}
#endif
p_rarch->video_driver_state_scale = maxsize / RARCH_SCALE_BASE;
p_rarch->video_driver_state_out_rgb32 = rarch_softfilter_get_output_format(
p_rarch->video_driver_state_filter) == RETRO_PIXEL_FORMAT_XRGB8888;
p_rarch->video_driver_state_out_bpp =
p_rarch->video_driver_state_out_rgb32 ?
sizeof(uint32_t) :
sizeof(uint16_t);
/* TODO: Aligned output. */
#ifdef _3DS
buf = linearMemAlign(
width * height * p_rarch->video_driver_state_out_bpp, 0x80);
#else
buf = malloc(
width * height * p_rarch->video_driver_state_out_bpp);
#endif
if (!buf)
{
RARCH_ERR("[Video]: Softfilter initialization failed.\n");
video_driver_filter_free();
return;
}
p_rarch->video_driver_state_buffer = buf;
}
#endif
static void video_driver_init_input(
input_driver_t *tmp,
settings_t *settings,
bool verbosity_enabled)
{
void *new_data = NULL;
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t
*input_driver_st = &p_rarch->input_driver_state;
input_driver_t **input = &input_driver_st->current_driver;
if (*input)
return;
/* Video driver didn't provide an input driver,
* so we use configured one. */
RARCH_LOG("[Video]: Graphics driver did not initialize an input driver."
" Attempting to pick a suitable driver.\n");
if (tmp)
*input = tmp;
else
{
if (!(input_driver_find_driver(
&p_rarch->input_driver_state, settings, "input driver",
verbosity_enabled)))
retroarch_fail(p_rarch, 1, "find_input_driver()");
}
/* This should never really happen as tmp (driver.input) is always
* found before this in find_driver_input(), or we have aborted
* in a similar fashion anyways. */
if ( !input_driver_st->current_driver ||
!(new_data = input_driver_init_wrap(
input_driver_st->current_driver,
settings->arrays.input_joypad_driver)))
{
RARCH_ERR("[Video]: Cannot initialize input driver. Exiting ...\n");
retroarch_fail(p_rarch, 1, "video_driver_init_input()");
return;
}
input_driver_st->current_data = new_data;
}
/**
* video_driver_monitor_compute_fps_statistics:
*
* Computes monitor FPS statistics.
**/
static void video_driver_monitor_compute_fps_statistics(uint64_t
frame_time_count)
{
double avg_fps = 0.0;
double stddev = 0.0;
unsigned samples = 0;
if (frame_time_count <
(2 * MEASURE_FRAME_TIME_SAMPLES_COUNT))
{
RARCH_LOG(
"[Video]: Does not have enough samples for monitor refresh rate"
" estimation. Requires to run for at least %u frames.\n",
2 * MEASURE_FRAME_TIME_SAMPLES_COUNT);
return;
}
if (video_monitor_fps_statistics(&avg_fps, &stddev, &samples))
{
RARCH_LOG("[Video]: Average monitor Hz: %.6f Hz. (%.3f %% frame time"
" deviation, based on %u last samples).\n",
avg_fps, 100.0f * stddev, samples);
}
}
static void video_driver_pixel_converter_free(
video_pixel_scaler_t *scalr)
{
if (!scalr)
return;
if (scalr->scaler)
{
scaler_ctx_gen_reset(scalr->scaler);
free(scalr->scaler);
}
if (scalr->scaler_out)
free(scalr->scaler_out);
scalr->scaler = NULL;
scalr->scaler_out = NULL;
free(scalr);
}
static void video_driver_free_hw_context(struct rarch_state *p_rarch)
{
VIDEO_DRIVER_CONTEXT_LOCK();
if (p_rarch->hw_render.context_destroy)
p_rarch->hw_render.context_destroy();
memset(&p_rarch->hw_render, 0, sizeof(p_rarch->hw_render));
VIDEO_DRIVER_CONTEXT_UNLOCK();
p_rarch->hw_render_context_negotiation = NULL;
}
static void video_driver_free_internal(struct rarch_state *p_rarch)
{
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
#ifdef HAVE_THREADS
bool is_threaded = VIDEO_DRIVER_IS_THREADED_INTERNAL();
#endif
#ifdef HAVE_VIDEO_LAYOUT
video_layout_deinit();
#endif
command_event(CMD_EVENT_OVERLAY_DEINIT, NULL);
if (!video_driver_is_video_cache_context())
video_driver_free_hw_context(p_rarch);
if (!(input_driver_st->current_data == p_rarch->video_driver_data))
{
if (input_driver_st->current_driver)
if (input_driver_st->current_driver->free)
input_driver_st->current_driver->free(input_driver_st->current_data);
if (input_driver_st->primary_joypad)
{
const input_device_driver_t *tmp = input_driver_st->primary_joypad;
input_driver_st->primary_joypad = NULL;
tmp->destroy();
}
#ifdef HAVE_MFI
if (input_driver_st->secondary_joypad)
{
const input_device_driver_t *tmp = input_driver_st->secondary_joypad;
input_driver_st->secondary_joypad = NULL;
tmp->destroy();
}
#endif
p_rarch->keyboard_mapping_blocked = false;
p_rarch->input_driver_state.current_data = NULL;
}
if (p_rarch->video_driver_data
&& p_rarch->current_video
&& p_rarch->current_video->free)
p_rarch->current_video->free(p_rarch->video_driver_data);
if (p_rarch->video_driver_scaler_ptr)
video_driver_pixel_converter_free(p_rarch->video_driver_scaler_ptr);
p_rarch->video_driver_scaler_ptr = NULL;
#ifdef HAVE_VIDEO_FILTER
video_driver_filter_free();
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
dir_free_shader(
(struct rarch_dir_shader_list*)&p_rarch->dir_shader_list,
p_rarch->configuration_settings->bools.video_shader_remember_last_dir);
#endif
#ifdef HAVE_THREADS
if (is_threaded)
return;
#endif
video_driver_monitor_compute_fps_statistics(p_rarch->video_driver_frame_time_count);
}
static video_pixel_scaler_t *video_driver_pixel_converter_init(
const enum retro_pixel_format video_driver_pix_fmt,
struct retro_hw_render_callback *hwr,
unsigned size)
{
void *scalr_out = NULL;
video_pixel_scaler_t *scalr = NULL;
struct scaler_ctx *scalr_ctx = NULL;
/* If pixel format is not 0RGB1555, we don't need to do
* any internal pixel conversion. */
if (video_driver_pix_fmt != RETRO_PIXEL_FORMAT_0RGB1555)
return NULL;
/* No need to perform pixel conversion for HW rendering contexts. */
if (hwr && hwr->context_type != RETRO_HW_CONTEXT_NONE)
return NULL;
RARCH_WARN("[Video]: 0RGB1555 pixel format is deprecated,"
" and will be slower. For 15/16-bit, RGB565"
" format is preferred.\n");
if (!(scalr = (video_pixel_scaler_t*)malloc(sizeof(*scalr))))
goto error;
scalr->scaler = NULL;
scalr->scaler_out = NULL;
if (!(scalr_ctx = (struct scaler_ctx*)calloc(1, sizeof(*scalr_ctx))))
goto error;
scalr->scaler = scalr_ctx;
scalr->scaler->scaler_type = SCALER_TYPE_POINT;
scalr->scaler->in_fmt = SCALER_FMT_0RGB1555;
/* TODO/FIXME: Pick either ARGB8888 or RGB565 depending on driver. */
scalr->scaler->out_fmt = SCALER_FMT_RGB565;
if (!scaler_ctx_gen_filter(scalr_ctx))
goto error;
if (!(scalr_out = calloc(sizeof(uint16_t), size * size)))
goto error;
scalr->scaler_out = scalr_out;
return scalr;
error:
video_driver_pixel_converter_free(scalr);
#ifdef HAVE_VIDEO_FILTER
video_driver_filter_free();
#endif
return NULL;
}
static void video_driver_set_viewport_config(
struct retro_game_geometry *geom,
float video_aspect_ratio,
bool video_aspect_ratio_auto)
{
if (video_aspect_ratio < 0.0f)
{
if (geom->aspect_ratio > 0.0f && video_aspect_ratio_auto)
aspectratio_lut[ASPECT_RATIO_CONFIG].value = geom->aspect_ratio;
else
{
unsigned base_width = geom->base_width;
unsigned base_height = geom->base_height;
/* Get around division by zero errors */
if (base_width == 0)
base_width = 1;
if (base_height == 0)
base_height = 1;
aspectratio_lut[ASPECT_RATIO_CONFIG].value =
(float)base_width / base_height; /* 1:1 PAR. */
}
}
else
aspectratio_lut[ASPECT_RATIO_CONFIG].value = video_aspect_ratio;
}
static void video_driver_set_viewport_square_pixel(struct retro_game_geometry *geom)
{
unsigned len, i, aspect_x, aspect_y;
unsigned highest = 1;
unsigned width = geom->base_width;
unsigned height = geom->base_height;
unsigned int rotation = retroarch_get_rotation();
if (width == 0 || height == 0)
return;
len = MIN(width, height);
for (i = 1; i < len; i++)
{
if ((width % i) == 0 && (height % i) == 0)
highest = i;
}
if (rotation % 2)
{
aspect_x = height / highest;
aspect_y = width / highest;
}
else
{
aspect_x = width / highest;
aspect_y = height / highest;
}
snprintf(aspectratio_lut[ASPECT_RATIO_SQUARE].name,
sizeof(aspectratio_lut[ASPECT_RATIO_SQUARE].name),
"1:1 PAR (%u:%u DAR)", aspect_x, aspect_y);
aspectratio_lut[ASPECT_RATIO_SQUARE].value = (float)aspect_x / aspect_y;
}
static bool video_driver_init_internal(
struct rarch_state *p_rarch,
settings_t *settings,
bool *video_is_threaded,
bool verbosity_enabled
)
{
video_info_t video;
unsigned max_dim, scale, width, height;
video_viewport_t *custom_vp = NULL;
input_driver_t *tmp = NULL;
static uint16_t dummy_pixels[32] = {0};
struct retro_game_geometry *geom = &p_rarch->video_driver_av_info.geometry;
const enum retro_pixel_format
video_driver_pix_fmt = p_rarch->video_driver_pix_fmt;
#ifdef HAVE_VIDEO_FILTER
const char *path_softfilter_plugin = settings->paths.path_softfilter_plugin;
if (!string_is_empty(path_softfilter_plugin))
video_driver_init_filter(video_driver_pix_fmt, settings);
#endif
max_dim = MAX(geom->max_width, geom->max_height);
scale = next_pow2(max_dim) / RARCH_SCALE_BASE;
scale = MAX(scale, 1);
#ifdef HAVE_VIDEO_FILTER
if (p_rarch->video_driver_state_filter)
scale = p_rarch->video_driver_state_scale;
#endif
/* Update core-dependent aspect ratio values. */
video_driver_set_viewport_square_pixel(geom);
video_driver_set_viewport_core();
video_driver_set_viewport_config(geom,
settings->floats.video_aspect_ratio,
settings->bools.video_aspect_ratio_auto);
/* Update CUSTOM viewport. */
custom_vp = &settings->video_viewport_custom;
if (settings->uints.video_aspect_ratio_idx == ASPECT_RATIO_CUSTOM)
{
float default_aspect = aspectratio_lut[ASPECT_RATIO_CORE].value;
aspectratio_lut[ASPECT_RATIO_CUSTOM].value =
(custom_vp->width && custom_vp->height) ?
(float)custom_vp->width / custom_vp->height : default_aspect;
}
{
/* Guard against aspect ratio index possibly being out of bounds */
unsigned new_aspect_idx = settings->uints.video_aspect_ratio_idx;
if (new_aspect_idx > ASPECT_RATIO_END)
new_aspect_idx = settings->uints.video_aspect_ratio_idx = 0;
video_driver_set_aspect_ratio_value(
aspectratio_lut[new_aspect_idx].value);
}
if (settings->bools.video_fullscreen || p_rarch->rarch_force_fullscreen)
{
width = settings->uints.video_fullscreen_x;
height = settings->uints.video_fullscreen_y;
}
else
{
#ifdef __WINRT__
if (is_running_on_xbox())
{
width = settings->uints.video_fullscreen_x != 0 ? settings->uints.video_fullscreen_x : 3840;
height = settings->uints.video_fullscreen_y != 0 ? settings->uints.video_fullscreen_y : 2160;
}
else
#endif
{
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
bool window_custom_size_enable = settings->bools.video_window_save_positions;
#else
bool window_custom_size_enable = settings->bools.video_window_custom_size_enable;
#endif
/* TODO: remove when the new window resizing core is hooked */
if (window_custom_size_enable &&
settings->uints.window_position_width &&
settings->uints.window_position_height)
{
width = settings->uints.window_position_width;
height = settings->uints.window_position_height;
}
else
{
float video_scale = settings->floats.video_scale;
/* Determine maximum allowed window dimensions
* NOTE: We cannot read the actual display
* metrics here, because the context driver
* has not yet been initialised... */
/* > Try explicitly configured values */
unsigned max_win_width = settings->uints.window_auto_width_max;
unsigned max_win_height = settings->uints.window_auto_height_max;
/* > Handle invalid settings */
if ((max_win_width == 0) || (max_win_height == 0))
{
/* > Try configured fullscreen width/height */
max_win_width = settings->uints.video_fullscreen_x;
max_win_height = settings->uints.video_fullscreen_y;
if ((max_win_width == 0) || (max_win_height == 0))
{
/* Maximum window width/size *must* be non-zero;
* if all else fails, used defined default
* maximum window size */
max_win_width = DEFAULT_WINDOW_AUTO_WIDTH_MAX;
max_win_height = DEFAULT_WINDOW_AUTO_HEIGHT_MAX;
}
}
/* Determine nominal window size based on
* core geometry */
if (settings->bools.video_force_aspect)
{
/* Do rounding here to simplify integer
* scale correctness. */
unsigned base_width = roundf(geom->base_height *
p_rarch->video_driver_aspect_ratio);
width = roundf(base_width * video_scale);
}
else
width = roundf(geom->base_width * video_scale);
height = roundf(geom->base_height * video_scale);
/* Cap window size to maximum allowed values */
if ((width > max_win_width) || (height > max_win_height))
{
unsigned geom_width = (width > 0) ? width : 1;
unsigned geom_height = (height > 0) ? height : 1;
float geom_aspect = (float)geom_width / (float)geom_height;
float max_win_aspect = (float)max_win_width / (float)max_win_height;
if (geom_aspect > max_win_aspect)
{
width = max_win_width;
height = geom_height * max_win_width / geom_width;
/* Account for any possible rounding errors... */
height = (height < 1) ? 1 : height;
height = (height > max_win_height) ? max_win_height : height;
}
else
{
height = max_win_height;
width = geom_width * max_win_height / geom_height;
/* Account for any possible rounding errors... */
width = (width < 1) ? 1 : width;
width = (width > max_win_width) ? max_win_width : width;
}
}
}
}
}
if (width && height)
RARCH_LOG("[Video]: Video @ %ux%u\n", width, height);
else
RARCH_LOG("[Video]: Video @ fullscreen\n");
p_rarch->video_driver_display_type = RARCH_DISPLAY_NONE;
p_rarch->video_driver_display = 0;
p_rarch->video_driver_display_userdata = 0;
p_rarch->video_driver_window = 0;
p_rarch->video_driver_scaler_ptr = video_driver_pixel_converter_init(
p_rarch->video_driver_pix_fmt,
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch),
RARCH_SCALE_BASE * scale);
video.width = width;
video.height = height;
video.fullscreen = settings->bools.video_fullscreen ||
p_rarch->rarch_force_fullscreen;
video.vsync = settings->bools.video_vsync &&
!runloop_state.force_nonblock;
video.force_aspect = settings->bools.video_force_aspect;
video.font_enable = settings->bools.video_font_enable;
video.swap_interval = settings->uints.video_swap_interval;
video.adaptive_vsync = settings->bools.video_adaptive_vsync;
#ifdef GEKKO
video.viwidth = settings->uints.video_viwidth;
video.vfilter = settings->bools.video_vfilter;
#endif
video.smooth = settings->bools.video_smooth;
video.ctx_scaling = settings->bools.video_ctx_scaling;
video.input_scale = scale;
video.font_size = settings->floats.video_font_size;
video.path_font = settings->paths.path_font;
#ifdef HAVE_VIDEO_FILTER
video.rgb32 =
p_rarch->video_driver_state_filter ?
p_rarch->video_driver_state_out_rgb32 :
(video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888);
#else
video.rgb32 =
(video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888);
#endif
video.parent = 0;
p_rarch->video_started_fullscreen = video.fullscreen;
/* Reset video frame count */
p_rarch->video_driver_frame_count = 0;
tmp = p_rarch->input_driver_state.current_driver;
/* Need to grab the "real" video driver interface on a reinit. */
video_driver_find_driver(p_rarch, settings,
"video driver", verbosity_enabled);
#ifdef HAVE_THREADS
video.is_threaded = VIDEO_DRIVER_IS_THREADED_INTERNAL();
*video_is_threaded = video.is_threaded;
if (video.is_threaded)
{
bool ret;
/* Can't do hardware rendering with threaded driver currently. */
RARCH_LOG("[Video]: Starting threaded video driver ...\n");
ret = video_init_thread((const video_driver_t**)&p_rarch->current_video,
&p_rarch->video_driver_data,
&p_rarch->input_driver_state.current_driver,
(void**)&p_rarch->input_driver_state.current_data,
p_rarch->current_video,
video);
if (!ret)
{
RARCH_ERR("[Video]: Cannot open threaded video driver ... Exiting ...\n");
goto error;
}
}
else
#endif
p_rarch->video_driver_data = p_rarch->current_video->init(
&video,
&p_rarch->input_driver_state.current_driver,
(void**)&p_rarch->input_driver_state.current_data);
if (!p_rarch->video_driver_data)
{
RARCH_ERR("[Video]: Cannot open video driver ... Exiting ...\n");
goto error;
}
p_rarch->video_driver_poke = NULL;
if (p_rarch->current_video->poke_interface)
p_rarch->current_video->poke_interface(
p_rarch->video_driver_data, &p_rarch->video_driver_poke);
if (p_rarch->current_video->viewport_info &&
(!custom_vp->width ||
!custom_vp->height))
{
/* Force custom viewport to have sane parameters. */
custom_vp->width = width;
custom_vp->height = height;
video_driver_get_viewport_info(custom_vp);
}
video_driver_set_rotation(retroarch_get_rotation() % 4);
p_rarch->current_video->suppress_screensaver(p_rarch->video_driver_data,
settings->bools.ui_suspend_screensaver_enable);
video_driver_init_input(tmp, settings, verbosity_enabled);
#ifdef HAVE_OVERLAY
retroarch_overlay_deinit(p_rarch);
retroarch_overlay_init(p_rarch);
#endif
#ifdef HAVE_VIDEO_LAYOUT
if (settings->bools.video_layout_enable)
{
video_layout_init(p_rarch->video_driver_data,
video_driver_layout_render_interface());
video_layout_load(settings->paths.path_video_layout);
video_layout_view_select(settings->uints.video_layout_selected_view);
}
#endif
if (!p_rarch->current_core.game_loaded)
video_driver_cached_frame_set(&dummy_pixels, 4, 4, 8);
#if defined(PSP)
video_driver_set_texture_frame(&dummy_pixels, false, 1, 1, 1.0f);
#endif
video_context_driver_reset();
video_display_server_init(p_rarch->video_driver_display_type);
if ((enum rotation)settings->uints.screen_orientation != ORIENTATION_NORMAL)
video_display_server_set_screen_orientation((enum rotation)settings->uints.screen_orientation);
/* Ensure that we preserve the 'grab mouse'
* state if it was enabled prior to driver
* (re-)initialisation */
if (p_rarch->input_driver_grab_mouse_state)
{
video_driver_hide_mouse();
input_driver_grab_mouse(p_rarch);
}
else if (video.fullscreen)
{
video_driver_hide_mouse();
if (!settings->bools.video_windowed_fullscreen)
input_driver_grab_mouse(p_rarch);
}
return true;
error:
retroarch_fail(p_rarch, 1, "init_video()");
return false;
}
void video_driver_set_viewport(unsigned width, unsigned height,
bool force_fullscreen, bool allow_rotate)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->current_video && p_rarch->current_video->set_viewport)
p_rarch->current_video->set_viewport(
p_rarch->video_driver_data, width, height,
force_fullscreen, allow_rotate);
}
bool video_driver_set_rotation(unsigned rotation)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video || !p_rarch->current_video->set_rotation)
return false;
p_rarch->current_video->set_rotation(p_rarch->video_driver_data, rotation);
return true;
}
bool video_driver_set_video_mode(unsigned width,
unsigned height, bool fullscreen)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke &&
p_rarch->video_driver_poke->set_video_mode)
{
p_rarch->video_driver_poke->set_video_mode(p_rarch->video_driver_data,
width, height, fullscreen);
return true;
}
return false;
}
bool video_driver_get_video_output_size(unsigned *width, unsigned *height)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->video_driver_poke || !p_rarch->video_driver_poke->get_video_output_size)
return false;
p_rarch->video_driver_poke->get_video_output_size(p_rarch->video_driver_data,
width, height);
return true;
}
/* Draw text on top of the screen */
void gfx_display_draw_text(
const font_data_t *font, const char *text,
float x, float y, int width, int height,
uint32_t color, enum text_alignment text_align,
float scale, bool shadows_enable, float shadow_offset,
bool draw_outside)
{
struct font_params params;
struct rarch_state *p_rarch = &rarch_st;
if ((color & 0x000000FF) == 0)
return;
/* Don't draw outside of the screen */
if (!draw_outside &&
((x < -64 || x > width + 64)
|| (y < -64 || y > height + 64))
)
return;
params.x = x / width;
params.y = 1.0f - y / height;
params.scale = scale;
params.drop_mod = 0.0f;
params.drop_x = 0.0f;
params.drop_y = 0.0f;
params.color = color;
params.full_screen = true;
params.text_align = text_align;
if (shadows_enable)
{
params.drop_x = shadow_offset;
params.drop_y = -shadow_offset;
params.drop_alpha = 0.35f;
}
if (p_rarch->video_driver_poke && p_rarch->video_driver_poke->set_osd_msg)
p_rarch->video_driver_poke->set_osd_msg(p_rarch->video_driver_data,
text, &params, (void*)font);
}
void video_driver_set_texture_enable(bool enable, bool fullscreen)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_poke && p_rarch->video_driver_poke->set_texture_enable)
p_rarch->video_driver_poke->set_texture_enable(p_rarch->video_driver_data,
enable, fullscreen);
}
void video_driver_set_texture_frame(const void *frame, bool rgb32,
unsigned width, unsigned height, float alpha)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_poke &&
p_rarch->video_driver_poke->set_texture_frame)
p_rarch->video_driver_poke->set_texture_frame(p_rarch->video_driver_data,
frame, rgb32, width, height, alpha);
}
#ifdef HAVE_OVERLAY
static bool video_driver_overlay_interface(
const video_overlay_interface_t **iface)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video || !p_rarch->current_video->overlay_interface)
return false;
p_rarch->current_video->overlay_interface(p_rarch->video_driver_data, iface);
return true;
}
#endif
#ifdef HAVE_VIDEO_LAYOUT
const video_layout_render_interface_t *video_driver_layout_render_interface(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( !p_rarch->current_video ||
!p_rarch->current_video->video_layout_render_interface)
return NULL;
return p_rarch->current_video->video_layout_render_interface(
p_rarch->video_driver_data);
}
#endif
void *video_driver_read_frame_raw(unsigned *width,
unsigned *height, size_t *pitch)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->current_video
&& p_rarch->current_video->read_frame_raw)
return p_rarch->current_video->read_frame_raw(
p_rarch->video_driver_data, width,
height, pitch);
return NULL;
}
void video_driver_set_filtering(unsigned index,
bool smooth, bool ctx_scaling)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->set_filtering)
p_rarch->video_driver_poke->set_filtering(
p_rarch->video_driver_data,
index, smooth, ctx_scaling);
}
void video_driver_set_hdr_max_nits(float max_nits)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->set_hdr_max_nits)
p_rarch->video_driver_poke->set_hdr_max_nits(
p_rarch->video_driver_data,
max_nits);
}
void video_driver_set_hdr_paper_white_nits(float paper_white_nits)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->set_hdr_paper_white_nits)
p_rarch->video_driver_poke->set_hdr_paper_white_nits(
p_rarch->video_driver_data,
paper_white_nits);
}
void video_driver_set_hdr_contrast(float contrast)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->set_hdr_contrast)
p_rarch->video_driver_poke->set_hdr_contrast(
p_rarch->video_driver_data,
VIDEO_HDR_MAX_CONTRAST - contrast);
}
void video_driver_set_hdr_expand_gamut(bool expand_gamut)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke
&& p_rarch->video_driver_poke->set_hdr_expand_gamut)
p_rarch->video_driver_poke->set_hdr_expand_gamut(
p_rarch->video_driver_data,
expand_gamut);
}
/* Use this value as a replacement for anywhere
* where a pure white colour value is used in the UI.
*
* When HDR is turned on 1,1,1,1 should never really
* be used as this is peak brightness and could cause
* damage to displays over long periods of time
* and be quite hard to look at on really bright displays.
*
* Use paper white instead which is always defined as
* 0.5, 0.5, 0.5, 1.0 or in other words is the top of
* the old SDR (Standard Dynamic Range) range
*/
unsigned video_driver_get_hdr_paper_white(void)
{
/* 0.5, 0.5, 0.5, 1 */
if ( video_driver_supports_hdr()
&& config_get_ptr()->bools.video_hdr_enable)
return 0x7f7f7fff;
return 0xffffffff;
}
/* Same as above but returns the white value in floats */
float *video_driver_get_hdr_paper_white_float(void)
{
static float paper_white[4] = { 0.5f, 0.5f, 0.5f, 1.0f};
static float sdr_white [4] = { 1.0f, 1.0f, 1.0f, 1.0f};
if( video_driver_supports_hdr()
&& config_get_ptr()->bools.video_hdr_enable)
return paper_white;
return sdr_white;
}
/* This is useful to create a HDR (High Dynamic Range) white
* based off of some passed in nit level - say you want a
* slightly brighter than paper white value for some parts
* of the UI
*/
float video_driver_get_hdr_luminance(float nits)
{
settings_t *settings = config_get_ptr();
if(video_driver_supports_hdr() && settings->bools.video_hdr_enable)
{
float luminance = nits /
settings->floats.video_hdr_paper_white_nits;
return luminance / (1.0f + luminance);
}
return nits;
}
/* Get reinhard tone mapped colour value for UI elements
* when using HDR and its inverse tonemapper - normally don't use
* but useful if you want a specific colour to look the same
* after inverse tonemapping has been applied */
unsigned video_driver_get_hdr_color(unsigned color)
{
if( video_driver_supports_hdr()
&& config_get_ptr()->bools.video_hdr_enable)
{
float luminance;
float rgb[3];
float yxy[3];
rgb[0] = (float)((color >> 24) & 0xFF) / 255.0f;
rgb[1] = (float)((color >> 16) & 0xFF) / 255.0f;
rgb[2] = (float)((color >> 8 ) & 0xFF) / 255.0f;
convert_rgb_to_yxy(rgb, yxy);
/* TODO: We should probably scale this by average luminance */
luminance = yxy[0];
yxy[0] = luminance / (1.0f + luminance);
convert_yxy_to_rgb(rgb, yxy);
return ( (unsigned)(saturate_value(rgb[0]) * 255.0f) << 24)
| ((unsigned)(saturate_value(rgb[1]) * 255.0f) << 16)
| ((unsigned)(saturate_value(rgb[2]) * 255.0f) << 8)
| (color & 0xFF);
}
return color;
}
void video_driver_cached_frame_set(const void *data, unsigned width,
unsigned height, size_t pitch)
{
struct rarch_state *p_rarch = &rarch_st;
if (data)
p_rarch->frame_cache_data = data;
p_rarch->frame_cache_width = width;
p_rarch->frame_cache_height = height;
p_rarch->frame_cache_pitch = pitch;
}
void video_driver_cached_frame_get(const void **data, unsigned *width,
unsigned *height, size_t *pitch)
{
struct rarch_state *p_rarch = &rarch_st;
if (data)
*data = p_rarch->frame_cache_data;
if (width)
*width = p_rarch->frame_cache_width;
if (height)
*height = p_rarch->frame_cache_height;
if (pitch)
*pitch = p_rarch->frame_cache_pitch;
}
void video_driver_get_size(unsigned *width, unsigned *height)
{
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_THREADS
bool is_threaded = VIDEO_DRIVER_IS_THREADED_INTERNAL();
VIDEO_DRIVER_THREADED_LOCK(is_threaded);
#endif
if (width)
*width = p_rarch->video_driver_width;
if (height)
*height = p_rarch->video_driver_height;
#ifdef HAVE_THREADS
VIDEO_DRIVER_THREADED_UNLOCK(is_threaded);
#endif
}
void video_driver_set_size(unsigned width, unsigned height)
{
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_THREADS
bool is_threaded = VIDEO_DRIVER_IS_THREADED_INTERNAL();
VIDEO_DRIVER_THREADED_LOCK(is_threaded);
#endif
p_rarch->video_driver_width = width;
p_rarch->video_driver_height = height;
#ifdef HAVE_THREADS
VIDEO_DRIVER_THREADED_UNLOCK(is_threaded);
#endif
}
/**
* video_monitor_set_refresh_rate:
* @hz : New refresh rate for monitor.
*
* Sets monitor refresh rate to new value.
**/
void video_monitor_set_refresh_rate(float hz)
{
char msg[128];
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
snprintf(msg, sizeof(msg),
"Setting refresh rate to: %.3f Hz.", hz);
if (settings->bools.notification_show_refresh_rate)
runloop_msg_queue_push(msg, 1, 180, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_LOG("[Video]: %s\n", msg);
configuration_set_float(settings,
settings->floats.video_refresh_rate,
hz);
}
/**
* video_monitor_fps_statistics
* @refresh_rate : Monitor refresh rate.
* @deviation : Deviation from measured refresh rate.
* @sample_points : Amount of sampled points.
*
* Gets the monitor FPS statistics based on the current
* runtime.
*
* Returns: true (1) on success.
* false (0) if:
* a) threaded video mode is enabled
* b) less than 2 frame time samples.
* c) FPS monitor enable is off.
**/
bool video_monitor_fps_statistics(double *refresh_rate,
double *deviation, unsigned *sample_points)
{
unsigned i;
retro_time_t accum = 0;
retro_time_t avg = 0;
retro_time_t accum_var = 0;
unsigned samples = 0;
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_THREADS
if (VIDEO_DRIVER_IS_THREADED_INTERNAL())
return false;
#endif
samples = MIN(MEASURE_FRAME_TIME_SAMPLES_COUNT,
(unsigned)p_rarch->video_driver_frame_time_count);
if (samples < 2)
return false;
/* Measure statistics on frame time (microsecs), *not* FPS. */
for (i = 0; i < samples; i++)
{
accum += p_rarch->video_driver_frame_time_samples[i];
#if 0
RARCH_LOG("[Video]: Interval #%u: %d usec / frame.\n",
i, (int)frame_time_samples[i]);
#endif
}
avg = accum / samples;
/* Drop first measurement. It is likely to be bad. */
for (i = 0; i < samples; i++)
{
retro_time_t diff = p_rarch->video_driver_frame_time_samples[i] - avg;
accum_var += diff * diff;
}
*deviation = sqrt((double)accum_var / (samples - 1)) / avg;
if (refresh_rate)
*refresh_rate = 1000000.0 / avg;
if (sample_points)
*sample_points = samples;
return true;
}
float video_driver_get_aspect_ratio(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_aspect_ratio;
}
void video_driver_set_aspect_ratio_value(float value)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_aspect_ratio = value;
}
enum retro_pixel_format video_driver_get_pixel_format(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_pix_fmt;
}
/**
* video_driver_cached_frame:
*
* Renders the current video frame.
**/
void video_driver_cached_frame(void)
{
struct rarch_state *p_rarch = &rarch_st;
void *recording = p_rarch->recording_data;
struct retro_callbacks *cbs = &p_rarch->retro_ctx;
/* Cannot allow recording when pushing duped frames. */
p_rarch->recording_data = NULL;
if (p_rarch->current_core.inited)
cbs->frame_cb(
(p_rarch->frame_cache_data != RETRO_HW_FRAME_BUFFER_VALID)
? p_rarch->frame_cache_data : NULL,
p_rarch->frame_cache_width,
p_rarch->frame_cache_height,
p_rarch->frame_cache_pitch);
p_rarch->recording_data = recording;
}
static bool video_driver_monitor_adjust_system_rates(
float timing_skew_hz,
float video_refresh_rate,
bool vrr_runloop_enable,
float audio_max_timing_skew,
double input_fps)
{
if (!vrr_runloop_enable)
{
float timing_skew = fabs(
1.0f - input_fps / timing_skew_hz);
/* We don't want to adjust pitch too much. If we have extreme cases,
* just don't readjust at all. */
if (timing_skew <= audio_max_timing_skew)
return true;
RARCH_LOG("[Video]: Timings deviate too much. Will not adjust."
" (Display = %.2f Hz, Game = %.2f Hz)\n",
video_refresh_rate,
(float)input_fps);
}
return input_fps <= timing_skew_hz;
}
static void video_driver_lock_new(struct rarch_state *p_rarch)
{
VIDEO_DRIVER_LOCK_FREE();
#ifdef HAVE_THREADS
if (!p_rarch->display_lock)
p_rarch->display_lock = slock_new();
retro_assert(p_rarch->display_lock);
if (!p_rarch->context_lock)
p_rarch->context_lock = slock_new();
retro_assert(p_rarch->context_lock);
#endif
}
void video_driver_set_cached_frame_ptr(const void *data)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->frame_cache_data = data;
}
void video_driver_set_stub_frame(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->frame_bak = p_rarch->current_video->frame;
p_rarch->current_video->frame = video_null.frame;
}
void video_driver_unset_stub_frame(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->frame_bak)
p_rarch->current_video->frame = p_rarch->frame_bak;
p_rarch->frame_bak = NULL;
}
bool video_driver_supports_viewport_read(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->current_video->read_viewport
&& p_rarch->current_video->viewport_info;
}
bool video_driver_prefer_viewport_read(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
#ifdef HAVE_SCREENSHOTS
bool video_gpu_screenshot = settings->bools.video_gpu_screenshot;
if (video_gpu_screenshot)
return true;
#endif
return (video_driver_is_hw_context() &&
!p_rarch->current_video->read_frame_raw);
}
bool video_driver_supports_read_frame_raw(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->current_video->read_frame_raw)
return true;
return false;
}
void video_driver_set_viewport_core(void)
{
struct rarch_state *p_rarch = &rarch_st;
struct retro_game_geometry *geom = &p_rarch->video_driver_av_info.geometry;
if (!geom || geom->base_width <= 0.0f || geom->base_height <= 0.0f)
return;
/* Fallback to 1:1 pixel ratio if none provided */
if (geom->aspect_ratio > 0.0f)
aspectratio_lut[ASPECT_RATIO_CORE].value = geom->aspect_ratio;
else
aspectratio_lut[ASPECT_RATIO_CORE].value =
(float)geom->base_width / geom->base_height;
}
void video_driver_set_viewport_full(void)
{
unsigned width = 0;
unsigned height = 0;
video_driver_get_size(&width, &height);
if (width == 0 || height == 0)
return;
aspectratio_lut[ASPECT_RATIO_FULL].value = (float)width / (float)height;
}
void video_driver_reset_custom_viewport(void *settings_data)
{
settings_t *settings = (settings_t*)settings_data;
struct video_viewport *custom_vp = &settings->video_viewport_custom;
custom_vp->width = 0;
custom_vp->height = 0;
custom_vp->x = 0;
custom_vp->y = 0;
}
void video_driver_set_rgba(void)
{
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_LOCK();
p_rarch->video_driver_use_rgba = true;
VIDEO_DRIVER_UNLOCK();
}
void video_driver_unset_rgba(void)
{
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_LOCK();
p_rarch->video_driver_use_rgba = false;
VIDEO_DRIVER_UNLOCK();
}
bool video_driver_supports_rgba(void)
{
bool tmp;
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_LOCK();
tmp = p_rarch->video_driver_use_rgba;
VIDEO_DRIVER_UNLOCK();
return tmp;
}
void video_driver_set_hdr_support(void)
{
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_LOCK();
p_rarch->video_driver_hdr_support = true;
VIDEO_DRIVER_UNLOCK();
}
void video_driver_unset_hdr_support(void)
{
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_LOCK();
p_rarch->video_driver_hdr_support = false;
VIDEO_DRIVER_UNLOCK();
}
bool video_driver_supports_hdr(void)
{
bool tmp;
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_LOCK();
tmp = p_rarch->video_driver_hdr_support;
VIDEO_DRIVER_UNLOCK();
return tmp;
}
bool video_driver_get_next_video_out(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( !p_rarch->video_driver_poke
|| !p_rarch->video_driver_poke->get_video_output_next
)
return false;
p_rarch->video_driver_poke->get_video_output_next(
p_rarch->video_driver_data);
return true;
}
bool video_driver_get_prev_video_out(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (
!p_rarch->video_driver_poke
|| !p_rarch->video_driver_poke->get_video_output_prev
)
return false;
p_rarch->video_driver_poke->get_video_output_prev(
p_rarch->video_driver_data);
return true;
}
void video_driver_monitor_reset(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_frame_time_count = 0;
}
void video_driver_set_aspect_ratio(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
unsigned aspect_ratio_idx = settings->uints.video_aspect_ratio_idx;
switch (aspect_ratio_idx)
{
case ASPECT_RATIO_SQUARE:
video_driver_set_viewport_square_pixel(&p_rarch->video_driver_av_info.geometry);
break;
case ASPECT_RATIO_CORE:
video_driver_set_viewport_core();
break;
case ASPECT_RATIO_CONFIG:
video_driver_set_viewport_config(
&p_rarch->video_driver_av_info.geometry,
settings->floats.video_aspect_ratio,
settings->bools.video_aspect_ratio_auto);
break;
case ASPECT_RATIO_FULL:
video_driver_set_viewport_full();
break;
default:
break;
}
video_driver_set_aspect_ratio_value(
aspectratio_lut[aspect_ratio_idx].value);
if ( p_rarch->video_driver_poke &&
p_rarch->video_driver_poke->set_aspect_ratio)
p_rarch->video_driver_poke->set_aspect_ratio(
p_rarch->video_driver_data, aspect_ratio_idx);
}
void video_driver_update_viewport(
struct video_viewport* vp, bool force_full, bool keep_aspect)
{
struct rarch_state *p_rarch = &rarch_st;
float device_aspect = (float)vp->full_width / vp->full_height;
settings_t *settings = p_rarch->configuration_settings;
bool video_scale_integer = settings->bools.video_scale_integer;
unsigned video_aspect_ratio_idx = settings->uints.video_aspect_ratio_idx;
float video_driver_aspect_ratio = p_rarch->video_driver_aspect_ratio;
vp->x = 0;
vp->y = 0;
vp->width = vp->full_width;
vp->height = vp->full_height;
if (video_scale_integer && !force_full)
video_viewport_get_scaled_integer(
vp,
vp->full_width,
vp->full_height,
video_driver_aspect_ratio, keep_aspect);
else if (keep_aspect && !force_full)
{
float desired_aspect = video_driver_aspect_ratio;
#if defined(HAVE_MENU)
if (video_aspect_ratio_idx == ASPECT_RATIO_CUSTOM)
{
const struct video_viewport *custom = &settings->video_viewport_custom;
vp->x = custom->x;
vp->y = custom->y;
vp->width = custom->width;
vp->height = custom->height;
}
else
#endif
{
float delta;
if (fabsf(device_aspect - desired_aspect) < 0.0001f)
{
/* If the aspect ratios of screen and desired aspect
* ratio are sufficiently equal (floating point stuff),
* assume they are actually equal.
*/
}
else if (device_aspect > desired_aspect)
{
delta = (desired_aspect / device_aspect - 1.0f)
/ 2.0f + 0.5f;
vp->x = (int)roundf(vp->full_width * (0.5f - delta));
vp->width = (unsigned)roundf(2.0f * vp->full_width * delta);
vp->y = 0;
vp->height = vp->full_height;
}
else
{
vp->x = 0;
vp->width = vp->full_width;
delta = (device_aspect / desired_aspect - 1.0f)
/ 2.0f + 0.5f;
vp->y = (int)roundf(vp->full_height * (0.5f - delta));
vp->height = (unsigned)roundf(2.0f * vp->full_height * delta);
}
}
}
#if defined(RARCH_MOBILE)
/* In portrait mode, we want viewport to gravitate to top of screen. */
if (device_aspect < 1.0f)
vp->y = 0;
#endif
}
void video_driver_show_mouse(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_poke && p_rarch->video_driver_poke->show_mouse)
p_rarch->video_driver_poke->show_mouse(p_rarch->video_driver_data, true);
}
void video_driver_hide_mouse(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_poke && p_rarch->video_driver_poke->show_mouse)
p_rarch->video_driver_poke->show_mouse(p_rarch->video_driver_data, false);
}
static void video_driver_restore_cached(struct rarch_state *p_rarch,
settings_t *settings)
{
if (p_rarch->cached_video_driver[0])
{
configuration_set_string(settings,
settings->arrays.video_driver, p_rarch->cached_video_driver);
p_rarch->cached_video_driver[0] = 0;
RARCH_LOG("[Video]: Restored video driver to \"%s\".\n",
settings->arrays.video_driver);
}
}
static bool video_driver_find_driver(struct rarch_state *p_rarch,
settings_t *settings,
const char *prefix, bool verbosity_enabled)
{
int i;
if (video_driver_is_hw_context())
{
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
int rdr_major = hwr->version_major;
int rdr_minor = hwr->version_minor;
const char *rdr_context_name = hw_render_context_name(hwr->context_type, rdr_major, rdr_minor);
enum retro_hw_context_type rdr_type = hw_render_context_type(rdr_context_name);
p_rarch->current_video = NULL;
if (hwr)
{
switch (rdr_type)
{
case RETRO_HW_CONTEXT_OPENGL_CORE:
case RETRO_HW_CONTEXT_VULKAN:
case RETRO_HW_CONTEXT_DIRECT3D:
#if defined(HAVE_VULKAN) || defined(HAVE_D3D11) || defined(HAVE_D3D9) || defined(HAVE_OPENGL_CORE)
RARCH_LOG("[Video]: Using HW render, %s driver forced.\n",
rdr_context_name);
if (!string_is_equal(settings->arrays.video_driver,
rdr_context_name))
{
strlcpy(p_rarch->cached_video_driver,
settings->arrays.video_driver,
sizeof(p_rarch->cached_video_driver));
configuration_set_string(settings,
settings->arrays.video_driver,
rdr_context_name);
RARCH_LOG("[Video]: \"%s\" saved as cached driver.\n",
settings->arrays.video_driver);
}
p_rarch->current_video = hw_render_context_driver(rdr_type, rdr_major, rdr_minor);
return true;
#else
break;
#endif
case RETRO_HW_CONTEXT_OPENGL:
#if defined(HAVE_OPENGL)
RARCH_LOG("[Video]: Using HW render, OpenGL driver forced.\n");
/* If we have configured one of the HW render
* capable GL drivers, go with that. */
#if defined(HAVE_OPENGL_CORE)
if ( !string_is_equal(settings->arrays.video_driver, "gl") &&
!string_is_equal(settings->arrays.video_driver, "glcore"))
{
strlcpy(p_rarch->cached_video_driver,
settings->arrays.video_driver,
sizeof(p_rarch->cached_video_driver));
configuration_set_string(settings,
settings->arrays.video_driver,
"glcore");
RARCH_LOG("[Video]: \"%s\" saved as cached driver.\n",
settings->arrays.video_driver);
p_rarch->current_video = &video_gl_core;
return true;
}
#else
if ( !string_is_equal(settings->arrays.video_driver, "gl"))
{
strlcpy(p_rarch->cached_video_driver,
settings->arrays.video_driver,
sizeof(p_rarch->cached_video_driver));
configuration_set_string(settings,
settings->arrays.video_driver,
"gl");
RARCH_LOG("[Video]: \"%s\" saved as cached driver.\n",
settings->arrays.video_driver);
p_rarch->current_video = &video_gl2;
return true;
}
#endif
RARCH_LOG("[Video]: Using configured \"%s\""
" driver for GL HW render.\n",
settings->arrays.video_driver);
break;
#endif
default:
case RETRO_HW_CONTEXT_NONE:
break;
}
}
}
if (frontend_driver_has_get_video_driver_func())
{
if ((p_rarch->current_video = (video_driver_t*)
frontend_driver_get_video_driver()))
return true;
RARCH_WARN("[Video]: Frontend supports get_video_driver() but did not specify one.\n");
}
i = (int)driver_find_index(
"video_driver",
settings->arrays.video_driver);
if (i >= 0)
p_rarch->current_video = (video_driver_t*)video_drivers[i];
else
{
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.video_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; video_drivers[d]; d++)
RARCH_LOG_OUTPUT("\t%s\n", video_drivers[d]->ident);
RARCH_WARN("Going to default to first %s...\n", prefix);
}
if (!(p_rarch->current_video = (video_driver_t*)video_drivers[0]))
retroarch_fail(p_rarch, 1, "find_video_driver()");
}
return true;
}
void video_driver_apply_state_changes(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->video_driver_poke &&
p_rarch->video_driver_poke->apply_state_changes)
p_rarch->video_driver_poke->apply_state_changes(
p_rarch->video_driver_data);
}
bool video_driver_read_viewport(uint8_t *buffer, bool is_idle)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->current_video->read_viewport
&& p_rarch->current_video->read_viewport(
p_rarch->video_driver_data, buffer, is_idle))
return true;
return false;
}
static void video_driver_reinit_context(struct rarch_state *p_rarch,
settings_t *settings, int flags)
{
/* RARCH_DRIVER_CTL_UNINIT clears the callback struct so we
* need to make sure to keep a copy */
struct retro_hw_render_callback hwr_copy;
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
const struct retro_hw_render_context_negotiation_interface *iface =
p_rarch->hw_render_context_negotiation;
memcpy(&hwr_copy, hwr, sizeof(hwr_copy));
driver_uninit(p_rarch, flags);
memcpy(hwr, &hwr_copy, sizeof(*hwr));
p_rarch->hw_render_context_negotiation = iface;
drivers_init(p_rarch, settings, flags, verbosity_is_enabled());
}
void video_driver_reinit(int flags)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
p_rarch->video_driver_cache_context = (hwr->cache_context != false);
p_rarch->video_driver_cache_context_ack = false;
video_driver_reinit_context(p_rarch, settings, flags);
p_rarch->video_driver_cache_context = false;
}
bool video_driver_is_hw_context(void)
{
bool is_hw_context = false;
struct rarch_state *p_rarch = &rarch_st;
VIDEO_DRIVER_CONTEXT_LOCK();
is_hw_context = (p_rarch->hw_render.context_type
!= RETRO_HW_CONTEXT_NONE);
VIDEO_DRIVER_CONTEXT_UNLOCK();
return is_hw_context;
}
const struct retro_hw_render_context_negotiation_interface *
video_driver_get_context_negotiation_interface(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->hw_render_context_negotiation;
}
bool video_driver_is_video_cache_context(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_cache_context;
}
void video_driver_set_video_cache_context_ack(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_cache_context_ack = true;
}
bool video_driver_get_viewport_info(struct video_viewport *viewport)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video || !p_rarch->current_video->viewport_info)
return false;
p_rarch->current_video->viewport_info(
p_rarch->video_driver_data, viewport);
return true;
}
/**
* video_viewport_get_scaled_integer:
* @vp : Viewport handle
* @width : Width.
* @height : Height.
* @aspect_ratio : Aspect ratio (in float).
* @keep_aspect : Preserve aspect ratio?
*
* Gets viewport scaling dimensions based on
* scaled integer aspect ratio.
**/
void video_viewport_get_scaled_integer(struct video_viewport *vp,
unsigned width, unsigned height,
float aspect_ratio, bool keep_aspect)
{
int padding_x = 0;
int padding_y = 0;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
unsigned video_aspect_ratio_idx = settings->uints.video_aspect_ratio_idx;
bool overscale = settings->bools.video_scale_integer_overscale;
if (video_aspect_ratio_idx == ASPECT_RATIO_CUSTOM)
{
struct video_viewport *custom = &settings->video_viewport_custom;
if (custom)
{
padding_x = width - custom->width;
padding_y = height - custom->height;
width = custom->width;
height = custom->height;
}
}
else
{
unsigned base_width;
/* Use system reported sizes as these define the
* geometry for the "normal" case. */
unsigned base_height =
p_rarch->video_driver_av_info.geometry.base_height;
unsigned int rotation = retroarch_get_rotation();
if (rotation % 2)
base_height = p_rarch->video_driver_av_info.geometry.base_width;
if (base_height == 0)
base_height = 1;
/* Account for non-square pixels.
* This is sort of contradictory with the goal of integer scale,
* but it is desirable in some cases.
*
* If square pixels are used, base_height will be equal to
* system->av_info.base_height. */
base_width = (unsigned)roundf(base_height * aspect_ratio);
/* Make sure that we don't get 0x scale ... */
if (width >= base_width && height >= base_height)
{
if (keep_aspect)
{
/* X/Y scale must be same. */
unsigned max_scale = 1;
if (overscale)
max_scale = MIN((width / base_width) + !!(width % base_width),
(height / base_height) + !!(height % base_height));
else
max_scale = MIN(width / base_width,
height / base_height);
padding_x = width - base_width * max_scale;
padding_y = height - base_height * max_scale;
}
else
{
/* X/Y can be independent, each scaled as much as possible. */
padding_x = width % base_width;
padding_y = height % base_height;
}
}
width -= padding_x;
height -= padding_y;
}
vp->width = width;
vp->height = height;
vp->x = padding_x / 2;
vp->y = padding_y / 2;
}
struct video_viewport *video_viewport_get_custom(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
return &settings->video_viewport_custom;
}
/**
* video_driver_frame:
* @data : pointer to data of the video frame.
* @width : width of the video frame.
* @height : height of the video frame.
* @pitch : pitch of the video frame.
*
* Video frame render callback function.
**/
static void video_driver_frame(const void *data, unsigned width,
unsigned height, size_t pitch)
{
char status_text[128];
static char video_driver_msg[256];
static retro_time_t curr_time;
static retro_time_t fps_time;
static float last_fps, frame_time;
static uint64_t last_used_memory, last_total_memory;
retro_time_t new_time;
video_frame_info_t video_info;
struct rarch_state *p_rarch = &rarch_st;
const enum retro_pixel_format
video_driver_pix_fmt = p_rarch->video_driver_pix_fmt;
bool runloop_idle = runloop_state.idle;
bool video_driver_active = p_rarch->video_driver_active;
#if defined(HAVE_GFX_WIDGETS)
bool widgets_active = p_rarch->widgets_active;
#endif
status_text[0] = '\0';
video_driver_msg[0] = '\0';
if (!video_driver_active)
return;
new_time = cpu_features_get_time_usec();
if (data)
p_rarch->frame_cache_data = data;
p_rarch->frame_cache_width = width;
p_rarch->frame_cache_height = height;
p_rarch->frame_cache_pitch = pitch;
if (
p_rarch->video_driver_scaler_ptr
&& data
&& (video_driver_pix_fmt == RETRO_PIXEL_FORMAT_0RGB1555)
&& (data != RETRO_HW_FRAME_BUFFER_VALID)
&& video_pixel_frame_scale(
p_rarch->video_driver_scaler_ptr->scaler,
p_rarch->video_driver_scaler_ptr->scaler_out,
data, width, height, pitch)
)
{
data = p_rarch->video_driver_scaler_ptr->scaler_out;
pitch = p_rarch->video_driver_scaler_ptr->scaler->out_stride;
}
video_driver_build_info(&video_info);
/* Get the amount of frames per seconds. */
if (p_rarch->video_driver_frame_count)
{
unsigned fps_update_interval =
video_info.fps_update_interval;
unsigned memory_update_interval =
video_info.memory_update_interval;
size_t buf_pos = 1;
/* set this to 1 to avoid an offset issue */
unsigned write_index =
p_rarch->video_driver_frame_time_count++ &
(MEASURE_FRAME_TIME_SAMPLES_COUNT - 1);
frame_time = new_time - fps_time;
p_rarch->video_driver_frame_time_samples
[write_index] = frame_time;
fps_time = new_time;
if (video_info.fps_show)
buf_pos = snprintf(
status_text, sizeof(status_text),
"FPS: %6.2f", last_fps);
if (video_info.framecount_show)
{
char frames_text[64];
if (status_text[buf_pos-1] != '\0')
strlcat(status_text, " || ", sizeof(status_text));
snprintf(frames_text,
sizeof(frames_text),
"%s: %" PRIu64, msg_hash_to_str(MSG_FRAMES),
(uint64_t)p_rarch->video_driver_frame_count);
buf_pos = strlcat(status_text, frames_text, sizeof(status_text));
}
if (video_info.memory_show)
{
char mem[128];
if ((p_rarch->video_driver_frame_count % memory_update_interval) == 0)
{
last_total_memory = frontend_driver_get_total_memory();
last_used_memory = last_total_memory - frontend_driver_get_free_memory();
}
mem[0] = '\0';
snprintf(
mem, sizeof(mem), "MEM: %.2f/%.2fMB", last_used_memory / (1024.0f * 1024.0f),
last_total_memory / (1024.0f * 1024.0f));
if (status_text[buf_pos-1] != '\0')
strlcat(status_text, " || ", sizeof(status_text));
strlcat(status_text, mem, sizeof(status_text));
}
if ((p_rarch->video_driver_frame_count % fps_update_interval) == 0)
{
last_fps = TIME_TO_FPS(curr_time, new_time,
fps_update_interval);
strlcpy(p_rarch->video_driver_window_title,
p_rarch->video_driver_title_buf,
sizeof(p_rarch->video_driver_window_title));
if (!string_is_empty(status_text))
{
strlcat(p_rarch->video_driver_window_title,
" || ", sizeof(p_rarch->video_driver_window_title));
strlcat(p_rarch->video_driver_window_title,
status_text, sizeof(p_rarch->video_driver_window_title));
}
curr_time = new_time;
p_rarch->video_driver_window_title_update = true;
}
}
else
{
curr_time = fps_time = new_time;
strlcpy(p_rarch->video_driver_window_title,
p_rarch->video_driver_title_buf,
sizeof(p_rarch->video_driver_window_title));
if (video_info.fps_show)
strlcpy(status_text,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE),
sizeof(status_text));
p_rarch->video_driver_window_title_update = true;
}
/* Add core status message to status text */
if (video_info.core_status_msg_show)
{
/* Note: We need to lock a mutex here. Strictly
* speaking, runloop_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_state);
/* Check whether duration timer has elapsed */
runloop_core_status_msg.duration -= anim_get_ptr()->delta_time;
if (runloop_core_status_msg.duration < 0.0f)
{
runloop_core_status_msg.str[0] = '\0';
runloop_core_status_msg.priority = 0;
runloop_core_status_msg.duration = 0.0f;
runloop_core_status_msg.set = false;
}
else
{
/* If status text is already set, add status
* message at the end */
if (!string_is_empty(status_text))
{
strlcat(status_text,
" || ", sizeof(status_text));
strlcat(status_text,
runloop_core_status_msg.str, sizeof(status_text));
}
else
strlcpy(status_text, runloop_core_status_msg.str,
sizeof(status_text));
}
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
}
/* Slightly messy code,
* but we really need to do processing before blocking on VSync
* for best possible scheduling.
*/
if (
(
#ifdef HAVE_VIDEO_FILTER
!p_rarch->video_driver_state_filter ||
#endif
!video_info.post_filter_record
|| !data
|| p_rarch->video_driver_record_gpu_buffer
) && p_rarch->recording_data
&& p_rarch->recording_driver
&& p_rarch->recording_driver->push_video)
recording_dump_frame(p_rarch,
data, width, height,
pitch, runloop_idle);
#ifdef HAVE_VIDEO_FILTER
if (data && p_rarch->video_driver_state_filter)
{
unsigned output_width = 0;
unsigned output_height = 0;
unsigned output_pitch = 0;
rarch_softfilter_get_output_size(p_rarch->video_driver_state_filter,
&output_width, &output_height, width, height);
output_pitch = (output_width) * p_rarch->video_driver_state_out_bpp;
rarch_softfilter_process(p_rarch->video_driver_state_filter,
p_rarch->video_driver_state_buffer, output_pitch,
data, width, height, pitch);
if (video_info.post_filter_record
&& p_rarch->recording_data
&& p_rarch->recording_driver
&& p_rarch->recording_driver->push_video)
recording_dump_frame(p_rarch,
p_rarch->video_driver_state_buffer,
output_width, output_height, output_pitch,
runloop_idle);
data = p_rarch->video_driver_state_buffer;
width = output_width;
height = output_height;
pitch = output_pitch;
}
#endif
if (runloop_state.msg_queue_size > 0)
{
/* If widgets are currently enabled, then
* messages were pushed to the queue before
* widgets were initialised - in this case, the
* first item in the message queue should be
* extracted and pushed to the widget message
* queue instead */
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
msg_queue_entry_t msg_entry;
bool msg_found = false;
RUNLOOP_MSG_QUEUE_LOCK(runloop_state);
msg_found = msg_queue_extract(
&runloop_state.msg_queue, &msg_entry);
runloop_state.msg_queue_size = msg_queue_size(
&runloop_state.msg_queue);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
if (msg_found)
gfx_widgets_msg_queue_push(
&p_rarch->dispwidget_st,
NULL,
msg_entry.msg,
roundf((float)msg_entry.duration / 60.0f * 1000.0f),
msg_entry.title,
msg_entry.icon,
msg_entry.category,
msg_entry.prio,
false,
#ifdef HAVE_MENU
p_rarch->menu_driver_alive
#else
false
#endif
);
}
/* ...otherwise, just output message via
* regular OSD notification text (if enabled) */
else if (video_info.font_enable)
#else
if (video_info.font_enable)
#endif
{
const char *msg = NULL;
RUNLOOP_MSG_QUEUE_LOCK(runloop_state);
msg = msg_queue_pull(&runloop_state.msg_queue);
runloop_state.msg_queue_size = msg_queue_size(&runloop_state.msg_queue);
if (msg)
strlcpy(video_driver_msg, msg, sizeof(video_driver_msg));
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
}
}
if (video_info.statistics_show)
{
audio_statistics_t audio_stats;
double stddev = 0.0;
struct retro_system_av_info *av_info = &p_rarch->video_driver_av_info;
unsigned red = 255;
unsigned green = 255;
unsigned blue = 255;
unsigned alpha = 255;
audio_stats.samples = 0;
audio_stats.average_buffer_saturation = 0.0f;
audio_stats.std_deviation_percentage = 0.0f;
audio_stats.close_to_underrun = 0.0f;
audio_stats.close_to_blocking = 0.0f;
video_monitor_fps_statistics(NULL, &stddev, NULL);
video_info.osd_stat_params.x = 0.010f;
video_info.osd_stat_params.y = 0.950f;
video_info.osd_stat_params.scale = 1.0f;
video_info.osd_stat_params.full_screen = true;
video_info.osd_stat_params.drop_x = -2;
video_info.osd_stat_params.drop_y = -2;
video_info.osd_stat_params.drop_mod = 0.3f;
video_info.osd_stat_params.drop_alpha = 1.0f;
video_info.osd_stat_params.color = COLOR_ABGR(
red, green, blue, alpha);
audio_compute_buffer_statistics(p_rarch, &audio_stats);
snprintf(video_info.stat_text,
sizeof(video_info.stat_text),
"Video Statistics:\n -Frame rate: %6.2f fps\n -Frame time: %6.2f ms\n -Frame time deviation: %.3f %%\n"
" -Frame count: %" PRIu64"\n -Viewport: %d x %d x %3.2f\n"
"Audio Statistics:\n -Average buffer saturation: %.2f %%\n -Standard deviation: %.2f %%\n -Time spent close to underrun: %.2f %%\n -Time spent close to blocking: %.2f %%\n -Sample count: %d\n"
"Core Geometry:\n -Size: %u x %u\n -Max Size: %u x %u\n -Aspect: %3.2f\nCore Timing:\n -FPS: %3.2f\n -Sample Rate: %6.2f\n",
last_fps,
frame_time / 1000.0f,
100.0f * stddev,
p_rarch->video_driver_frame_count,
video_info.width,
video_info.height,
video_info.refresh_rate,
audio_stats.average_buffer_saturation,
audio_stats.std_deviation_percentage,
audio_stats.close_to_underrun,
audio_stats.close_to_blocking,
audio_stats.samples,
av_info->geometry.base_width,
av_info->geometry.base_height,
av_info->geometry.max_width,
av_info->geometry.max_height,
av_info->geometry.aspect_ratio,
av_info->timing.fps,
av_info->timing.sample_rate);
/* TODO/FIXME - add OSD chat text here */
}
if (p_rarch->current_video && p_rarch->current_video->frame)
p_rarch->video_driver_active = p_rarch->current_video->frame(
p_rarch->video_driver_data, data, width, height,
p_rarch->video_driver_frame_count, (unsigned)pitch,
video_info.menu_screensaver_active ? "" : video_driver_msg,
&video_info);
p_rarch->video_driver_frame_count++;
/* Display the status text, with a higher priority. */
if ( ( video_info.fps_show
|| video_info.framecount_show
|| video_info.memory_show
|| video_info.core_status_msg_show
)
&& !video_info.menu_screensaver_active
)
{
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
strlcpy(
p_rarch->dispwidget_st.gfx_widgets_status_text,
status_text,
sizeof(p_rarch->dispwidget_st.gfx_widgets_status_text)
);
else
#endif
{
runloop_msg_queue_push(status_text, 2, 1, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
}
#if defined(HAVE_CRTSWITCHRES)
/* trigger set resolution*/
if (video_info.crt_switch_resolution)
{
unsigned native_width = width;
bool dynamic_super_width = false;
p_rarch->video_driver_crt_switching_active = true;
switch (video_info.crt_switch_resolution_super)
{
case 2560:
case 3840:
case 1920:
width = video_info.crt_switch_resolution_super;
dynamic_super_width = false;
break;
case 1:
dynamic_super_width = true;
break;
default:
break;
}
crt_switch_res_core(
&p_rarch->crt_switch_st,
native_width, width,
height,
p_rarch->video_driver_core_hz,
video_info.crt_switch_resolution,
video_info.crt_switch_center_adjust,
video_info.crt_switch_porch_adjust,
video_info.monitor_index,
dynamic_super_width,
video_info.crt_switch_resolution_super,
video_info.crt_switch_hires_menu);
}
else if (!video_info.crt_switch_resolution)
#endif
p_rarch->video_driver_crt_switching_active = false;
}
void crt_switch_driver_refresh(void)
{
/*video_context_driver_reset();*/
video_driver_reinit(DRIVERS_CMD_ALL);
}
char* crt_switch_core_name(void)
{
return (char*)runloop_state.system.info.library_name;
}
void video_driver_display_type_set(enum rarch_display_type type)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_display_type = type;
}
uintptr_t video_driver_display_get(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_display;
}
uintptr_t video_driver_display_userdata_get(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_display_userdata;
}
void video_driver_display_userdata_set(uintptr_t idx)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_display_userdata = idx;
}
void video_driver_display_set(uintptr_t idx)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_display = idx;
}
enum rarch_display_type video_driver_display_type_get(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_display_type;
}
void video_driver_window_set(uintptr_t idx)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->video_driver_window = idx;
}
uintptr_t video_driver_window_get(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_window;
}
bool video_driver_texture_load(void *data,
enum texture_filter_type filter_type,
uintptr_t *id)
{
struct rarch_state *p_rarch = &rarch_st;
if ( !id
|| !p_rarch->video_driver_poke
|| !p_rarch->video_driver_poke->load_texture)
return false;
*id = p_rarch->video_driver_poke->load_texture(
p_rarch->video_driver_data, data,
VIDEO_DRIVER_IS_THREADED_INTERNAL(),
filter_type);
return true;
}
bool video_driver_texture_unload(uintptr_t *id)
{
struct rarch_state *p_rarch = &rarch_st;
if ( !p_rarch->video_driver_poke
|| !p_rarch->video_driver_poke->unload_texture)
return false;
p_rarch->video_driver_poke->unload_texture(
p_rarch->video_driver_data,
VIDEO_DRIVER_IS_THREADED_INTERNAL(),
*id);
*id = 0;
return true;
}
void video_driver_build_info(video_frame_info_t *video_info)
{
video_viewport_t *custom_vp = NULL;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
#ifdef HAVE_THREADS
bool is_threaded =
VIDEO_DRIVER_IS_THREADED_INTERNAL();
VIDEO_DRIVER_THREADED_LOCK(is_threaded);
#endif
custom_vp = &settings->video_viewport_custom;
#ifdef HAVE_GFX_WIDGETS
video_info->widgets_active = p_rarch->widgets_active;
#else
video_info->widgets_active = false;
#endif
video_info->refresh_rate = settings->floats.video_refresh_rate;
video_info->crt_switch_resolution = settings->uints.crt_switch_resolution;
video_info->crt_switch_resolution_super = settings->uints.crt_switch_resolution_super;
video_info->crt_switch_center_adjust = settings->ints.crt_switch_center_adjust;
video_info->crt_switch_porch_adjust = settings->ints.crt_switch_porch_adjust;
video_info->crt_switch_hires_menu = settings->bools.crt_switch_hires_menu;
video_info->black_frame_insertion = settings->uints.video_black_frame_insertion;
video_info->hard_sync = settings->bools.video_hard_sync;
video_info->hard_sync_frames = settings->uints.video_hard_sync_frames;
video_info->fps_show = settings->bools.video_fps_show;
video_info->memory_show = settings->bools.video_memory_show;
video_info->statistics_show = settings->bools.video_statistics_show;
video_info->framecount_show = settings->bools.video_framecount_show;
video_info->core_status_msg_show = runloop_core_status_msg.set;
video_info->aspect_ratio_idx = settings->uints.video_aspect_ratio_idx;
video_info->post_filter_record = settings->bools.video_post_filter_record;
video_info->input_menu_swap_ok_cancel_buttons = settings->bools.input_menu_swap_ok_cancel_buttons;
video_info->max_swapchain_images = settings->uints.video_max_swapchain_images;
video_info->windowed_fullscreen = settings->bools.video_windowed_fullscreen;
video_info->fullscreen = settings->bools.video_fullscreen
|| p_rarch->rarch_force_fullscreen;
video_info->menu_mouse_enable = settings->bools.menu_mouse_enable;
video_info->monitor_index = settings->uints.video_monitor_index;
video_info->font_enable = settings->bools.video_font_enable;
video_info->font_msg_pos_x = settings->floats.video_msg_pos_x;
video_info->font_msg_pos_y = settings->floats.video_msg_pos_y;
video_info->font_msg_color_r = settings->floats.video_msg_color_r;
video_info->font_msg_color_g = settings->floats.video_msg_color_g;
video_info->font_msg_color_b = settings->floats.video_msg_color_b;
video_info->custom_vp_x = custom_vp->x;
video_info->custom_vp_y = custom_vp->y;
video_info->custom_vp_width = custom_vp->width;
video_info->custom_vp_height = custom_vp->height;
video_info->custom_vp_full_width = custom_vp->full_width;
video_info->custom_vp_full_height = custom_vp->full_height;
#if defined(HAVE_GFX_WIDGETS)
video_info->widgets_userdata = &p_rarch->dispwidget_st;
video_info->widgets_is_paused = p_rarch->gfx_widgets_paused;
video_info->widgets_is_fast_forwarding = p_rarch->gfx_widgets_fast_forward;
video_info->widgets_is_rewinding = p_rarch->gfx_widgets_rewinding;
#else
video_info->widgets_userdata = NULL;
video_info->widgets_is_paused = false;
video_info->widgets_is_fast_forwarding = false;
video_info->widgets_is_rewinding = false;
#endif
video_info->width = p_rarch->video_driver_width;
video_info->height = p_rarch->video_driver_height;
video_info->use_rgba = p_rarch->video_driver_use_rgba;
video_info->hdr_enable = settings->bools.video_hdr_enable;
video_info->libretro_running = false;
video_info->msg_bgcolor_enable =
settings->bools.video_msg_bgcolor_enable;
video_info->fps_update_interval = settings->uints.fps_update_interval;
video_info->memory_update_interval = settings->uints.memory_update_interval;
#ifdef HAVE_MENU
video_info->menu_is_alive = p_rarch->menu_driver_alive;
video_info->menu_screensaver_active = menu_state_get_ptr()->screensaver_active;
video_info->menu_footer_opacity = settings->floats.menu_footer_opacity;
video_info->menu_header_opacity = settings->floats.menu_header_opacity;
video_info->materialui_color_theme = settings->uints.menu_materialui_color_theme;
video_info->ozone_color_theme = settings->uints.menu_ozone_color_theme;
video_info->menu_shader_pipeline = settings->uints.menu_xmb_shader_pipeline;
video_info->xmb_theme = settings->uints.menu_xmb_theme;
video_info->xmb_color_theme = settings->uints.menu_xmb_color_theme;
video_info->timedate_enable = settings->bools.menu_timedate_enable;
video_info->battery_level_enable = settings->bools.menu_battery_level_enable;
video_info->xmb_shadows_enable =
settings->bools.menu_xmb_shadows_enable;
video_info->xmb_alpha_factor =
settings->uints.menu_xmb_alpha_factor;
video_info->menu_wallpaper_opacity =
settings->floats.menu_wallpaper_opacity;
video_info->menu_framebuffer_opacity =
settings->floats.menu_framebuffer_opacity;
video_info->libretro_running = p_rarch->current_core.game_loaded;
#else
video_info->menu_is_alive = false;
video_info->menu_screensaver_active = false;
video_info->menu_footer_opacity = 0.0f;
video_info->menu_header_opacity = 0.0f;
video_info->materialui_color_theme = 0;
video_info->menu_shader_pipeline = 0;
video_info->xmb_color_theme = 0;
video_info->xmb_theme = 0;
video_info->timedate_enable = false;
video_info->battery_level_enable = false;
video_info->xmb_shadows_enable = false;
video_info->xmb_alpha_factor = 0.0f;
video_info->menu_framebuffer_opacity = 0.0f;
video_info->menu_wallpaper_opacity = 0.0f;
#endif
video_info->runloop_is_paused = runloop_state.paused;
video_info->runloop_is_slowmotion = runloop_state.slowmotion;
video_info->input_driver_nonblock_state = input_driver_st ?
input_driver_st->nonblocking_flag : false;
video_info->input_driver_grab_mouse_state = p_rarch->input_driver_grab_mouse_state;
video_info->disp_userdata = &p_rarch->dispgfx;
video_info->userdata = VIDEO_DRIVER_GET_PTR_INTERNAL(p_rarch);
#ifdef HAVE_THREADS
VIDEO_DRIVER_THREADED_UNLOCK(is_threaded);
#endif
}
/**
* video_driver_translate_coord_viewport:
* @mouse_x : Pointer X coordinate.
* @mouse_y : Pointer Y coordinate.
* @res_x : Scaled X coordinate.
* @res_y : Scaled Y coordinate.
* @res_screen_x : Scaled screen X coordinate.
* @res_screen_y : Scaled screen Y coordinate.
*
* Translates pointer [X,Y] coordinates into scaled screen
* coordinates based on viewport info.
*
* Returns: true (1) if successful, false if video driver doesn't support
* viewport info.
**/
bool video_driver_translate_coord_viewport(
struct video_viewport *vp,
int mouse_x, int mouse_y,
int16_t *res_x, int16_t *res_y,
int16_t *res_screen_x, int16_t *res_screen_y)
{
int norm_vp_width = (int)vp->width;
int norm_vp_height = (int)vp->height;
int norm_full_vp_width = (int)vp->full_width;
int norm_full_vp_height = (int)vp->full_height;
int scaled_screen_x = -0x8000; /* OOB */
int scaled_screen_y = -0x8000; /* OOB */
int scaled_x = -0x8000; /* OOB */
int scaled_y = -0x8000; /* OOB */
if (norm_vp_width <= 0 ||
norm_vp_height <= 0 ||
norm_full_vp_width <= 0 ||
norm_full_vp_height <= 0)
return false;
if (mouse_x >= 0 && mouse_x <= norm_full_vp_width)
scaled_screen_x = ((2 * mouse_x * 0x7fff)
/ norm_full_vp_width) - 0x7fff;
if (mouse_y >= 0 && mouse_y <= norm_full_vp_height)
scaled_screen_y = ((2 * mouse_y * 0x7fff)
/ norm_full_vp_height) - 0x7fff;
mouse_x -= vp->x;
mouse_y -= vp->y;
if (mouse_x >= 0 && mouse_x <= norm_vp_width)
scaled_x = ((2 * mouse_x * 0x7fff)
/ norm_vp_width) - 0x7fff;
else
scaled_x = -0x8000; /* OOB */
if (mouse_y >= 0 && mouse_y <= norm_vp_height)
scaled_y = ((2 * mouse_y * 0x7fff)
/ norm_vp_height) - 0x7fff;
*res_x = scaled_x;
*res_y = scaled_y;
*res_screen_x = scaled_screen_x;
*res_screen_y = scaled_screen_y;
return true;
}
bool video_driver_has_focus(void)
{
struct rarch_state *p_rarch = &rarch_st;
return VIDEO_HAS_FOCUS(p_rarch);
}
void video_driver_get_window_title(char *buf, unsigned len)
{
struct rarch_state *p_rarch = &rarch_st;
if (buf && p_rarch->video_driver_window_title_update)
{
struct rarch_state *p_rarch = &rarch_st;
strlcpy(buf, p_rarch->video_driver_window_title, len);
p_rarch->video_driver_window_title_update = false;
}
}
/**
* video_context_driver_init:
* @core_set_shared_context : Boolean value that tells us whether shared context
* is set.
* @ctx : Graphics context driver to initialize.
* @ident : Identifier of graphics context driver to find.
* @api : API of higher-level graphics API.
* @major : Major version number of higher-level graphics API.
* @minor : Minor version number of higher-level graphics API.
* @hw_render_ctx : Request a graphics context driver capable of
* hardware rendering?
*
* Initialize graphics context driver.
*
* Returns: graphics context driver if successfully initialized,
* otherwise NULL.
**/
static const gfx_ctx_driver_t *video_context_driver_init(
bool core_set_shared_context,
settings_t *settings,
void *data,
const gfx_ctx_driver_t *ctx,
const char *ident,
enum gfx_ctx_api api, unsigned major,
unsigned minor, bool hw_render_ctx,
void **ctx_data)
{
if (!ctx->bind_api(data, api, major, minor))
{
RARCH_WARN("Failed to bind API (#%u, version %u.%u)"
" on context driver \"%s\".\n",
(unsigned)api, major, minor, ctx->ident);
return NULL;
}
if (!(*ctx_data = ctx->init(data)))
return NULL;
if (ctx->bind_hw_render)
{
bool video_shared_context =
settings->bools.video_shared_context || core_set_shared_context;
ctx->bind_hw_render(*ctx_data,
video_shared_context && hw_render_ctx);
}
return ctx;
}
#ifdef HAVE_VULKAN
static const gfx_ctx_driver_t *vk_context_driver_init_first(
struct rarch_state *p_rarch,
settings_t *settings,
void *data,
const char *ident, enum gfx_ctx_api api, unsigned major,
unsigned minor, bool hw_render_ctx, void **ctx_data)
{
unsigned j;
int i = -1;
for (j = 0; gfx_ctx_vk_drivers[j]; j++)
{
if (string_is_equal_noncase(ident, gfx_ctx_vk_drivers[j]->ident))
{
i = j;
break;
}
}
if (i >= 0)
{
const gfx_ctx_driver_t *ctx = video_context_driver_init(
p_rarch->core_set_shared_context,
settings,
data,
gfx_ctx_vk_drivers[i], ident,
api, major, minor, hw_render_ctx, ctx_data);
if (ctx)
{
p_rarch->video_context_data = *ctx_data;
return ctx;
}
}
for (i = 0; gfx_ctx_vk_drivers[i]; i++)
{
const gfx_ctx_driver_t *ctx =
video_context_driver_init(
p_rarch->core_set_shared_context,
settings,
data,
gfx_ctx_vk_drivers[i], ident,
api, major, minor, hw_render_ctx, ctx_data);
if (ctx)
{
p_rarch->video_context_data = *ctx_data;
return ctx;
}
}
return NULL;
}
#endif
static const gfx_ctx_driver_t *gl_context_driver_init_first(
struct rarch_state *p_rarch,
settings_t *settings,
void *data,
const char *ident, enum gfx_ctx_api api, unsigned major,
unsigned minor, bool hw_render_ctx, void **ctx_data)
{
unsigned j;
int i = -1;
for (j = 0; gfx_ctx_gl_drivers[j]; j++)
{
if (string_is_equal_noncase(ident, gfx_ctx_gl_drivers[j]->ident))
{
i = j;
break;
}
}
if (i >= 0)
{
const gfx_ctx_driver_t *ctx = video_context_driver_init(
p_rarch->core_set_shared_context,
settings,
data,
gfx_ctx_gl_drivers[i], ident,
api, major, minor, hw_render_ctx, ctx_data);
if (ctx)
{
p_rarch->video_context_data = *ctx_data;
return ctx;
}
}
for (i = 0; gfx_ctx_gl_drivers[i]; i++)
{
const gfx_ctx_driver_t *ctx =
video_context_driver_init(
p_rarch->core_set_shared_context,
settings,
data,
gfx_ctx_gl_drivers[i], ident,
api, major, minor, hw_render_ctx, ctx_data);
if (ctx)
{
p_rarch->video_context_data = *ctx_data;
return ctx;
}
}
return NULL;
}
/**
* video_context_driver_init_first:
* @data : Input data.
* @ident : Identifier of graphics context driver to find.
* @api : API of higher-level graphics API.
* @major : Major version number of higher-level graphics API.
* @minor : Minor version number of higher-level graphics API.
* @hw_render_ctx : Request a graphics context driver capable of
* hardware rendering?
*
* Finds first suitable graphics context driver and initializes.
*
* Returns: graphics context driver if found, otherwise NULL.
**/
const gfx_ctx_driver_t *video_context_driver_init_first(void *data,
const char *ident, enum gfx_ctx_api api, unsigned major,
unsigned minor, bool hw_render_ctx, void **ctx_data)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
switch (api)
{
case GFX_CTX_VULKAN_API:
#ifdef HAVE_VULKAN
{
const gfx_ctx_driver_t *ptr = vk_context_driver_init_first(
p_rarch, settings,
data, ident, api, major, minor, hw_render_ctx, ctx_data);
if (ptr && !string_is_equal(ptr->ident, "null"))
return ptr;
/* fall-through if no valid driver was found */
}
#endif
case GFX_CTX_OPENGL_API:
case GFX_CTX_OPENGL_ES_API:
case GFX_CTX_OPENVG_API:
case GFX_CTX_METAL_API:
case GFX_CTX_RSX_API:
return gl_context_driver_init_first(
p_rarch, settings,
data, ident, api, major, minor,
hw_render_ctx, ctx_data);
case GFX_CTX_NONE:
default:
break;
}
return NULL;
}
void video_context_driver_free(void)
{
struct rarch_state *p_rarch = &rarch_st;
video_context_driver_destroy_internal(&p_rarch->current_video_context);
p_rarch->video_context_data = NULL;
}
bool video_context_driver_get_metrics(gfx_ctx_metrics_t *metrics)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch && p_rarch->current_video_context.get_metrics)
return p_rarch->current_video_context.get_metrics(
p_rarch->video_context_data,
metrics->type,
metrics->value);
return false;
}
bool video_context_driver_get_refresh_rate(float *refresh_rate)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video_context.get_refresh_rate || !refresh_rate)
return false;
if (!p_rarch->video_context_data)
return false;
if (!p_rarch->video_driver_crt_switching_active)
{
if (refresh_rate)
*refresh_rate =
p_rarch->current_video_context.get_refresh_rate(
p_rarch->video_context_data);
}
else
{
float refresh_holder = 0;
if (refresh_rate)
refresh_holder =
p_rarch->current_video_context.get_refresh_rate(
p_rarch->video_context_data);
/* Fix for incorrect interlacing detection --
* HARD SET VSNC TO REQUIRED REFRESH FOR CRT*/
if (refresh_holder != p_rarch->video_driver_core_hz)
*refresh_rate = p_rarch->video_driver_core_hz;
}
return true;
}
bool video_context_driver_get_ident(gfx_ctx_ident_t *ident)
{
struct rarch_state *p_rarch = &rarch_st;
if (!ident)
return false;
ident->ident = p_rarch->current_video_context.ident;
return true;
}
bool video_context_driver_get_flags(gfx_ctx_flags_t *flags)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_video_context.get_flags)
return false;
if (p_rarch->deferred_video_context_driver_set_flags)
{
flags->flags =
p_rarch->deferred_flag_data.flags;
p_rarch->deferred_video_context_driver_set_flags = false;
return true;
}
flags->flags = p_rarch->current_video_context.get_flags(
p_rarch->video_context_data);
return true;
}
static bool video_driver_get_flags(
struct rarch_state *p_rarch,
gfx_ctx_flags_t *flags)
{
if (!p_rarch->video_driver_poke || !p_rarch->video_driver_poke->get_flags)
return false;
flags->flags = p_rarch->video_driver_poke->get_flags(p_rarch->video_driver_data);
return true;
}
gfx_ctx_flags_t video_driver_get_flags_wrapper(void)
{
gfx_ctx_flags_t flags;
struct rarch_state *p_rarch = &rarch_st;
flags.flags = 0;
if (!video_driver_get_flags(p_rarch, &flags))
video_context_driver_get_flags(&flags);
return flags;
}
/**
* video_driver_test_all_flags:
* @testflag : flag to test
*
* Poll both the video and context driver's flags and test
* whether @testflag is set or not.
**/
bool video_driver_test_all_flags(enum display_flags testflag)
{
gfx_ctx_flags_t flags;
struct rarch_state *p_rarch = &rarch_st;
if (video_driver_get_flags(p_rarch, &flags))
if (BIT32_GET(flags.flags, testflag))
return true;
if (video_context_driver_get_flags(&flags))
if (BIT32_GET(flags.flags, testflag))
return true;
return false;
}
bool video_context_driver_set_flags(gfx_ctx_flags_t *flags)
{
struct rarch_state *p_rarch = &rarch_st;
if (!flags)
return false;
if (p_rarch->current_video_context.set_flags)
{
p_rarch->current_video_context.set_flags(
p_rarch->video_context_data, flags->flags);
return true;
}
p_rarch->deferred_flag_data.flags = flags->flags;
p_rarch->deferred_video_context_driver_set_flags = true;
return false;
}
enum gfx_ctx_api video_context_driver_get_api(void)
{
struct rarch_state *p_rarch = &rarch_st;
enum gfx_ctx_api ctx_api = p_rarch->video_context_data ?
p_rarch->current_video_context.get_api(
p_rarch->video_context_data) : GFX_CTX_NONE;
if (ctx_api == GFX_CTX_NONE)
{
const char *video_ident = (p_rarch->current_video)
? p_rarch->current_video->ident
: NULL;
if (string_starts_with_size(video_ident, "d3d", STRLEN_CONST("d3d")))
{
if (string_is_equal(video_ident, "d3d9"))
return GFX_CTX_DIRECT3D9_API;
else if (string_is_equal(video_ident, "d3d10"))
return GFX_CTX_DIRECT3D10_API;
else if (string_is_equal(video_ident, "d3d11"))
return GFX_CTX_DIRECT3D11_API;
else if (string_is_equal(video_ident, "d3d12"))
return GFX_CTX_DIRECT3D12_API;
}
if (string_starts_with_size(video_ident, "gl", STRLEN_CONST("gl")))
{
if (string_is_equal(video_ident, "gl"))
return GFX_CTX_OPENGL_API;
else if (string_is_equal(video_ident, "gl1"))
return GFX_CTX_OPENGL_API;
else if (string_is_equal(video_ident, "glcore"))
return GFX_CTX_OPENGL_API;
}
else if (string_is_equal(video_ident, "vulkan"))
return GFX_CTX_VULKAN_API;
else if (string_is_equal(video_ident, "metal"))
return GFX_CTX_METAL_API;
return GFX_CTX_NONE;
}
return ctx_api;
}
bool video_driver_has_windowed(void)
{
#if !(defined(RARCH_CONSOLE) || defined(RARCH_MOBILE))
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_data && p_rarch->current_video->has_windowed)
return p_rarch->current_video->has_windowed(p_rarch->video_driver_data);
#endif
return false;
}
bool video_driver_cached_frame_has_valid_framebuffer(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->frame_cache_data)
return (p_rarch->frame_cache_data == RETRO_HW_FRAME_BUFFER_VALID);
return false;
}
bool video_shader_driver_get_current_shader(video_shader_ctx_t *shader)
{
struct rarch_state *p_rarch = &rarch_st;
void *video_driver = p_rarch->video_driver_data;
const video_poke_interface_t *video_poke = p_rarch->video_driver_poke;
shader->data = NULL;
if (!video_poke || !video_driver || !video_poke->get_current_shader)
return false;
shader->data = video_poke->get_current_shader(video_driver);
return true;
}
float video_driver_get_refresh_rate(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->video_driver_poke && p_rarch->video_driver_poke->get_refresh_rate)
return p_rarch->video_driver_poke->get_refresh_rate(p_rarch->video_driver_data);
return 0.0f;
}
#if defined(HAVE_GFX_WIDGETS)
bool video_driver_has_widgets(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->current_video
&& p_rarch->current_video->gfx_widgets_enabled
&& p_rarch->current_video->gfx_widgets_enabled(
p_rarch->video_driver_data);
}
#endif
void video_driver_set_gpu_device_string(const char *str)
{
struct rarch_state *p_rarch = &rarch_st;
strlcpy(p_rarch->video_driver_gpu_device_string, str,
sizeof(p_rarch->video_driver_gpu_device_string));
}
const char* video_driver_get_gpu_device_string(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_gpu_device_string;
}
void video_driver_set_gpu_api_version_string(const char *str)
{
struct rarch_state *p_rarch = &rarch_st;
strlcpy(p_rarch->video_driver_gpu_api_version_string, str,
sizeof(p_rarch->video_driver_gpu_api_version_string));
}
const char* video_driver_get_gpu_api_version_string(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->video_driver_gpu_api_version_string;
}
/* string list stays owned by the caller and must be available at
* all times after the video driver is inited */
void video_driver_set_gpu_api_devices(
enum gfx_ctx_api api, struct string_list *list)
{
int i;
for (i = 0; i < ARRAY_SIZE(gpu_map); i++)
{
if (api == gpu_map[i].api)
{
gpu_map[i].list = list;
break;
}
}
}
struct string_list* video_driver_get_gpu_api_devices(enum gfx_ctx_api api)
{
int i;
for (i = 0; i < ARRAY_SIZE(gpu_map); i++)
{
if (api == gpu_map[i].api)
return gpu_map[i].list;
}
return NULL;
}
/* CAMERA */
/**
* config_get_camera_driver_options:
*
* Get an enumerated list of all camera driver names,
* separated by '|'.
*
* Returns: string listing of all camera driver names,
* separated by '|'.
**/
const char *config_get_camera_driver_options(void)
{
return char_list_new_special(STRING_LIST_CAMERA_DRIVERS, NULL);
}
static bool driver_camera_start(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->camera_driver &&
p_rarch->camera_data &&
p_rarch->camera_driver->start)
{
settings_t *settings = p_rarch->configuration_settings;
bool camera_allow = settings->bools.camera_allow;
if (camera_allow)
return p_rarch->camera_driver->start(p_rarch->camera_data);
runloop_msg_queue_push(
"Camera is explicitly disabled.\n", 1, 180, false,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
return true;
}
static void driver_camera_stop(void)
{
struct rarch_state *p_rarch = &rarch_st;
if ( p_rarch->camera_driver
&& p_rarch->camera_driver->stop
&& p_rarch->camera_data)
p_rarch->camera_driver->stop(p_rarch->camera_data);
}
static void camera_driver_find_driver(struct rarch_state *p_rarch,
settings_t *settings,
const char *prefix,
bool verbosity_enabled)
{
int i = (int)driver_find_index(
"camera_driver",
settings->arrays.camera_driver);
if (i >= 0)
p_rarch->camera_driver = (const camera_driver_t*)camera_drivers[i];
else
{
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.camera_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; camera_drivers[d]; d++)
{
if (camera_drivers[d])
{
RARCH_LOG_OUTPUT("\t%s\n", camera_drivers[d]->ident);
}
}
RARCH_WARN("Going to default to first %s...\n", prefix);
}
p_rarch->camera_driver = (const camera_driver_t*)camera_drivers[0];
if (!p_rarch->camera_driver)
retroarch_fail(p_rarch, 1, "find_camera_driver()");
}
}
static void driver_adjust_system_rates(
struct rarch_state *p_rarch,
bool vrr_runloop_enable,
float video_refresh_rate,
float audio_max_timing_skew,
bool video_adaptive_vsync,
unsigned video_swap_interval)
{
struct retro_system_av_info *av_info = &p_rarch->video_driver_av_info;
const struct retro_system_timing *info =
(const struct retro_system_timing*)&av_info->timing;
double input_sample_rate = info->sample_rate;
double input_fps = info->fps;
if (input_sample_rate > 0.0)
{
if (vrr_runloop_enable)
p_rarch->audio_driver_input = input_sample_rate;
else
p_rarch->audio_driver_input =
audio_driver_monitor_adjust_system_rates(
input_sample_rate,
input_fps,
video_refresh_rate,
video_swap_interval,
audio_max_timing_skew);
RARCH_LOG("[Audio]: Set audio input rate to: %.2f Hz.\n",
p_rarch->audio_driver_input);
}
runloop_state.force_nonblock = false;
if (input_fps > 0.0)
{
float timing_skew_hz = video_refresh_rate;
if (p_rarch->video_driver_crt_switching_active)
timing_skew_hz = input_fps;
p_rarch->video_driver_core_hz = input_fps;
if (!video_driver_monitor_adjust_system_rates(
timing_skew_hz,
video_refresh_rate,
vrr_runloop_enable,
audio_max_timing_skew,
input_fps))
{
/* We won't be able to do VSync reliably when game FPS > monitor FPS. */
runloop_state.force_nonblock = true;
RARCH_LOG("[Video]: Game FPS > Monitor FPS. Cannot rely on VSync.\n");
if (VIDEO_DRIVER_GET_PTR_INTERNAL(p_rarch))
{
if (p_rarch->current_video->set_nonblock_state)
p_rarch->current_video->set_nonblock_state(
p_rarch->video_driver_data, true,
video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
video_adaptive_vsync,
video_swap_interval
);
}
return;
}
}
if (VIDEO_DRIVER_GET_PTR_INTERNAL(p_rarch))
driver_set_nonblock_state();
}
/**
* driver_set_nonblock_state:
*
* Sets audio and video drivers to nonblock state (if enabled).
*
* If nonblock state is false, sets
* blocking state for both audio and video drivers instead.
**/
void driver_set_nonblock_state(void)
{
struct rarch_state
*p_rarch = &rarch_st;
input_driver_state_t
*input_driver_st = &p_rarch->input_driver_state;
bool enable = input_driver_st ?
input_driver_st->nonblocking_flag : false;
settings_t *settings = p_rarch->configuration_settings;
bool audio_sync = settings->bools.audio_sync;
bool video_vsync = settings->bools.video_vsync;
bool adaptive_vsync = settings->bools.video_adaptive_vsync;
unsigned swap_interval = settings->uints.video_swap_interval;
bool video_driver_active = p_rarch->video_driver_active;
bool audio_driver_active = p_rarch->audio_driver_active;
bool runloop_force_nonblock = runloop_state.force_nonblock;
/* Only apply non-block-state for video if we're using vsync. */
if (video_driver_active && VIDEO_DRIVER_GET_PTR_INTERNAL(p_rarch))
{
if (p_rarch->current_video->set_nonblock_state)
{
bool video_nonblock = enable;
if (!video_vsync || runloop_force_nonblock)
video_nonblock = true;
p_rarch->current_video->set_nonblock_state(p_rarch->video_driver_data,
video_nonblock,
video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
adaptive_vsync, swap_interval);
}
}
if (audio_driver_active && p_rarch->audio_driver_context_audio_data)
p_rarch->current_audio->set_nonblock_state(
p_rarch->audio_driver_context_audio_data,
audio_sync ? enable : true);
p_rarch->audio_driver_chunk_size = enable
? p_rarch->audio_driver_chunk_nonblock_size
: p_rarch->audio_driver_chunk_block_size;
}
/**
* drivers_init:
* @flags : Bitmask of drivers to initialize.
*
* Initializes drivers.
* @flags determines which drivers get initialized.
**/
static void drivers_init(struct rarch_state *p_rarch,
settings_t *settings,
int flags,
bool verbosity_enabled)
{
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
bool video_is_threaded = VIDEO_DRIVER_IS_THREADED_INTERNAL();
gfx_display_t *p_disp = &p_rarch->dispgfx;
#if defined(HAVE_GFX_WIDGETS)
bool video_font_enable = settings->bools.video_font_enable;
bool menu_enable_widgets = settings->bools.menu_enable_widgets;
/* By default, we want display widgets to persist through driver reinits. */
p_rarch->widgets_persisting = true;
#endif
#ifdef HAVE_MENU
/* By default, we want the menu to persist through driver reinits. */
if (menu_st)
menu_st->data_own = true;
#endif
if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
driver_adjust_system_rates(p_rarch,
settings->bools.vrr_runloop_enable,
settings->floats.video_refresh_rate,
settings->floats.audio_max_timing_skew,
settings->bools.video_adaptive_vsync,
settings->uints.video_swap_interval
);
/* Initialize video driver */
if (flags & DRIVER_VIDEO_MASK)
{
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(p_rarch);
p_rarch->video_driver_frame_time_count = 0;
video_driver_lock_new(p_rarch);
#ifdef HAVE_VIDEO_FILTER
video_driver_filter_free();
#endif
video_driver_set_cached_frame_ptr(NULL);
video_driver_init_internal(p_rarch, settings, &video_is_threaded,
verbosity_enabled);
if (!p_rarch->video_driver_cache_context_ack
&& hwr->context_reset)
hwr->context_reset();
p_rarch->video_driver_cache_context_ack = false;
runloop_state.frame_time_last = 0;
}
/* Initialize audio driver */
if (flags & DRIVER_AUDIO_MASK)
{
audio_driver_init_internal(p_rarch,
settings,
p_rarch->audio_callback.callback != NULL);
if ( p_rarch->current_audio &&
p_rarch->current_audio->device_list_new &&
p_rarch->audio_driver_context_audio_data)
p_rarch->audio_driver_devices_list = (struct string_list*)
p_rarch->current_audio->device_list_new(
p_rarch->audio_driver_context_audio_data);
}
if (flags & DRIVER_CAMERA_MASK)
{
/* Only initialize camera driver if we're ever going to use it. */
if (p_rarch->camera_driver_active)
{
/* Resource leaks will follow if camera is initialized twice. */
if (!p_rarch->camera_data)
{
camera_driver_find_driver(p_rarch, settings, "camera driver",
verbosity_enabled);
if (p_rarch->camera_driver)
{
p_rarch->camera_data = p_rarch->camera_driver->init(
*settings->arrays.camera_device ?
settings->arrays.camera_device : NULL,
p_rarch->camera_cb.caps,
settings->uints.camera_width ?
settings->uints.camera_width : p_rarch->camera_cb.width,
settings->uints.camera_height ?
settings->uints.camera_height : p_rarch->camera_cb.height);
if (!p_rarch->camera_data)
{
RARCH_ERR("Failed to initialize camera driver. Will continue without camera.\n");
p_rarch->camera_driver_active = false;
}
if (p_rarch->camera_cb.initialized)
p_rarch->camera_cb.initialized();
}
}
}
}
if (flags & DRIVER_BLUETOOTH_MASK)
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_INIT, NULL);
if ((flags & DRIVER_WIFI_MASK))
wifi_driver_ctl(RARCH_WIFI_CTL_INIT, NULL);
if (flags & DRIVER_LOCATION_MASK)
{
/* Only initialize location driver if we're ever going to use it. */
if (p_rarch->location_driver_active)
if (!init_location(&runloop_state.system,
settings, verbosity_is_enabled()))
p_rarch->location_driver_active = false;
}
core_info_init_current_core();
#if defined(HAVE_GFX_WIDGETS)
/* Note that we only enable widgets if 'video_font_enable'
* is true. 'video_font_enable' corresponds to the generic
* 'On-Screen Notifications' setting, which should serve as
* a global notifications on/off toggle switch */
if (video_font_enable &&
menu_enable_widgets &&
video_driver_has_widgets())
{
bool rarch_force_fullscreen = p_rarch->rarch_force_fullscreen;
bool video_is_fullscreen = settings->bools.video_fullscreen ||
rarch_force_fullscreen;
p_rarch->widgets_active = gfx_widgets_init(
&p_rarch->dispwidget_st,
&p_rarch->dispgfx,
anim_get_ptr(),
settings,
(uintptr_t)&p_rarch->widgets_active,
video_is_threaded,
p_rarch->video_driver_width,
p_rarch->video_driver_height,
video_is_fullscreen,
settings->paths.directory_assets,
settings->paths.path_font);
}
else
#endif
{
gfx_display_init_first_driver(p_disp, video_is_threaded);
}
#ifdef HAVE_MENU
if (flags & DRIVER_VIDEO_MASK)
{
/* Initialize menu driver */
if (flags & DRIVER_MENU_MASK)
{
if (!menu_driver_init(video_is_threaded))
RARCH_ERR("Unable to init menu driver.\n");
#ifdef HAVE_LIBRETRODB
menu_explore_context_init();
#endif
}
}
/* Initialising the menu driver will also initialise
* core info - if we are not initialising the menu
* driver, must initialise core info 'by hand' */
if (!(flags & DRIVER_VIDEO_MASK) ||
!(flags & DRIVER_MENU_MASK))
{
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
}
#else
/* Qt uses core info, even if the menu is disabled */
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
#endif
if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
{
/* Keep non-throttled state as good as possible. */
if (input_driver_st && input_driver_st->nonblocking_flag)
driver_set_nonblock_state();
}
/* Initialize LED driver */
if (flags & DRIVER_LED_MASK)
led_driver_init(settings->arrays.led_driver);
/* Initialize MIDI driver */
if (flags & DRIVER_MIDI_MASK)
midi_driver_init(settings);
#ifdef HAVE_LAKKA
cpu_scaling_driver_init();
#endif
}
/**
* Driver ownership - set this to true if the platform in
* question needs to 'own'
* the respective handle and therefore skip regular RetroArch
* driver teardown/reiniting procedure.
*
* If to true, the 'free' function will get skipped. It is
* then up to the driver implementation to properly handle
* 'reiniting' inside the 'init' function and make sure it
* returns the existing handle instead of allocating and
* returning a pointer to a new handle.
*
* Typically, if a driver intends to make use of this, it should
* set this to true at the end of its 'init' function.
**/
static void driver_uninit(struct rarch_state *p_rarch, int flags)
{
core_info_deinit_list();
core_info_free_current_core();
#if defined(HAVE_GFX_WIDGETS)
/* This absolutely has to be done before video_driver_free_internal()
* is called/completes, otherwise certain menu drivers
* (e.g. Vulkan) will segfault */
if (p_rarch->dispwidget_st.widgets_inited)
{
gfx_widgets_deinit(&p_rarch->dispwidget_st, p_rarch->widgets_persisting);
p_rarch->widgets_active = false;
}
#endif
#ifdef HAVE_MENU
if (flags & DRIVER_MENU_MASK)
{
#ifdef HAVE_LIBRETRODB
menu_explore_context_deinit();
#endif
menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
}
#endif
if ((flags & DRIVER_LOCATION_MASK))
uninit_location(&runloop_state.system);
if ((flags & DRIVER_CAMERA_MASK))
{
if (p_rarch->camera_data && p_rarch->camera_driver)
{
if (p_rarch->camera_cb.deinitialized)
p_rarch->camera_cb.deinitialized();
if (p_rarch->camera_driver->free)
p_rarch->camera_driver->free(p_rarch->camera_data);
}
p_rarch->camera_data = NULL;
}
if ((flags & DRIVER_BLUETOOTH_MASK))
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_DEINIT, NULL);
if ((flags & DRIVER_WIFI_MASK))
wifi_driver_ctl(RARCH_WIFI_CTL_DEINIT, NULL);
if (flags & DRIVER_LED)
led_driver_free();
if (flags & DRIVERS_VIDEO_INPUT)
{
video_driver_free_internal(p_rarch);
VIDEO_DRIVER_LOCK_FREE();
p_rarch->video_driver_data = NULL;
video_driver_set_cached_frame_ptr(NULL);
}
if (flags & DRIVER_AUDIO_MASK)
audio_driver_deinit(p_rarch, p_rarch->configuration_settings);
if ((flags & DRIVER_VIDEO_MASK))
p_rarch->video_driver_data = NULL;
if ((flags & DRIVER_INPUT_MASK))
p_rarch->input_driver_state.current_data = NULL;
if ((flags & DRIVER_AUDIO_MASK))
p_rarch->audio_driver_context_audio_data = NULL;
if (flags & DRIVER_MIDI_MASK)
midi_driver_free();
#ifdef HAVE_LAKKA
cpu_scaling_driver_free();
#endif
}
static void retroarch_deinit_drivers(
struct rarch_state *p_rarch, struct retro_callbacks *cbs)
{
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
#if defined(HAVE_GFX_WIDGETS)
/* Tear down display widgets no matter what
* in case the handle is lost in the threaded
* video driver in the meantime
* (breaking video_driver_has_widgets) */
if (p_rarch->dispwidget_st.widgets_inited)
{
gfx_widgets_deinit(&p_rarch->dispwidget_st,
p_rarch->widgets_persisting);
p_rarch->widgets_active = false;
}
#endif
#if defined(HAVE_CRTSWITCHRES)
/* Switchres deinit */
if (p_rarch->video_driver_crt_switching_active)
{
#if defined(DEBUG)
RARCH_LOG("[CRT]: Getting video info\n");
RARCH_LOG("[CRT]: About to destroy SR\n");
#endif
crt_destroy_modes(&p_rarch->crt_switch_st);
}
#endif
/* Video */
video_display_server_destroy();
p_rarch->video_driver_use_rgba = false;
p_rarch->video_driver_hdr_support = false;
p_rarch->video_driver_active = false;
p_rarch->video_driver_cache_context = false;
p_rarch->video_driver_cache_context_ack = false;
p_rarch->video_driver_record_gpu_buffer = NULL;
p_rarch->current_video = NULL;
video_driver_set_cached_frame_ptr(NULL);
/* Audio */
p_rarch->audio_driver_active = false;
p_rarch->current_audio = NULL;
/* Input */
p_rarch->input_driver_keyboard_linefeed_enable = false;
p_rarch->input_driver_block_hotkey = false;
p_rarch->input_driver_block_libretro_input = false;
if (input_driver_st)
input_driver_st->nonblocking_flag = false;
memset(&p_rarch->input_driver_turbo_btns, 0, sizeof(turbo_buttons_t));
memset(&p_rarch->input_driver_analog_requested, 0,
sizeof(p_rarch->input_driver_analog_requested));
p_rarch->input_driver_state.current_driver = NULL;
#ifdef HAVE_MENU
menu_driver_destroy(
menu_state_get_ptr());
p_rarch->menu_driver_alive = false;
#endif
p_rarch->location_driver_active = false;
destroy_location();
/* Camera */
p_rarch->camera_driver_active = false;
p_rarch->camera_driver = NULL;
p_rarch->camera_data = NULL;
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_DESTROY, NULL);
wifi_driver_ctl(RARCH_WIFI_CTL_DESTROY, NULL);
cbs->frame_cb = retro_frame_null;
cbs->poll_cb = retro_input_poll_null;
cbs->sample_cb = NULL;
cbs->sample_batch_cb = NULL;
cbs->state_cb = NULL;
p_rarch->current_core.inited = false;
}
bool driver_ctl(enum driver_ctl_state state, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
driver_ctx_info_t *drv = (driver_ctx_info_t*)data;
switch (state)
{
case RARCH_DRIVER_CTL_SET_REFRESH_RATE:
{
float *hz = (float*)data;
settings_t *settings = p_rarch->configuration_settings;
unsigned audio_output_sample_rate = settings->uints.audio_output_sample_rate;
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
float video_refresh_rate = settings->floats.video_refresh_rate;
float audio_max_timing_skew = settings->floats.audio_max_timing_skew;
bool video_adaptive_vsync = settings->bools.video_adaptive_vsync;
unsigned video_swap_interval = settings->uints.video_swap_interval;
video_monitor_set_refresh_rate(*hz);
/* Sets audio monitor rate to new value. */
p_rarch->audio_source_ratio_original =
p_rarch->audio_source_ratio_current = (double)audio_output_sample_rate
/ p_rarch->audio_driver_input;
driver_adjust_system_rates(p_rarch,
vrr_runloop_enable,
video_refresh_rate,
audio_max_timing_skew,
video_adaptive_vsync,
video_swap_interval
);
}
break;
case RARCH_DRIVER_CTL_FIND_FIRST:
if (!drv)
return false;
find_driver_nonempty(drv->label, 0, drv->s, drv->len);
break;
case RARCH_DRIVER_CTL_FIND_LAST:
if (!drv)
return false;
driver_find_last(drv->label, drv->s, drv->len);
break;
case RARCH_DRIVER_CTL_FIND_PREV:
if (!drv)
return false;
return driver_find_prev(drv->label, drv->s, drv->len);
case RARCH_DRIVER_CTL_FIND_NEXT:
if (!drv)
return false;
return driver_find_next(drv->label, drv->s, drv->len);
case RARCH_DRIVER_CTL_NONE:
default:
break;
}
return true;
}
/* RUNAHEAD */
#ifdef HAVE_RUNAHEAD
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(256, sizeof(int16_t));
element->state_size = 256;
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(
struct rarch_state *p_rarch,
unsigned port, unsigned device,
unsigned index, unsigned id, int16_t value)
{
unsigned i;
input_list_element *element = NULL;
if (!p_rarch->input_state_list)
mylist_create(&p_rarch->input_state_list, 16,
input_list_element_constructor,
input_list_element_destructor);
/* Find list item */
for (i = 0; i < (unsigned)p_rarch->input_state_list->size; i++)
{
element = (input_list_element*)p_rarch->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 (p_rarch->input_state_list)
element = (input_list_element*)
mylist_add_element(p_rarch->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_get_last(unsigned port,
unsigned device, unsigned index, unsigned id)
{
unsigned i;
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->input_state_list)
return 0;
/* find list item */
for (i = 0; i < (unsigned)p_rarch->input_state_list->size; i++)
{
input_list_element *element =
(input_list_element*)p_rarch->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 int16_t input_state_with_logging(unsigned port,
unsigned device, unsigned index, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->input_state_callback_original)
{
int16_t result = p_rarch->input_state_callback_original(
port, device, index, id);
int16_t last_input =
input_state_get_last(port, device, index, id);
if (result != last_input)
p_rarch->input_is_dirty = true;
/*arbitrary limit of up to 65536 elements in state array*/
if (id < 65536)
input_state_set_last(p_rarch, port, device, index, id, result);
return result;
}
return 0;
}
static void reset_hook(void)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_is_dirty = true;
if (p_rarch->retro_reset_callback_original)
p_rarch->retro_reset_callback_original();
}
static bool unserialize_hook(const void *buf, size_t size)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->input_is_dirty = true;
if (p_rarch->retro_unserialize_callback_original)
return p_rarch->retro_unserialize_callback_original(buf, size);
return false;
}
static void add_input_state_hook(struct rarch_state *p_rarch)
{
struct retro_callbacks *cbs = &p_rarch->retro_ctx;
if (!p_rarch->input_state_callback_original)
{
p_rarch->input_state_callback_original = cbs->state_cb;
cbs->state_cb = input_state_with_logging;
p_rarch->current_core.retro_set_input_state(cbs->state_cb);
}
if (!p_rarch->retro_reset_callback_original)
{
p_rarch->retro_reset_callback_original = p_rarch->current_core.retro_reset;
p_rarch->current_core.retro_reset = reset_hook;
}
if (!p_rarch->retro_unserialize_callback_original)
{
p_rarch->retro_unserialize_callback_original = p_rarch->current_core.retro_unserialize;
p_rarch->current_core.retro_unserialize = unserialize_hook;
}
}
static void remove_input_state_hook(struct rarch_state *p_rarch)
{
struct retro_callbacks *cbs = &p_rarch->retro_ctx;
if (p_rarch->input_state_callback_original)
{
cbs->state_cb = p_rarch->input_state_callback_original;
p_rarch->current_core.retro_set_input_state(cbs->state_cb);
p_rarch->input_state_callback_original = NULL;
mylist_destroy(&p_rarch->input_state_list);
}
if (p_rarch->retro_reset_callback_original)
{
p_rarch->current_core.retro_reset =
p_rarch->retro_reset_callback_original;
p_rarch->retro_reset_callback_original = NULL;
}
if (p_rarch->retro_unserialize_callback_original)
{
p_rarch->current_core.retro_unserialize =
p_rarch->retro_unserialize_callback_original;
p_rarch->retro_unserialize_callback_original = NULL;
}
}
static void *runahead_save_state_alloc(void)
{
struct rarch_state *p_rarch = &rarch_st;
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 ( (p_rarch->runahead_save_state_size > 0) &&
p_rarch->runahead_save_state_size_known)
{
savestate->data = malloc(p_rarch->runahead_save_state_size);
savestate->data_const = savestate->data;
savestate->size = p_rarch->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(
struct rarch_state *p_rarch,
size_t save_state_size)
{
p_rarch->runahead_save_state_size = save_state_size;
p_rarch->runahead_save_state_size_known = true;
mylist_create(&p_rarch->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(struct rarch_state *p_rarch)
{
if (p_rarch->original_retro_deinit)
{
p_rarch->current_core.retro_deinit = p_rarch->original_retro_deinit;
p_rarch->original_retro_deinit = NULL;
}
if (p_rarch->original_retro_unload)
{
p_rarch->current_core.retro_unload_game = p_rarch->original_retro_unload;
p_rarch->original_retro_unload = NULL;
}
remove_input_state_hook(p_rarch);
}
static void runahead_destroy(struct rarch_state *p_rarch)
{
mylist_destroy(&p_rarch->runahead_save_state_list);
runahead_remove_hooks(p_rarch);
runahead_clear_variables(p_rarch);
}
static void unload_hook(void)
{
struct rarch_state *p_rarch = &rarch_st;
runahead_remove_hooks(p_rarch);
runahead_destroy(p_rarch);
secondary_core_destroy(p_rarch);
if (p_rarch->current_core.retro_unload_game)
p_rarch->current_core.retro_unload_game();
p_rarch->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
}
static void runahead_deinit_hook(void)
{
struct rarch_state *p_rarch = &rarch_st;
runahead_remove_hooks(p_rarch);
runahead_destroy(p_rarch);
secondary_core_destroy(p_rarch);
if (p_rarch->current_core.retro_deinit)
p_rarch->current_core.retro_deinit();
}
static void runahead_add_hooks(struct rarch_state *p_rarch)
{
if (!p_rarch->original_retro_deinit)
{
p_rarch->original_retro_deinit = p_rarch->current_core.retro_deinit;
p_rarch->current_core.retro_deinit = runahead_deinit_hook;
}
if (!p_rarch->original_retro_unload)
{
p_rarch->original_retro_unload = p_rarch->current_core.retro_unload_game;
p_rarch->current_core.retro_unload_game = unload_hook;
}
add_input_state_hook(p_rarch);
}
/* Runahead Code */
static void runahead_error(struct rarch_state *p_rarch)
{
p_rarch->runahead_available = false;
mylist_destroy(&p_rarch->runahead_save_state_list);
runahead_remove_hooks(p_rarch);
p_rarch->runahead_save_state_size = 0;
p_rarch->runahead_save_state_size_known = true;
}
static bool runahead_create(struct rarch_state *p_rarch)
{
/* get savestate size and allocate buffer */
retro_ctx_size_info_t info;
p_rarch->request_fast_savestate = true;
core_serialize_size(&info);
p_rarch->request_fast_savestate = false;
runahead_save_state_list_init(p_rarch, info.size);
p_rarch->runahead_video_driver_is_active =
p_rarch->video_driver_active;
if ( (p_rarch->runahead_save_state_size == 0) ||
!p_rarch->runahead_save_state_size_known)
{
runahead_error(p_rarch);
return false;
}
runahead_add_hooks(p_rarch);
p_rarch->runahead_force_input_dirty = true;
if (p_rarch->runahead_save_state_list)
mylist_resize(p_rarch->runahead_save_state_list, 1, true);
return true;
}
static bool runahead_save_state(struct rarch_state *p_rarch)
{
retro_ctx_serialize_info_t *serialize_info;
bool okay = false;
if (!p_rarch->runahead_save_state_list)
return false;
serialize_info =
(retro_ctx_serialize_info_t*)p_rarch->runahead_save_state_list->data[0];
p_rarch->request_fast_savestate = true;
okay = core_serialize(serialize_info);
p_rarch->request_fast_savestate = false;
if (okay)
return true;
runahead_error(p_rarch);
return false;
}
static bool runahead_load_state(struct rarch_state *p_rarch)
{
bool okay = false;
retro_ctx_serialize_info_t *serialize_info = (retro_ctx_serialize_info_t*)
p_rarch->runahead_save_state_list->data[0];
bool last_dirty = p_rarch->input_is_dirty;
p_rarch->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 = p_rarch->current_core.retro_unserialize(
serialize_info->data_const, serialize_info->size);
p_rarch->request_fast_savestate = false;
p_rarch->input_is_dirty = last_dirty;
if (!okay)
runahead_error(p_rarch);
return okay;
}
#if HAVE_DYNAMIC
static bool runahead_load_state_secondary(struct rarch_state *p_rarch)
{
bool okay = false;
retro_ctx_serialize_info_t *serialize_info =
(retro_ctx_serialize_info_t*)p_rarch->runahead_save_state_list->data[0];
p_rarch->request_fast_savestate = true;
okay = secondary_core_deserialize(
p_rarch, p_rarch->configuration_settings,
serialize_info->data_const, (int)serialize_info->size);
p_rarch->request_fast_savestate = false;
if (!okay)
{
p_rarch->runahead_secondary_core_available = false;
runahead_error(p_rarch);
return false;
}
return true;
}
#endif
static bool runahead_core_run_use_last_input(struct rarch_state *p_rarch)
{
struct retro_callbacks *cbs = &p_rarch->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;
p_rarch->current_core.retro_set_input_poll(cbs->poll_cb);
p_rarch->current_core.retro_set_input_state(cbs->state_cb);
p_rarch->current_core.retro_run();
cbs->poll_cb = old_poll_function;
cbs->state_cb = old_input_function;
p_rarch->current_core.retro_set_input_poll(cbs->poll_cb);
p_rarch->current_core.retro_set_input_state(cbs->state_cb);
return true;
}
static void do_runahead(
struct rarch_state *p_rarch,
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
uint64_t frame_count = p_rarch->video_driver_frame_count;
if (runahead_count <= 0 || !p_rarch->runahead_available)
goto force_input_dirty;
if (!p_rarch->runahead_save_state_size_known)
{
if (!runahead_create(p_rarch))
{
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);
goto force_input_dirty;
}
}
/* Check for GUI */
/* Hack: If we were in the GUI, force a resync. */
if (frame_count != p_rarch->runahead_last_frame_count + 1)
p_rarch->runahead_force_input_dirty = true;
p_rarch->runahead_last_frame_count = frame_count;
if ( !use_secondary
|| !have_dynamic
|| !p_rarch->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)
{
p_rarch->audio_suspended = true;
p_rarch->video_driver_active = false;
}
if (frame_number == 0)
core_run();
else
runahead_core_run_use_last_input(p_rarch);
if (suspended_frame)
{
RUNAHEAD_RESUME_VIDEO(p_rarch);
p_rarch->audio_suspended = false;
}
if (frame_number == 0)
{
if (!runahead_save_state(p_rarch))
{
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);
return;
}
}
if (last_frame)
{
if (!runahead_load_state(p_rarch))
{
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);
return;
}
}
}
}
else
{
#if HAVE_DYNAMIC
if (!secondary_core_ensure_exists(p_rarch, p_rarch->configuration_settings))
{
secondary_core_destroy(p_rarch);
p_rarch->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);
goto force_input_dirty;
}
/* run main core with video suspended */
p_rarch->video_driver_active = false;
core_run();
RUNAHEAD_RESUME_VIDEO(p_rarch);
if ( p_rarch->input_is_dirty
|| p_rarch->runahead_force_input_dirty)
{
p_rarch->input_is_dirty = false;
if (!runahead_save_state(p_rarch))
{
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);
return;
}
if (!runahead_load_state_secondary(p_rarch))
{
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);
return;
}
for (frame_number = 0; frame_number < runahead_count - 1; frame_number++)
{
p_rarch->video_driver_active = false;
p_rarch->audio_suspended = true;
p_rarch->hard_disable_audio = true;
RUNAHEAD_RUN_SECONDARY(p_rarch);
p_rarch->hard_disable_audio = false;
p_rarch->audio_suspended = false;
RUNAHEAD_RESUME_VIDEO(p_rarch);
}
}
p_rarch->audio_suspended = true;
p_rarch->hard_disable_audio = true;
RUNAHEAD_RUN_SECONDARY(p_rarch);
p_rarch->hard_disable_audio = false;
p_rarch->audio_suspended = false;
#endif
}
p_rarch->runahead_force_input_dirty = false;
return;
force_input_dirty:
core_run();
p_rarch->runahead_force_input_dirty = true;
}
#endif
static retro_time_t rarch_core_runtime_tick(
struct rarch_state *p_rarch,
float slowmotion_ratio,
retro_time_t current_time)
{
retro_time_t frame_time =
(1.0 / p_rarch->video_driver_av_info.timing.fps) * 1000000;
bool runloop_slowmotion = runloop_state.slowmotion;
bool runloop_fastmotion = runloop_state.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();
* libretro_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 -
p_rarch->libretro_core_runtime_last;
p_rarch->libretro_core_runtime_last = current_usec;
if (potential_frame_time < frame_time)
return potential_frame_time;
}
return frame_time;
}
static void retroarch_print_features(void)
{
char buf[2048];
buf[0] = '\0';
frontend_driver_attach_console();
strlcpy(buf, "\nFeatures:\n", sizeof(buf));
_PSUPP_BUF(buf, SUPPORTS_LIBRETRODB, "LibretroDB", "LibretroDB support");
_PSUPP_BUF(buf, SUPPORTS_COMMAND, "Command", "Command interface support");
_PSUPP_BUF(buf, SUPPORTS_NETWORK_COMMAND, "Network Command", "Network Command interface "
"support");
_PSUPP_BUF(buf, SUPPORTS_SDL, "SDL", "SDL input/audio/video drivers");
_PSUPP_BUF(buf, SUPPORTS_SDL2, "SDL2", "SDL2 input/audio/video drivers");
_PSUPP_BUF(buf, SUPPORTS_X11, "X11", "X11 input/video drivers");
_PSUPP_BUF(buf, SUPPORTS_WAYLAND, "wayland", "Wayland input/video drivers");
_PSUPP_BUF(buf, SUPPORTS_THREAD, "Threads", "Threading support");
_PSUPP_BUF(buf, SUPPORTS_VULKAN, "Vulkan", "Vulkan video driver");
_PSUPP_BUF(buf, SUPPORTS_METAL, "Metal", "Metal video driver");
_PSUPP_BUF(buf, SUPPORTS_OPENGL, "OpenGL", "OpenGL video driver support");
_PSUPP_BUF(buf, SUPPORTS_OPENGLES, "OpenGL ES", "OpenGLES video driver support");
_PSUPP_BUF(buf, SUPPORTS_XVIDEO, "XVideo", "Video driver");
_PSUPP_BUF(buf, SUPPORTS_UDEV, "UDEV", "UDEV/EVDEV input driver support");
_PSUPP_BUF(buf, SUPPORTS_EGL, "EGL", "Video context driver");
_PSUPP_BUF(buf, SUPPORTS_KMS, "KMS", "Video context driver");
_PSUPP_BUF(buf, SUPPORTS_VG, "OpenVG", "Video context driver");
_PSUPP_BUF(buf, SUPPORTS_COREAUDIO, "CoreAudio", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_COREAUDIO3, "CoreAudioV3", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_ALSA, "ALSA", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_OSS, "OSS", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_JACK, "Jack", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_RSOUND, "RSound", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_ROAR, "RoarAudio", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_PULSE, "PulseAudio", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_DSOUND, "DirectSound", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_WASAPI, "WASAPI", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_XAUDIO, "XAudio2", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_AL, "OpenAL", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_SL, "OpenSL", "Audio driver");
_PSUPP_BUF(buf, SUPPORTS_7ZIP, "7zip", "7zip extraction support");
_PSUPP_BUF(buf, SUPPORTS_ZLIB, "zlib", ".zip extraction support");
_PSUPP_BUF(buf, SUPPORTS_DYLIB, "External", "External filter and plugin support");
_PSUPP_BUF(buf, SUPPORTS_CG, "Cg", "Fragment/vertex shader driver");
_PSUPP_BUF(buf, SUPPORTS_GLSL, "GLSL", "Fragment/vertex shader driver");
_PSUPP_BUF(buf, SUPPORTS_HLSL, "HLSL", "Fragment/vertex shader driver");
_PSUPP_BUF(buf, SUPPORTS_SDL_IMAGE, "SDL_image", "SDL_image image loading");
_PSUPP_BUF(buf, SUPPORTS_RPNG, "rpng", "PNG image loading/encoding");
_PSUPP_BUF(buf, SUPPORTS_RJPEG, "rjpeg", "JPEG image loading");
_PSUPP_BUF(buf, SUPPORTS_DYNAMIC, "Dynamic", "Dynamic run-time loading of "
"libretro library");
_PSUPP_BUF(buf, SUPPORTS_FFMPEG, "FFmpeg", "On-the-fly recording of gameplay "
"with libavcodec");
_PSUPP_BUF(buf, SUPPORTS_FREETYPE, "FreeType", "TTF font rendering driver");
_PSUPP_BUF(buf, SUPPORTS_CORETEXT, "CoreText", "TTF font rendering driver ");
_PSUPP_BUF(buf, SUPPORTS_NETPLAY, "Netplay", "Peer-to-peer netplay");
_PSUPP_BUF(buf, SUPPORTS_PYTHON, "Python", "Script support in shaders");
_PSUPP_BUF(buf, SUPPORTS_LIBUSB, "Libusb", "Libusb support");
_PSUPP_BUF(buf, SUPPORTS_COCOA, "Cocoa", "Cocoa UI companion support "
"(for OSX and/or iOS)");
_PSUPP_BUF(buf, SUPPORTS_QT, "Qt", "Qt UI companion support");
_PSUPP_BUF(buf, SUPPORTS_V4L2, "Video4Linux2", "Camera driver");
puts(buf);
}
static void retroarch_print_version(void)
{
char str[255];
frontend_driver_attach_console();
str[0] = '\0';
fprintf(stderr, "%s: %s -- v%s",
msg_hash_to_str(MSG_PROGRAM),
msg_hash_to_str(MSG_LIBRETRO_FRONTEND),
PACKAGE_VERSION);
#ifdef HAVE_GIT_VERSION
printf(" -- %s --\n", retroarch_git_version);
#else
printf("\n");
#endif
retroarch_get_capabilities(RARCH_CAPABILITIES_COMPILER, str, sizeof(str));
strlcat(str, " Built: " __DATE__, sizeof(str));
fprintf(stdout, "%s\n", str);
}
/**
* retroarch_print_help:
*
* Prints help message explaining the program's commandline switches.
**/
static void retroarch_print_help(const char *arg0)
{
frontend_driver_attach_console();
puts("===================================================================");
retroarch_print_version();
puts("===================================================================");
printf("Usage: %s [OPTIONS]... [FILE]\n", arg0);
{
char buf[2148];
buf[0] = '\0';
strlcpy(buf, " -h, --help Show this help message.\n", sizeof(buf));
strlcat(buf, " -v, --verbose Verbose logging.\n", sizeof(buf));
strlcat(buf, " --log-file FILE Log messages to FILE.\n", sizeof(buf));
strlcat(buf, " --version Show version.\n", sizeof(buf));
strlcat(buf, " --features Prints available features compiled into "
"program.\n", sizeof(buf));
#ifdef HAVE_MENU
strlcat(buf, " --menu Do not require content or libretro core to "
"be loaded,\n"
" starts directly in menu. If no arguments "
"are passed to\n"
" the program, it is equivalent to using "
"--menu as only argument.\n", sizeof(buf));
#endif
strlcat(buf, " -s, --save=PATH Path for save files (*.srm). (DEPRECATED, use --appendconfig and savefile_directory)\n", sizeof(buf));
strlcat(buf, " -S, --savestate=PATH Path for the save state files (*.state). (DEPRECATED, use --appendconfig and savestate_directory)\n", sizeof(buf));
strlcat(buf, " --set-shader PATH Path to a shader (preset) that will be loaded each time content is loaded.\n"
" Effectively overrides automatic shader presets.\n"
" An empty argument \"\" will disable automatic shader presets.\n", sizeof(buf));
strlcat(buf, " -f, --fullscreen Start the program in fullscreen regardless "
"of config settings.\n", sizeof(buf));
#ifdef HAVE_CONFIGFILE
#ifdef _WIN32
strlcat(buf, " -c, --config=FILE Path for config file."
"\n\t\tDefaults to retroarch.cfg in same directory as retroarch.exe."
"\n\t\tIf a default config is not found, the program will attempt to "
"create one.\n"
, sizeof(buf));
#else
strlcat(buf, " -c, --config=FILE Path for config file."
"\n\t\tBy default looks for config in $XDG_CONFIG_HOME/retroarch/"
"retroarch.cfg,\n\t\t$HOME/.config/retroarch/retroarch.cfg,\n\t\t"
"and $HOME/.retroarch.cfg.\n\t\tIf a default config is not found, "
"the program will attempt to create one based on the \n\t\t"
"skeleton config (" GLOBAL_CONFIG_DIR "/retroarch.cfg). \n"
, sizeof(buf));
#endif
#endif
strlcat(buf, " --appendconfig=FILE\n"
" Extra config files are loaded in, "
"and take priority over\n"
" config selected in -c (or default). "
"Multiple configs are\n"
" delimited by '|'.\n", sizeof(buf));
#ifdef HAVE_DYNAMIC
strlcat(buf, " -L, --libretro=FILE Path to libretro implementation. "
"Overrides any config setting.\n", sizeof(buf));
#endif
strlcat(buf, " --subsystem=NAME Use a subsystem of the libretro core. "
"Multiple content\n"
" files are loaded as multiple arguments. "
"If a content\n"
" file is skipped, use a blank (\"\") "
"command line argument.\n"
" Content must be loaded in an order "
"which depends on the\n"
" particular subsystem used. See verbose "
"log output to learn\n"
" how a particular subsystem wants content "
"to be loaded.\n", sizeof(buf));
puts(buf);
}
printf(" -N, --nodevice=PORT\n"
" Disconnects controller device connected "
"to PORT (1 to %d).\n", MAX_USERS);
printf(" -A, --dualanalog=PORT\n"
" Connect a DualAnalog controller to PORT "
"(1 to %d).\n", MAX_USERS);
printf(" -d, --device=PORT:ID\n"
" Connect a generic device into PORT of "
"the device (1 to %d).\n", MAX_USERS);
{
char buf[2560];
buf[0] = '\0';
strlcpy(buf, " Format is PORT:ID, where ID is a number "
"corresponding to the particular device.\n", sizeof(buf));
#ifdef HAVE_BSV_MOVIE
strlcat(buf, " -P, --bsvplay=FILE Playback a BSV movie file.\n", sizeof(buf));
strlcat(buf, " -R, --bsvrecord=FILE Start recording a BSV movie file from "
"the beginning.\n", sizeof(buf));
strlcat(buf, " --eof-exit Exit upon reaching the end of the "
"BSV movie file.\n", sizeof(buf));
#endif
strlcat(buf, " -M, --sram-mode=MODE SRAM handling mode. MODE can be "
"'noload-nosave',\n"
" 'noload-save', 'load-nosave' or "
"'load-save'.\n"
" Note: 'noload-save' implies that "
"save files *WILL BE OVERWRITTEN*.\n", sizeof(buf));
#ifdef HAVE_NETWORKING
strlcat(buf, " -H, --host Host netplay as user 1.\n", sizeof(buf));
strlcat(buf, " -C, --connect=HOST Connect to netplay server as user 2.\n", sizeof(buf));
strlcat(buf, " --port=PORT Port used to netplay. Default is 55435.\n", sizeof(buf));
strlcat(buf, " --stateless Use \"stateless\" mode for netplay\n", sizeof(buf));
strlcat(buf, " (requires a very fast network).\n", sizeof(buf));
strlcat(buf, " --check-frames=NUMBER\n"
" Check frames when using netplay.\n", sizeof(buf));
#ifdef HAVE_NETWORK_CMD
strlcat(buf, " --command Sends a command over UDP to an already "
"running program process.\n", sizeof(buf));
strlcat(buf, " Available commands are listed if command is invalid.\n", sizeof(buf));
#endif
#endif
strlcat(buf, " --nick=NICK Picks a username (for use with netplay). "
"Not mandatory.\n", sizeof(buf));
strlcat(buf, " -r, --record=FILE Path to record video file.\n "
"Using .mkv extension is recommended.\n", sizeof(buf));
strlcat(buf, " --recordconfig Path to settings used during recording.\n", sizeof(buf));
strlcat(buf, " --size=WIDTHxHEIGHT\n"
" Overrides output video size when recording.\n", sizeof(buf));
#ifdef HAVE_PATCH
strlcat(buf, " -U, --ups=FILE Specifies path for UPS patch that will be "
"applied to content.\n", sizeof(buf));
strlcat(buf, " --bps=FILE Specifies path for BPS patch that will be "
"applied to content.\n", sizeof(buf));
strlcat(buf, " --ips=FILE Specifies path for IPS patch that will be "
"applied to content.\n", sizeof(buf));
strlcat(buf, " --no-patch Disables all forms of content patching.\n", sizeof(buf));
#endif
strlcat(buf, " -D, --detach Detach program from the running console. "
"Not relevant for all platforms.\n", sizeof(buf));
strlcat(buf, " --max-frames=NUMBER\n"
" Runs for the specified number of frames, "
"then exits.\n", sizeof(buf));
#ifdef HAVE_SCREENSHOTS
strlcat(buf, " --max-frames-ss\n"
" Takes a screenshot at the end of max-frames.\n", sizeof(buf));
strlcat(buf, " --max-frames-ss-path=FILE\n"
" Path to save the screenshot to at the end of max-frames.\n", sizeof(buf));
#endif
#ifdef HAVE_ACCESSIBILITY
strlcat(buf, " --accessibility\n"
" Enables accessibilty for blind users using text-to-speech.\n", sizeof(buf));
#endif
strlcat(buf, " --load-menu-on-error\n"
" Open menu instead of quitting if specified core or content fails to load.\n", sizeof(buf));
puts(buf);
}
}
/**
* retroarch_parse_input_and_config:
* @argc : Count of (commandline) arguments.
* @argv : (Commandline) arguments.
*
* Parses (commandline) arguments passed to program and loads the config file,
* with command line options overriding the config file.
*
**/
static bool retroarch_parse_input_and_config(
struct rarch_state *p_rarch,
global_t *global,
int argc, char *argv[])
{
unsigned i;
static bool first_run = true;
bool verbosity_enabled = false;
const char *optstring = NULL;
bool explicit_menu = false;
bool cli_active = false;
bool cli_core_set = false;
bool cli_content_set = false;
const struct option opts[] = {
#ifdef HAVE_DYNAMIC
{ "libretro", 1, NULL, 'L' },
#endif
{ "menu", 0, NULL, RA_OPT_MENU },
{ "help", 0, NULL, 'h' },
{ "save", 1, NULL, 's' },
{ "fullscreen", 0, NULL, 'f' },
{ "record", 1, NULL, 'r' },
{ "recordconfig", 1, NULL, RA_OPT_RECORDCONFIG },
{ "size", 1, NULL, RA_OPT_SIZE },
{ "verbose", 0, NULL, 'v' },
#ifdef HAVE_CONFIGFILE
{ "config", 1, NULL, 'c' },
{ "appendconfig", 1, NULL, RA_OPT_APPENDCONFIG },
#endif
{ "nodevice", 1, NULL, 'N' },
{ "dualanalog", 1, NULL, 'A' },
{ "device", 1, NULL, 'd' },
{ "savestate", 1, NULL, 'S' },
{ "set-shader", 1, NULL, RA_OPT_SET_SHADER },
#ifdef HAVE_BSV_MOVIE
{ "bsvplay", 1, NULL, 'P' },
{ "bsvrecord", 1, NULL, 'R' },
#endif
{ "sram-mode", 1, NULL, 'M' },
#ifdef HAVE_NETWORKING
{ "host", 0, NULL, 'H' },
{ "connect", 1, NULL, 'C' },
{ "stateless", 0, NULL, RA_OPT_STATELESS },
{ "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES },
{ "port", 1, NULL, RA_OPT_PORT },
#ifdef HAVE_NETWORK_CMD
{ "command", 1, NULL, RA_OPT_COMMAND },
#endif
#endif
{ "nick", 1, NULL, RA_OPT_NICK },
#ifdef HAVE_PATCH
{ "ups", 1, NULL, 'U' },
{ "bps", 1, NULL, RA_OPT_BPS },
{ "ips", 1, NULL, RA_OPT_IPS },
{ "no-patch", 0, NULL, RA_OPT_NO_PATCH },
#endif
{ "detach", 0, NULL, 'D' },
{ "features", 0, NULL, RA_OPT_FEATURES },
{ "subsystem", 1, NULL, RA_OPT_SUBSYSTEM },
{ "max-frames", 1, NULL, RA_OPT_MAX_FRAMES },
{ "max-frames-ss", 0, NULL, RA_OPT_MAX_FRAMES_SCREENSHOT },
{ "max-frames-ss-path", 1, NULL, RA_OPT_MAX_FRAMES_SCREENSHOT_PATH },
{ "eof-exit", 0, NULL, RA_OPT_EOF_EXIT },
{ "version", 0, NULL, RA_OPT_VERSION },
{ "log-file", 1, NULL, RA_OPT_LOG_FILE },
{ "accessibility", 0, NULL, RA_OPT_ACCESSIBILITY},
{ "load-menu-on-error", 0, NULL, RA_OPT_LOAD_MENU_ON_ERROR },
{ NULL, 0, NULL, 0 }
};
if (first_run)
{
/* Copy the args into a buffer so launch arguments can be reused */
for (i = 0; i < (unsigned)argc; i++)
{
strlcat(p_rarch->launch_arguments,
argv[i], sizeof(p_rarch->launch_arguments));
strlcat(p_rarch->launch_arguments, " ",
sizeof(p_rarch->launch_arguments));
}
string_trim_whitespace_left(p_rarch->launch_arguments);
string_trim_whitespace_right(p_rarch->launch_arguments);
first_run = false;
/* Command line interface is only considered
* to be 'active' (i.e. used by a third party)
* if this is the first run (subsequent runs
* are triggered by RetroArch itself) */
cli_active = true;
}
/* Handling the core type is finicky. Based on the arguments we pass in,
* we handle it differently.
* Some current cases which track desired behavior and how it is supposed to work:
*
* Dynamically linked RA:
* ./retroarch -> CORE_TYPE_DUMMY
* ./retroarch -v -> CORE_TYPE_DUMMY + verbose
* ./retroarch --menu -> CORE_TYPE_DUMMY
* ./retroarch --menu -v -> CORE_TYPE_DUMMY + verbose
* ./retroarch -L contentless-core -> CORE_TYPE_PLAIN
* ./retroarch -L content-core -> CORE_TYPE_PLAIN + FAIL (This currently crashes)
* ./retroarch [-L content-core] ROM -> CORE_TYPE_PLAIN
* ./retroarch <-L or ROM> --menu -> FAIL
*
* The heuristic here seems to be that if we use the -L CLI option or
* optind < argc at the end we should set CORE_TYPE_PLAIN.
* To handle --menu, we should ensure that CORE_TYPE_DUMMY is still set
* otherwise, fail early, since the CLI options are non-sensical.
* We could also simply ignore --menu in this case to be more friendly with
* bogus arguments.
*/
if (!p_rarch->has_set_core)
retroarch_set_current_core_type(CORE_TYPE_DUMMY, false);
path_clear(RARCH_PATH_SUBSYSTEM);
retroarch_override_setting_free_state();
p_rarch->has_set_username = false;
#ifdef HAVE_PATCH
p_rarch->rarch_ups_pref = false;
p_rarch->rarch_ips_pref = false;
p_rarch->rarch_bps_pref = false;
*global->name.ups = '\0';
*global->name.bps = '\0';
*global->name.ips = '\0';
#endif
#ifdef HAVE_CONFIGFILE
runloop_state.overrides_active = false;
#endif
global->cli_load_menu_on_error = false;
/* Make sure we can call retroarch_parse_input several times ... */
optind = 0;
optstring = "hs:fvS:A:U:DN:d:"
BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
#if defined(ORBIS)
argv = &(argv[2]);
argc = argc - 2;
#elif defined(WEBOS)
argv = &(argv[1]);
argc = argc - 1;
#endif
#ifndef HAVE_MENU
if (argc == 1)
{
printf("%s\n", msg_hash_to_str(MSG_NO_ARGUMENTS_SUPPLIED_AND_NO_MENU_BUILTIN));
retroarch_print_help(argv[0]);
exit(0);
}
#endif
/* First pass: Read the config file path and any directory overrides, so
* they're in place when we load the config */
if (argc)
{
for (;;)
{
int c = getopt_long(argc, argv, optstring, opts, NULL);
#if 0
fprintf(stderr, "c is: %c (%d), optarg is: [%s]\n", c, c, string_is_empty(optarg) ? "" : optarg);
#endif
if (c == -1)
break;
switch (c)
{
case 'h':
retroarch_print_help(argv[0]);
exit(0);
#ifdef HAVE_CONFIGFILE
case 'c':
path_set(RARCH_PATH_CONFIG, optarg);
break;
case RA_OPT_APPENDCONFIG:
path_set(RARCH_PATH_CONFIG_APPEND, optarg);
break;
#endif
case 's':
strlcpy(global->name.savefile, optarg,
sizeof(global->name.savefile));
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL);
break;
case 'S':
strlcpy(global->name.savestate, optarg,
sizeof(global->name.savestate));
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_STATE_PATH, NULL);
break;
case 'v':
verbosity_enable();
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_VERBOSITY, NULL);
break;
case RA_OPT_LOG_FILE:
/* Enable 'log to file' */
configuration_set_bool(p_rarch->configuration_settings,
p_rarch->configuration_settings->bools.log_to_file, true);
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_LOG_TO_FILE, NULL);
/* Cache log file path override */
rarch_log_file_set_override(optarg);
break;
/* Must handle '?' otherwise you get an infinite loop */
case '?':
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
break;
/* All other arguments are handled in the second pass */
}
}
}
verbosity_enabled = verbosity_is_enabled();
/* Enable logging to file if verbosity and log-file arguments were passed.
* RARCH_OVERRIDE_SETTING_LOG_TO_FILE is set by the RA_OPT_LOG_FILE case above
* The parameters passed to rarch_log_file_init are hardcoded as the config
* has not yet been initialized at this point. */
if (verbosity_enabled && retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_LOG_TO_FILE, NULL))
rarch_log_file_init(true, false, NULL);
/* Flush out some states that could have been set
* by core environment variables. */
p_rarch->current_core.has_set_input_descriptors = false;
p_rarch->current_core.has_set_subsystems = false;
/* Load the config file now that we know what it is */
#ifdef HAVE_CONFIGFILE
if (!p_rarch->rarch_block_config_read)
#endif
{
/* If this is a static build, load salamander
* config file first (sets RARCH_PATH_CORE) */
#if !defined(HAVE_DYNAMIC)
config_load_file_salamander();
#endif
config_load(&p_rarch->g_extern);
}
verbosity_enabled = verbosity_is_enabled();
/* Init logging after config load only if not overridden by command line argument.
* This handles when logging is set in the config but not via the --log-file option. */
if (verbosity_enabled && !retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_LOG_TO_FILE, NULL))
rarch_log_file_init(
p_rarch->configuration_settings->bools.log_to_file,
p_rarch->configuration_settings->bools.log_to_file_timestamp,
p_rarch->configuration_settings->paths.log_dir);
/* Second pass: All other arguments override the config file */
optind = 1;
if (argc)
{
for (;;)
{
int c = getopt_long(argc, argv, optstring, opts, NULL);
if (c == -1)
break;
switch (c)
{
case 'd':
{
unsigned new_port;
unsigned id = 0;
struct string_list *list = string_split(optarg, ":");
int port = 0;
if (list && list->size == 2)
{
port = (int)strtol(list->elems[0].data, NULL, 0);
id = (unsigned)strtoul(list->elems[1].data, NULL, 0);
}
string_list_free(list);
if (port < 1 || port > MAX_USERS)
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_VALUE_CONNECT_DEVICE_FROM_A_VALID_PORT));
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
new_port = port -1;
input_config_set_device(new_port, id);
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &new_port);
}
break;
case 'A':
{
unsigned new_port;
int port = (int)strtol(optarg, NULL, 0);
if (port < 1 || port > MAX_USERS)
{
RARCH_ERR("Connect dualanalog to a valid port.\n");
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
new_port = port - 1;
input_config_set_device(new_port, RETRO_DEVICE_ANALOG);
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &new_port);
}
break;
case 'f':
p_rarch->rarch_force_fullscreen = true;
break;
case 'N':
{
unsigned new_port;
int port = (int)strtol(optarg, NULL, 0);
if (port < 1 || port > MAX_USERS)
{
RARCH_ERR("%s\n",
msg_hash_to_str(MSG_DISCONNECT_DEVICE_FROM_A_VALID_PORT));
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
new_port = port - 1;
input_config_set_device(port - 1, RETRO_DEVICE_NONE);
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &new_port);
}
break;
case 'r':
strlcpy(global->record.path, optarg,
sizeof(global->record.path));
if (p_rarch->recording_enable)
p_rarch->recording_enable = true;
break;
case RA_OPT_SET_SHADER:
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
/* disable auto-shaders */
if (string_is_empty(optarg))
{
p_rarch->cli_shader_disable = true;
break;
}
/* rebase on shader directory */
if (!path_is_absolute(optarg))
{
settings_t *settings = p_rarch->configuration_settings;
char *ref_path = settings->paths.directory_video_shader;
fill_pathname_join(p_rarch->cli_shader,
ref_path, optarg, sizeof(p_rarch->cli_shader));
break;
}
strlcpy(p_rarch->cli_shader, optarg, sizeof(p_rarch->cli_shader));
#endif
break;
#ifdef HAVE_DYNAMIC
case 'L':
{
int path_stats;
if (string_ends_with_size(optarg, "builtin",
strlen(optarg), STRLEN_CONST("builtin")))
{
RARCH_LOG("--libretro argument \"%s\" is a built-in core. Ignoring.\n",
optarg);
break;
}
path_stats = path_stat(optarg);
if ((path_stats & RETRO_VFS_STAT_IS_DIRECTORY) != 0)
{
settings_t *settings = p_rarch->configuration_settings;
path_clear(RARCH_PATH_CORE);
configuration_set_string(settings,
settings->paths.directory_libretro, optarg);
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY, NULL);
RARCH_WARN("Using old --libretro behavior. "
"Setting libretro_directory to \"%s\" instead.\n",
optarg);
}
else if ((path_stats & RETRO_VFS_STAT_IS_VALID) != 0)
{
path_set(RARCH_PATH_CORE, optarg);
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
/* We requested explicit core, so use PLAIN core type. */
retroarch_set_current_core_type(CORE_TYPE_PLAIN, false);
}
else
{
RARCH_WARN("--libretro argument \"%s\" is neither a file nor directory. Ignoring.\n",
optarg);
}
}
break;
#endif
case 'P':
#ifdef HAVE_BSV_MOVIE
strlcpy(p_rarch->bsv_movie_state.movie_start_path, optarg,
sizeof(p_rarch->bsv_movie_state.movie_start_path));
p_rarch->bsv_movie_state.movie_start_playback = true;
p_rarch->bsv_movie_state.movie_start_recording = false;
#endif
break;
case 'R':
#ifdef HAVE_BSV_MOVIE
strlcpy(p_rarch->bsv_movie_state.movie_start_path, optarg,
sizeof(p_rarch->bsv_movie_state.movie_start_path));
p_rarch->bsv_movie_state.movie_start_playback = false;
p_rarch->bsv_movie_state.movie_start_recording = true;
#endif
break;
case 'M':
if (string_is_equal(optarg, "noload-nosave"))
{
p_rarch->rarch_is_sram_load_disabled = true;
p_rarch->rarch_is_sram_save_disabled = true;
}
else if (string_is_equal(optarg, "noload-save"))
p_rarch->rarch_is_sram_load_disabled = true;
else if (string_is_equal(optarg, "load-nosave"))
p_rarch->rarch_is_sram_save_disabled = true;
else if (string_is_not_equal(optarg, "load-save"))
{
RARCH_ERR("Invalid argument in --sram-mode.\n");
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
break;
#ifdef HAVE_NETWORKING
case 'H':
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
break;
case 'C':
{
settings_t *settings = p_rarch->configuration_settings;
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS, NULL);
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
configuration_set_string(settings,
settings->paths.netplay_server, optarg);
}
break;
case RA_OPT_STATELESS:
{
settings_t *settings = p_rarch->configuration_settings;
configuration_set_bool(settings,
settings->bools.netplay_stateless_mode, true);
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE, NULL);
}
break;
case RA_OPT_CHECK_FRAMES:
{
settings_t *settings = p_rarch->configuration_settings;
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, NULL);
configuration_set_int(settings,
settings->ints.netplay_check_frames,
(int)strtoul(optarg, NULL, 0));
}
break;
case RA_OPT_PORT:
{
settings_t *settings = p_rarch->configuration_settings;
retroarch_override_setting_set(
RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, NULL);
configuration_set_uint(settings,
settings->uints.netplay_port,
(int)strtoul(optarg, NULL, 0));
}
break;
#ifdef HAVE_NETWORK_CMD
case RA_OPT_COMMAND:
#ifdef HAVE_COMMAND
if (command_network_send((const char*)optarg))
exit(0);
else
retroarch_fail(p_rarch, 1, "network_cmd_send()");
#endif
break;
#endif
#endif
case RA_OPT_BPS:
#ifdef HAVE_PATCH
strlcpy(global->name.bps, optarg,
sizeof(global->name.bps));
p_rarch->rarch_bps_pref = true;
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_BPS_PREF, NULL);
#endif
break;
case 'U':
#ifdef HAVE_PATCH
strlcpy(global->name.ups, optarg,
sizeof(global->name.ups));
p_rarch->rarch_ups_pref = true;
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_UPS_PREF, NULL);
#endif
break;
case RA_OPT_IPS:
#ifdef HAVE_PATCH
strlcpy(global->name.ips, optarg,
sizeof(global->name.ips));
p_rarch->rarch_ips_pref = true;
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_IPS_PREF, NULL);
#endif
break;
case RA_OPT_NO_PATCH:
#ifdef HAVE_PATCH
p_rarch->rarch_patch_blocked = true;
#endif
break;
case 'D':
frontend_driver_detach_console();
break;
case RA_OPT_MENU:
explicit_menu = true;
break;
case RA_OPT_NICK:
{
settings_t *settings = p_rarch->configuration_settings;
p_rarch->has_set_username = true;
configuration_set_string(settings,
settings->paths.username, optarg);
}
break;
case RA_OPT_SIZE:
if (sscanf(optarg, "%ux%u",
&p_rarch->recording_width,
&p_rarch->recording_height) != 2)
{
RARCH_ERR("Wrong format for --size.\n");
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
break;
case RA_OPT_RECORDCONFIG:
strlcpy(global->record.config, optarg,
sizeof(global->record.config));
break;
case RA_OPT_MAX_FRAMES:
runloop_state.max_frames = (unsigned)strtoul(optarg, NULL, 10);
break;
case RA_OPT_MAX_FRAMES_SCREENSHOT:
#ifdef HAVE_SCREENSHOTS
runloop_state.max_frames_screenshot = true;
#endif
break;
case RA_OPT_MAX_FRAMES_SCREENSHOT_PATH:
#ifdef HAVE_SCREENSHOTS
strlcpy(runloop_state.max_frames_screenshot_path,
optarg,
sizeof(runloop_state.max_frames_screenshot_path));
#endif
break;
case RA_OPT_SUBSYSTEM:
path_set(RARCH_PATH_SUBSYSTEM, optarg);
break;
case RA_OPT_FEATURES:
retroarch_print_features();
exit(0);
case RA_OPT_EOF_EXIT:
#ifdef HAVE_BSV_MOVIE
p_rarch->bsv_movie_state.eof_exit = true;
#endif
break;
case RA_OPT_VERSION:
retroarch_print_version();
exit(0);
case 'h':
#ifdef HAVE_CONFIGFILE
case 'c':
case RA_OPT_APPENDCONFIG:
#endif
case 's':
case 'S':
case 'v':
case RA_OPT_LOG_FILE:
break; /* Handled in the first pass */
case '?':
retroarch_print_help(argv[0]);
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
case RA_OPT_ACCESSIBILITY:
#ifdef HAVE_ACCESSIBILITY
p_rarch->accessibility_enabled = true;
#endif
break;
case RA_OPT_LOAD_MENU_ON_ERROR:
global->cli_load_menu_on_error = true;
break;
default:
RARCH_ERR("%s\n", msg_hash_to_str(MSG_ERROR_PARSING_ARGUMENTS));
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
}
}
#ifdef HAVE_GIT_VERSION
RARCH_LOG("RetroArch %s (Git %s)\n",
PACKAGE_VERSION, retroarch_git_version);
#endif
if (explicit_menu)
{
if (optind < argc)
{
RARCH_ERR("--menu was used, but content file was passed as well.\n");
retroarch_fail(p_rarch, 1, "retroarch_parse_input()");
}
#ifdef HAVE_DYNAMIC
else
{
/* Allow stray -L arguments to go through to workaround cases
* where it's used as "config file".
*
* This seems to still be the case for Android, which
* should be properly fixed. */
retroarch_set_current_core_type(CORE_TYPE_DUMMY, false);
}
#endif
}
if (optind < argc)
{
bool subsystem_path_is_empty = path_is_empty(RARCH_PATH_SUBSYSTEM);
/* We requested explicit ROM, so use PLAIN core type. */
retroarch_set_current_core_type(CORE_TYPE_PLAIN, false);
if (subsystem_path_is_empty)
path_set(RARCH_PATH_NAMES, (const char*)argv[optind]);
else
path_set_special(argv + optind, argc - optind);
/* Register that content has been set via the
* command line interface */
cli_content_set = true;
}
/* Check whether a core has been set via the
* command line interface */
cli_core_set = (p_rarch->current_core_type != CORE_TYPE_DUMMY);
/* Update global 'content launched from command
* line' status flag */
global->launched_from_cli = cli_active && (cli_core_set || cli_content_set);
/* Copy SRM/state dirs used, so they can be reused on reentrancy. */
if (retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL) &&
path_is_directory(global->name.savefile))
dir_set(RARCH_DIR_SAVEFILE, global->name.savefile);
if (retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_STATE_PATH, NULL) &&
path_is_directory(global->name.savestate))
dir_set(RARCH_DIR_SAVESTATE, global->name.savestate);
return verbosity_enabled;
}
static bool retroarch_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];
new_path[0] = '\0';
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 retroarch_validate_game_options(
const char *core_name,
char *s, size_t len, bool mkdir)
{
const char *game_name = path_basename(path_get(RARCH_PATH_BASENAME));
return retroarch_validate_per_core_options(s, len, mkdir,
core_name, game_name);
}
static bool retroarch_validate_folder_options(
char *s, size_t len, bool mkdir)
{
char folder_name[PATH_MAX_LENGTH];
const char *core_name = runloop_state.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 retroarch_validate_per_core_options(s, len, mkdir,
core_name, folder_name);
}
/* Validates CPU features for given processor architecture.
* Make sure we haven't compiled for something we cannot run.
* Ideally, code would get swapped out depending on CPU support,
* but this will do for now. */
static void retroarch_validate_cpu_features(struct rarch_state *p_rarch)
{
uint64_t cpu = cpu_features_get();
(void)cpu;
#ifdef __MMX__
if (!(cpu & RETRO_SIMD_MMX))
FAIL_CPU(p_rarch, "MMX");
#endif
#ifdef __SSE__
if (!(cpu & RETRO_SIMD_SSE))
FAIL_CPU(p_rarch, "SSE");
#endif
#ifdef __SSE2__
if (!(cpu & RETRO_SIMD_SSE2))
FAIL_CPU(p_rarch, "SSE2");
#endif
#ifdef __AVX__
if (!(cpu & RETRO_SIMD_AVX))
FAIL_CPU(p_rarch, "AVX");
#endif
}
#ifdef HAVE_MENU
static const menu_ctx_driver_t *menu_driver_find_driver(
settings_t *settings,
const char *prefix,
bool verbosity_enabled)
{
int i = (int)driver_find_index("menu_driver",
settings->arrays.menu_driver);
if (i >= 0)
return (const menu_ctx_driver_t*)menu_ctx_drivers[i];
if (verbosity_enabled)
{
unsigned d;
RARCH_WARN("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.menu_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; menu_ctx_drivers[d]; d++)
{
if (menu_ctx_drivers[d])
{
RARCH_LOG_OUTPUT("\t%s\n", menu_ctx_drivers[d]->ident);
}
}
RARCH_WARN("Going to default to first %s...\n", prefix);
}
return (const menu_ctx_driver_t*)menu_ctx_drivers[0];
}
#endif
/**
* retroarch_main_init:
* @argc : Count of (commandline) arguments.
* @argv : (Commandline) arguments.
*
* Initializes the program.
*
* Returns: true on success, otherwise false if there was an error.
**/
bool retroarch_main_init(int argc, char *argv[])
{
#if defined(DEBUG) && defined(HAVE_DRMINGW)
char log_file_name[128];
#endif
bool verbosity_enabled = false;
bool init_failed = false;
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
global_t *global = &p_rarch->g_extern;
#ifdef HAVE_ACCESSIBILITY
bool accessibility_enable = false;
unsigned accessibility_narrator_speech_speed = 0;
#endif
p_rarch->osk_idx = OSK_LOWERCASE_LATIN;
p_rarch->video_driver_active = true;
p_rarch->audio_driver_active = true;
if (setjmp(p_rarch->error_sjlj_context) > 0)
{
RARCH_ERR("%s: \"%s\"\n",
msg_hash_to_str(MSG_FATAL_ERROR_RECEIVED_IN), p_rarch->error_string);
goto error;
}
p_rarch->rarch_error_on_init = true;
/* Have to initialise non-file logging once at the start... */
retro_main_log_file_init(NULL, false);
verbosity_enabled = retroarch_parse_input_and_config(p_rarch, &p_rarch->g_extern, argc, argv);
#ifdef HAVE_ACCESSIBILITY
accessibility_enable = settings->bools.accessibility_enable;
accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
/* State that the narrator is on, and also include the first menu
item we're on at startup. */
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
"RetroArch accessibility on. Main Menu Load Core.",
10);
#endif
if (verbosity_enabled)
{
{
char str_output[256];
const char *cpu_model = NULL;
str_output[0] = '\0';
cpu_model = frontend_driver_get_cpu_model_name();
strlcpy(str_output,
"=== Build =======================================\n",
sizeof(str_output));
if (!string_is_empty(cpu_model))
{
strlcat(str_output, FILE_PATH_LOG_INFO " CPU Model Name: ", sizeof(str_output));
strlcat(str_output, cpu_model, sizeof(str_output));
strlcat(str_output, "\n", sizeof(str_output));
}
RARCH_LOG_OUTPUT(str_output);
}
{
char str_output[256];
char str[128];
str[0] = '\0';
retroarch_get_capabilities(RARCH_CAPABILITIES_CPU, str, sizeof(str));
#ifdef HAVE_GIT_VERSION
snprintf(str_output, sizeof(str_output),
"%s: %s" "\n" FILE_PATH_LOG_INFO " Built: " __DATE__ "\n" FILE_PATH_LOG_INFO " Version: " PACKAGE_VERSION "\n" FILE_PATH_LOG_INFO " Git: %s" "\n" FILE_PATH_LOG_INFO " =================================================\n",
msg_hash_to_str(MSG_CAPABILITIES),
str,
retroarch_git_version
);
#else
snprintf(str_output, sizeof(str_output),
"%s: %s" "\n" FILE_PATH_LOG_INFO " Built: " __DATE__ "\n" FILE_PATH_LOG_INFO " Version: " PACKAGE_VERSION "\n" FILE_PATH_LOG_INFO " =================================================\n",
msg_hash_to_str(MSG_CAPABILITIES),
str);
#endif
RARCH_LOG_OUTPUT(str_output);
}
}
#if defined(DEBUG) && defined(HAVE_DRMINGW)
RARCH_LOG_OUTPUT("Initializing Dr.MingW Exception handler\n");
fill_str_dated_filename(log_file_name, "crash",
"log", sizeof(log_file_name));
ExcHndlInit();
ExcHndlSetLogFileNameA(log_file_name);
#endif
retroarch_validate_cpu_features(p_rarch);
retroarch_init_task_queue();
{
const char *fullpath = path_get(RARCH_PATH_CONTENT);
if (!string_is_empty(fullpath))
{
enum rarch_content_type cont_type = path_is_media_type(fullpath);
#ifdef HAVE_IMAGEVIEWER
bool builtin_imageviewer = settings->bools.multimedia_builtin_imageviewer_enable;
#endif
bool builtin_mediaplayer = settings->bools.multimedia_builtin_mediaplayer_enable;
switch (cont_type)
{
case RARCH_CONTENT_MOVIE:
case RARCH_CONTENT_MUSIC:
if (builtin_mediaplayer)
{
/* TODO/FIXME - it needs to become possible to
* switch between FFmpeg and MPV at runtime */
#if defined(HAVE_MPV)
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
retroarch_set_current_core_type(CORE_TYPE_MPV, false);
#elif defined(HAVE_FFMPEG)
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
retroarch_set_current_core_type(CORE_TYPE_FFMPEG, false);
#endif
}
break;
#ifdef HAVE_IMAGEVIEWER
case RARCH_CONTENT_IMAGE:
if (builtin_imageviewer)
{
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
retroarch_set_current_core_type(CORE_TYPE_IMAGEVIEWER, false);
}
break;
#endif
#ifdef HAVE_GONG
case RARCH_CONTENT_GONG:
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
retroarch_set_current_core_type(CORE_TYPE_GONG, false);
break;
#endif
default:
break;
}
}
}
/* Pre-initialize all drivers
* Attempts to find a default driver for
* all driver types.
*/
if (!(audio_driver_find_driver(p_rarch, settings,
"audio driver", verbosity_enabled)))
retroarch_fail(p_rarch, 1, "audio_driver_find()");
video_driver_find_driver(p_rarch, settings,
"video driver", verbosity_enabled);
if (!input_driver_find_driver(
&p_rarch->input_driver_state, settings,
"input driver", verbosity_enabled))
retroarch_fail(p_rarch, 1, "find_input_driver()");
camera_driver_find_driver(p_rarch, settings,
"camera driver", verbosity_enabled);
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_FIND_DRIVER, NULL);
wifi_driver_ctl(RARCH_WIFI_CTL_FIND_DRIVER, NULL);
location_driver_find_driver(settings,
"location driver", verbosity_enabled);
#ifdef HAVE_MENU
{
struct menu_state *menu_st = menu_state_get_ptr();
if (!(menu_st->driver_ctx = menu_driver_find_driver(settings,
"menu driver", verbosity_enabled)))
retroarch_fail(p_rarch, 1, "menu_driver_find_driver()");
}
#endif
/* Enforce stored brightness if needed */
if (frontend_driver_can_set_screen_brightness())
frontend_driver_set_screen_brightness(settings->uints.screen_brightness);
/* Attempt to initialize core */
if (p_rarch->has_set_core)
{
p_rarch->has_set_core = false;
if (!command_event(CMD_EVENT_CORE_INIT,
&p_rarch->explicit_current_core_type))
init_failed = true;
}
else if (!command_event(CMD_EVENT_CORE_INIT,
&p_rarch->current_core_type))
init_failed = true;
/* Handle core initialization failure */
if (init_failed)
{
#ifdef HAVE_DYNAMIC
/* Check if menu was active prior to core initialization */
if ( !global->launched_from_cli
|| global->cli_load_menu_on_error
#ifdef HAVE_MENU
|| p_rarch->menu_driver_alive
#endif
)
#endif
{
/* Before initialising the dummy core, ensure
* that we:
* - Disable any active config overrides
* - Unload any active input remaps */
#ifdef HAVE_CONFIGFILE
if (runloop_state.overrides_active)
{
/* Reload the original config */
config_unload_override();
runloop_state.overrides_active = false;
}
#endif
if ( runloop_state.remaps_core_active
|| runloop_state.remaps_content_dir_active
|| runloop_state.remaps_game_active
)
{
input_remapping_deinit();
input_remapping_set_defaults(true);
}
else
input_remapping_restore_global_config(true);
/* Attempt initializing dummy core */
p_rarch->current_core_type = CORE_TYPE_DUMMY;
if (!command_event(CMD_EVENT_CORE_INIT, &p_rarch->current_core_type))
goto error;
}
#ifdef HAVE_DYNAMIC
else /* Fall back to regular error handling */
goto error;
#endif
}
#ifdef HAVE_CHEATS
cheat_manager_state_free();
command_event_init_cheats(
settings->bools.apply_cheats_after_load,
settings->paths.path_cheat_database,
#ifdef HAVE_BSV_MOVIE
p_rarch->bsv_movie_state_handle
#else
NULL
#endif
);
#endif
drivers_init(p_rarch, settings, DRIVERS_CMD_ALL, verbosity_enabled);
#ifdef HAVE_COMMAND
input_driver_deinit_command(p_rarch);
input_driver_init_command(p_rarch, settings);
#endif
#ifdef HAVE_NETWORKGAMEPAD
if (p_rarch->input_driver_remote)
input_remote_free(p_rarch->input_driver_remote,
settings->uints.input_max_users);
p_rarch->input_driver_remote = NULL;
if (settings->bools.network_remote_enable)
p_rarch->input_driver_remote = input_driver_init_remote(
settings,
settings->uints.input_max_users);
#endif
input_mapper_reset(&p_rarch->input_driver_mapper);
#ifdef HAVE_REWIND
command_event(CMD_EVENT_REWIND_INIT, NULL);
#endif
command_event(CMD_EVENT_CONTROLLER_INIT, NULL);
if (!string_is_empty(global->record.path))
command_event(CMD_EVENT_RECORD_INIT, NULL);
path_init_savefile(p_rarch);
command_event(CMD_EVENT_SET_PER_GAME_RESOLUTION, NULL);
p_rarch->rarch_error_on_init = false;
p_rarch->rarch_is_inited = true;
#ifdef HAVE_DISCORD
if (command_event(CMD_EVENT_DISCORD_INIT, NULL))
discord_is_inited = true;
if (discord_is_inited)
{
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_MENU;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
}
#endif
#if defined(HAVE_AUDIOMIXER)
audio_driver_load_system_sounds();
#endif
return true;
error:
command_event(CMD_EVENT_CORE_DEINIT, NULL);
p_rarch->rarch_is_inited = false;
return false;
}
#if 0
static bool retroarch_is_on_main_thread(shtread_tls_t *tls)
{
#ifdef HAVE_THREAD_STORAGE
return sthread_tls_get(tls) == MAGIC_POINTER;
#else
return true;
#endif
}
#endif
#ifdef HAVE_MENU
/* Gets called when we want to toggle the menu.
* If the menu is already running, it will be turned off.
* If the menu is off, then the menu will be started.
*/
static void menu_driver_toggle(
video_driver_t *current_video,
void *video_driver_data,
menu_handle_t *menu,
menu_input_t *menu_input,
settings_t *settings,
bool menu_driver_alive,
bool overlay_alive,
retro_keyboard_event_t *key_event,
retro_keyboard_event_t *frontend_key_event,
bool on)
{
/* TODO/FIXME - retroarch_main_quit calls menu_driver_toggle -
* we might have to redesign this to avoid EXXC_BAD_ACCESS errors
* on OSX - for now we work around this by checking if the settings
* struct is NULL
*/
bool pause_libretro = false;
#ifdef HAVE_AUDIOMIXER
bool audio_enable_menu = false;
#if 0
bool audio_enable_menu_bgm = false;
#endif
#endif
bool runloop_shutdown_initiated = runloop_state.shutdown_initiated;
#ifdef HAVE_OVERLAY
bool input_overlay_hide_in_menu = false;
bool input_overlay_enable = false;
#endif
bool video_adaptive_vsync = false;
bool video_swap_interval = false;
if (settings)
{
pause_libretro = settings->bools.menu_pause_libretro;
#ifdef HAVE_AUDIOMIXER
audio_enable_menu = settings->bools.audio_enable_menu;
#if 0
audio_enable_menu_bgm = settings->bools.audio_enable_menu_bgm ;
#endif
#endif
#ifdef HAVE_OVERLAY
input_overlay_hide_in_menu = settings->bools.input_overlay_hide_in_menu;
input_overlay_enable = settings->bools.input_overlay_enable;
#endif
video_adaptive_vsync = settings->bools.video_adaptive_vsync;
video_swap_interval = settings->uints.video_swap_interval;
}
if (on)
{
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
#endif
#ifdef HAVE_OVERLAY
/* If an overlay was displayed before the toggle
* and overlays are disabled in menu, need to
* inhibit 'select' input */
if (input_overlay_hide_in_menu)
{
if (input_overlay_enable && overlay_alive)
{
/* Inhibits pointer 'select' and 'cancel' actions
* (until the next time 'select'/'cancel' are released) */
menu_input->select_inhibit= true;
menu_input->cancel_inhibit= true;
}
}
#endif
}
else
{
#ifdef HAVE_LAKKA
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
#endif
#ifdef HAVE_OVERLAY
/* Inhibits pointer 'select' and 'cancel' actions
* (until the next time 'select'/'cancel' are released) */
menu_input->select_inhibit = false;
menu_input->cancel_inhibit = false;
#endif
}
if (menu_driver_alive)
{
bool refresh = false;
#ifdef WIIU
/* Enable burn-in protection menu is running */
IMEnableDim();
#endif
menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
/* Menu should always run with vsync on. */
if (current_video->set_nonblock_state)
current_video->set_nonblock_state(
video_driver_data,
false,
video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
video_adaptive_vsync,
video_swap_interval
);
/* Stop all rumbling before entering the menu. */
command_event(CMD_EVENT_RUMBLE_STOP, NULL);
if (pause_libretro && !audio_enable_menu)
command_event(CMD_EVENT_AUDIO_STOP, NULL);
#if 0
if (audio_enable_menu && audio_enable_menu_bgm)
audio_driver_mixer_play_menu_sound_looped(AUDIO_MIXER_SYSTEM_SLOT_BGM);
#endif
/* Override keyboard callback to redirect to menu instead.
* We'll use this later for something ... */
if (key_event && frontend_key_event)
{
*frontend_key_event = *key_event;
*key_event = menu_input_key_event;
runloop_state.frame_time_last= 0;
}
}
else
{
#ifdef WIIU
/* Disable burn-in protection while core is running; this is needed
* because HID inputs don't count for the purpose of Wii U
* power-saving. */
IMDisableDim();
#endif
if (!runloop_shutdown_initiated)
driver_set_nonblock_state();
if (pause_libretro && !audio_enable_menu)
command_event(CMD_EVENT_AUDIO_START, NULL);
#if 0
if (audio_enable_menu && audio_enable_menu_bgm)
audio_driver_mixer_stop_stream(AUDIO_MIXER_SYSTEM_SLOT_BGM);
#endif
/* Restore libretro keyboard callback. */
if (key_event && frontend_key_event)
*key_event = *frontend_key_event;
}
}
#endif
void retroarch_menu_running(void)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(HAVE_MENU) || defined(HAVE_OVERLAY)
settings_t *settings = p_rarch->configuration_settings;
#endif
#ifdef HAVE_OVERLAY
bool input_overlay_hide_in_menu = settings->bools.input_overlay_hide_in_menu;
#endif
#ifdef HAVE_AUDIOMIXER
bool audio_enable_menu = settings->bools.audio_enable_menu;
bool audio_enable_menu_bgm = settings->bools.audio_enable_menu_bgm;
#endif
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
menu_input_t *menu_input = &menu_st->input_state;
if (menu)
{
if (menu->driver_ctx && menu->driver_ctx->toggle)
menu->driver_ctx->toggle(menu->userdata, true);
p_rarch->menu_driver_alive = true;
menu_driver_toggle(
p_rarch->current_video,
p_rarch->video_driver_data,
menu,
menu_input,
settings,
p_rarch->menu_driver_alive,
#ifdef HAVE_OVERLAY
p_rarch->overlay_ptr &&
p_rarch->overlay_ptr->alive,
#else
false,
#endif
&runloop_state.key_event,
&runloop_state.frontend_key_event,
true);
}
/* Prevent stray input (for a single frame) */
menu_st->input_driver_flushing_input = 1;
#ifdef HAVE_AUDIOMIXER
if (audio_enable_menu && audio_enable_menu_bgm)
audio_driver_mixer_play_menu_sound_looped(AUDIO_MIXER_SYSTEM_SLOT_BGM);
#endif
/* Ensure that game focus mode is disabled when
* running the menu (note: it is not currently
* possible for game focus to be enabled at this
* point, but must safeguard against future changes) */
if (p_rarch->game_focus_state.enabled)
{
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_OFF;
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd);
}
/* Ensure that menu screensaver is disabled when
* first switching to the menu */
if (menu_st->screensaver_active)
{
menu_ctx_environment_t menu_environ;
menu_environ.type = MENU_ENVIRON_DISABLE_SCREENSAVER;
menu_environ.data = NULL;
menu_st->screensaver_active = false;
menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
}
menu_st->input_last_time_us = cpu_features_get_time_usec();
#endif
#ifdef HAVE_OVERLAY
if (input_overlay_hide_in_menu)
command_event(CMD_EVENT_OVERLAY_DEINIT, NULL);
#endif
}
void retroarch_menu_running_finished(bool quit)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(HAVE_MENU) || defined(HAVE_OVERLAY)
settings_t *settings = p_rarch->configuration_settings;
#endif
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
menu_handle_t *menu = menu_st->driver_data;
menu_input_t *menu_input = &menu_st->input_state;
if (menu)
{
if (menu->driver_ctx && menu->driver_ctx->toggle)
menu->driver_ctx->toggle(menu->userdata, false);
p_rarch->menu_driver_alive = false;
menu_driver_toggle(
p_rarch->current_video,
p_rarch->video_driver_data,
menu,
menu_input,
settings,
p_rarch->menu_driver_alive,
#ifdef HAVE_OVERLAY
p_rarch->overlay_ptr &&
p_rarch->overlay_ptr->alive,
#else
false,
#endif
&runloop_state.key_event,
&runloop_state.frontend_key_event,
false);
}
/* Prevent stray input
* (for a single frame) */
menu_st->input_driver_flushing_input = 1;
if (!quit)
{
#ifdef HAVE_AUDIOMIXER
/* Stop menu background music before we exit the menu */
if ( settings &&
settings->bools.audio_enable_menu &&
settings->bools.audio_enable_menu_bgm
)
audio_driver_mixer_stop_stream(AUDIO_MIXER_SYSTEM_SLOT_BGM);
#endif
/* Enable game focus mode, if required */
if (p_rarch->current_core_type != CORE_TYPE_DUMMY)
{
enum input_auto_game_focus_type auto_game_focus_type = settings ?
(enum input_auto_game_focus_type)settings->uints.input_auto_game_focus :
AUTO_GAME_FOCUS_OFF;
if ((auto_game_focus_type == AUTO_GAME_FOCUS_ON) ||
((auto_game_focus_type == AUTO_GAME_FOCUS_DETECT) &&
p_rarch->game_focus_state.core_requested))
{
enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_ON;
command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd);
}
}
}
/* Ensure that menu screensaver is disabled when
* switching off the menu */
if (menu_st->screensaver_active)
{
menu_ctx_environment_t menu_environ;
menu_environ.type = MENU_ENVIRON_DISABLE_SCREENSAVER;
menu_environ.data = NULL;
menu_st->screensaver_active = false;
menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ);
}
#endif
video_driver_set_texture_enable(false, false);
#ifdef HAVE_OVERLAY
if (!quit)
if (settings && settings->bools.input_overlay_hide_in_menu)
retroarch_overlay_init(p_rarch);
#endif
}
/**
* rarch_game_specific_options:
*
* Returns: true (1) if a game specific core
* options path has been found,
* otherwise false (0).
**/
static bool rarch_game_specific_options(char **output)
{
char game_options_path[PATH_MAX_LENGTH];
game_options_path[0] ='\0';
if (!retroarch_validate_game_options(
runloop_state.system.info.library_name,
game_options_path,
sizeof(game_options_path), false) ||
!path_is_valid(game_options_path))
return false;
RARCH_LOG("%s %s\n",
msg_hash_to_str(MSG_GAME_SPECIFIC_CORE_OPTIONS_FOUND_AT),
game_options_path);
*output = strdup(game_options_path);
return true;
}
/**
* rarch_folder_specific_options:
*
* Returns: true (1) if a folder specific core
* options path has been found,
* otherwise false (0).
**/
static bool rarch_folder_specific_options(
char **output)
{
char folder_options_path[PATH_MAX_LENGTH];
folder_options_path[0] ='\0';
if (!retroarch_validate_folder_options(
folder_options_path,
sizeof(folder_options_path), false) ||
!path_is_valid(folder_options_path))
return false;
RARCH_LOG("%s %s\n",
msg_hash_to_str(MSG_FOLDER_SPECIFIC_CORE_OPTIONS_FOUND_AT),
folder_options_path);
*output = strdup(folder_options_path);
return true;
}
static void runloop_task_msg_queue_push(
retro_task_t *task, const char *msg,
unsigned prio, unsigned duration,
bool flush)
{
#if defined(HAVE_GFX_WIDGETS)
struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = p_rarch->configuration_settings;
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
bool widgets_active = p_rarch->widgets_active;
if (widgets_active && task->title && !task->mute)
{
RUNLOOP_MSG_QUEUE_LOCK(runloop_state);
ui_companion_driver_msg_queue_push(p_rarch, msg,
prio, task ? duration : duration * 60 / 1000, flush);
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
(char*)msg, 0);
#endif
gfx_widgets_msg_queue_push(
&p_rarch->dispwidget_st,
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
p_rarch->menu_driver_alive
#else
false
#endif
);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
}
else
#endif
runloop_msg_queue_push(msg, prio, duration, flush, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
/* 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 rarch_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;
bool game_specific_options = settings->bools.game_specific_options;
/* Check whether game-specific options exist */
if (game_specific_options &&
rarch_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_state.game_options_active = true;
runloop_state.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 &&
rarch_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_state.game_options_active = false;
runloop_state.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;
global_options_path[0] = '\0';
per_core_options_path[0] = '\0';
if (per_core_options)
{
const char *core_name = runloop_state.system.info.library_name;
/* Get core-specific options path
* > if retroarch_validate_per_core_options() returns
* false, then per-core options are disabled (due to
* unknown system errors...) */
per_core_options = retroarch_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_state.game_options_active = false;
runloop_state.folder_options_active = false;
}
}
static core_option_manager_t *rarch_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 */
rarch_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;
}
void retroarch_init_task_queue(void)
{
#ifdef HAVE_THREADS
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
bool threaded_enable = settings->bools.threaded_data_runloop_enable;
#else
bool threaded_enable = false;
#endif
task_queue_deinit();
task_queue_init(threaded_enable, runloop_task_msg_queue_push);
}
bool rarch_ctl(enum rarch_ctl_state state, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
switch(state)
{
case RARCH_CTL_HAS_SET_SUBSYSTEMS:
return (p_rarch->current_core.has_set_subsystems);
case RARCH_CTL_CORE_IS_RUNNING:
return runloop_state.core_running;
#ifdef HAVE_BSV_MOVIE
case RARCH_CTL_BSV_MOVIE_IS_INITED:
return (p_rarch->bsv_movie_state_handle != NULL);
#endif
#ifdef HAVE_PATCH
case RARCH_CTL_IS_PATCH_BLOCKED:
return p_rarch->rarch_patch_blocked;
case RARCH_CTL_IS_BPS_PREF:
return p_rarch->rarch_bps_pref;
case RARCH_CTL_UNSET_BPS_PREF:
p_rarch->rarch_bps_pref = false;
break;
case RARCH_CTL_IS_UPS_PREF:
return p_rarch->rarch_ups_pref;
case RARCH_CTL_UNSET_UPS_PREF:
p_rarch->rarch_ups_pref = false;
break;
case RARCH_CTL_IS_IPS_PREF:
return p_rarch->rarch_ips_pref;
case RARCH_CTL_UNSET_IPS_PREF:
p_rarch->rarch_ips_pref = false;
break;
#endif
case RARCH_CTL_IS_DUMMY_CORE:
return (p_rarch->current_core_type == CORE_TYPE_DUMMY);
case RARCH_CTL_IS_CORE_LOADED:
{
const char *core_path = (const char*)data;
const char *core_file = path_basename_nocompression(core_path);
if (!string_is_empty(core_file))
{
/* Get loaded core file name */
const char *loaded_core_file = path_basename_nocompression(
path_get(RARCH_PATH_CORE));
/* Check whether specified core and currently
* loaded core are the same */
if (!string_is_empty(loaded_core_file))
if (string_is_equal(core_file, loaded_core_file))
return true;
}
}
return false;
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
case RARCH_CTL_IS_SECOND_CORE_AVAILABLE:
return runloop_state.core_running &&
p_rarch->runahead_secondary_core_available;
case RARCH_CTL_IS_SECOND_CORE_LOADED:
return runloop_state.core_running &&
(p_rarch->secondary_lib_handle != NULL);
#endif
case RARCH_CTL_HAS_SET_USERNAME:
return p_rarch->has_set_username;
case RARCH_CTL_IS_INITED:
return p_rarch->rarch_is_inited;
case RARCH_CTL_MAIN_DEINIT:
if (!p_rarch->rarch_is_inited)
return false;
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
#ifdef HAVE_COMMAND
input_driver_deinit_command(p_rarch);
#endif
#ifdef HAVE_NETWORKGAMEPAD
if (p_rarch->input_driver_remote)
input_remote_free(p_rarch->input_driver_remote,
p_rarch->configuration_settings->uints.input_max_users);
p_rarch->input_driver_remote = NULL;
#endif
input_mapper_reset(&p_rarch->input_driver_mapper);
#ifdef HAVE_THREADS
if (p_rarch->rarch_use_sram)
autosave_deinit();
#endif
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
command_event(CMD_EVENT_SAVE_FILES, NULL);
#ifdef HAVE_REWIND
command_event(CMD_EVENT_REWIND_DEINIT, NULL);
#endif
#ifdef HAVE_CHEATS
cheat_manager_state_free();
#endif
#ifdef HAVE_BSV_MOVIE
bsv_movie_deinit(p_rarch);
#endif
command_event(CMD_EVENT_CORE_DEINIT, NULL);
content_deinit();
path_deinit_subsystem(p_rarch);
path_deinit_savefile();
p_rarch->rarch_is_inited = false;
#ifdef HAVE_THREAD_STORAGE
sthread_tls_delete(&p_rarch->rarch_tls);
#endif
break;
#ifdef HAVE_CONFIGFILE
case RARCH_CTL_SET_BLOCK_CONFIG_READ:
p_rarch->rarch_block_config_read = true;
break;
case RARCH_CTL_UNSET_BLOCK_CONFIG_READ:
p_rarch->rarch_block_config_read = false;
break;
#endif
case RARCH_CTL_GET_CORE_OPTION_SIZE:
{
unsigned *idx = (unsigned*)data;
if (!idx)
return false;
if (runloop_state.core_options)
*idx = (unsigned)runloop_state.core_options->size;
else
*idx = 0;
}
break;
case RARCH_CTL_HAS_CORE_OPTIONS:
return (runloop_state.core_options != NULL);
case RARCH_CTL_CORE_OPTIONS_LIST_GET:
{
core_option_manager_t **coreopts = (core_option_manager_t**)data;
if (!coreopts || !runloop_state.core_options)
return false;
*coreopts = runloop_state.core_options;
}
break;
case RARCH_CTL_CORE_OPTION_UPDATE_DISPLAY:
if (runloop_state.core_options &&
runloop_state.core_options_callback.update_display)
{
/* Note: The update_display() callback may read
* core option values via RETRO_ENVIRONMENT_GET_VARIABLE.
* This will reset the 'options updated' flag.
* We therefore have to cache the current 'options updated'
* state and restore it after the update_display() function
* returns */
bool values_updated = runloop_state.core_options->updated;
bool display_updated = runloop_state.core_options_callback.update_display();
runloop_state.core_options->updated = values_updated;
return display_updated;
}
return false;
#ifdef HAVE_CONFIGFILE
case RARCH_CTL_IS_OVERRIDES_ACTIVE:
return runloop_state.overrides_active;
case RARCH_CTL_SET_REMAPS_CORE_ACTIVE:
runloop_state.remaps_core_active = true;
break;
case RARCH_CTL_IS_REMAPS_CORE_ACTIVE:
return runloop_state.remaps_core_active;
case RARCH_CTL_SET_REMAPS_GAME_ACTIVE:
runloop_state.remaps_game_active = true;
break;
case RARCH_CTL_IS_REMAPS_GAME_ACTIVE:
return runloop_state.remaps_game_active;
case RARCH_CTL_SET_REMAPS_CONTENT_DIR_ACTIVE:
runloop_state.remaps_content_dir_active = true;
break;
case RARCH_CTL_IS_REMAPS_CONTENT_DIR_ACTIVE:
return runloop_state.remaps_content_dir_active;
#endif
case RARCH_CTL_SET_MISSING_BIOS:
runloop_state.missing_bios = true;
break;
case RARCH_CTL_UNSET_MISSING_BIOS:
runloop_state.missing_bios = false;
break;
case RARCH_CTL_IS_MISSING_BIOS:
return runloop_state.missing_bios;
case RARCH_CTL_IS_GAME_OPTIONS_ACTIVE:
return runloop_state.game_options_active;
case RARCH_CTL_IS_FOLDER_OPTIONS_ACTIVE:
return runloop_state.folder_options_active;
case RARCH_CTL_GET_PERFCNT:
{
bool **perfcnt = (bool**)data;
if (!perfcnt)
return false;
*perfcnt = &runloop_state.perfcnt_enable;
}
break;
case RARCH_CTL_SET_PERFCNT_ENABLE:
runloop_state.perfcnt_enable = true;
break;
case RARCH_CTL_UNSET_PERFCNT_ENABLE:
runloop_state.perfcnt_enable = false;
break;
case RARCH_CTL_IS_PERFCNT_ENABLE:
return runloop_state.perfcnt_enable;
case RARCH_CTL_SET_WINDOWED_SCALE:
{
unsigned *idx = (unsigned*)data;
if (!idx)
return false;
runloop_state.pending_windowed_scale = *idx;
}
break;
case RARCH_CTL_STATE_FREE:
runloop_state.perfcnt_enable = false;
runloop_state.idle = false;
runloop_state.paused = false;
runloop_state.slowmotion = false;
#ifdef HAVE_CONFIGFILE
runloop_state.overrides_active = false;
#endif
runloop_state.autosave = false;
retroarch_frame_time_free();
retroarch_audio_buffer_status_free();
retroarch_game_focus_free(&p_rarch->game_focus_state);
retroarch_fastmotion_override_free(p_rarch, &runloop_state);
retroarch_core_options_callback_free(&runloop_state);
memset(&p_rarch->input_driver_analog_requested, 0,
sizeof(p_rarch->input_driver_analog_requested));
break;
case RARCH_CTL_IS_IDLE:
return runloop_state.idle;
case RARCH_CTL_SET_IDLE:
{
bool *ptr = (bool*)data;
if (!ptr)
return false;
runloop_state.idle = *ptr;
}
break;
case RARCH_CTL_SET_PAUSED:
{
bool *ptr = (bool*)data;
if (!ptr)
return false;
runloop_state.paused = *ptr;
}
break;
case RARCH_CTL_IS_PAUSED:
return runloop_state.paused;
case RARCH_CTL_SET_SHUTDOWN:
runloop_state.shutdown_initiated = true;
break;
case RARCH_CTL_CORE_OPTION_PREV:
/*
* Get previous value for core option specified by @idx.
* Options wrap around.
*/
{
unsigned *idx = (unsigned*)data;
if (!idx || !runloop_state.core_options)
return false;
core_option_manager_adjust_val(runloop_state.core_options,
*idx, -1, true);
}
break;
case RARCH_CTL_CORE_OPTION_NEXT:
/*
* Get next value for core option specified by @idx.
* Options wrap around.
*/
{
unsigned* idx = (unsigned*)data;
if (!idx || !runloop_state.core_options)
return false;
core_option_manager_adjust_val(runloop_state.core_options,
*idx, 1, true);
}
break;
case RARCH_CTL_NONE:
default:
return false;
}
return true;
}
static void retroarch_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 Options]: 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 Options]: 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 core_option_manager_t *retroarch_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 */
rarch_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;
}
bool retroarch_is_forced_fullscreen(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->rarch_force_fullscreen;
}
bool retroarch_is_switching_display_mode(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->rarch_is_switching_display_mode;
}
/* get the name of the current shader preset */
const char *retroarch_get_shader_preset(void)
{
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
const char *core_name = runloop_state.system.info.library_name;
bool video_shader_enable = settings->bools.video_shader_enable;
unsigned video_shader_delay = settings->uints.video_shader_delay;
bool auto_shaders_enable = settings->bools.auto_shaders_enable;
bool cli_shader_disable = p_rarch->cli_shader_disable;
if (!video_shader_enable)
return NULL;
if (video_shader_delay && !p_rarch->shader_delay_timer.timer_end)
return NULL;
/* Disallow loading auto-shaders when no core is loaded */
if (string_is_empty(core_name))
return NULL;
if (!string_is_empty(p_rarch->runtime_shader_preset))
return p_rarch->runtime_shader_preset;
/* load auto-shader once, --set-shader works like a global auto-shader */
if (p_rarch->shader_presets_need_reload && !cli_shader_disable)
{
p_rarch->shader_presets_need_reload = false;
if (video_shader_is_supported(video_shader_parse_type(p_rarch->cli_shader)))
strlcpy(p_rarch->runtime_shader_preset,
p_rarch->cli_shader,
sizeof(p_rarch->runtime_shader_preset));
else
{
if (auto_shaders_enable) /* sets runtime_shader_preset */
{
if (load_shader_preset(
settings,
runloop_state.system.info.library_name,
p_rarch->runtime_shader_preset,
sizeof(p_rarch->runtime_shader_preset)))
{
RARCH_LOG("[Shaders]: Specific shader preset found at %s.\n",
p_rarch->runtime_shader_preset);
}
}
}
return p_rarch->runtime_shader_preset;
}
#endif
return NULL;
}
bool retroarch_override_setting_is_set(
enum rarch_override_setting enum_idx, void *data)
{
struct rarch_state *p_rarch = &rarch_st;
switch (enum_idx)
{
case RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE:
{
unsigned *val = (unsigned*)data;
if (val)
{
unsigned bit = *val;
return BIT256_GET(p_rarch->has_set_libretro_device, bit);
}
}
break;
case RARCH_OVERRIDE_SETTING_VERBOSITY:
return p_rarch->has_set_verbosity;
case RARCH_OVERRIDE_SETTING_LIBRETRO:
return p_rarch->has_set_libretro;
case RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY:
return p_rarch->has_set_libretro_directory;
case RARCH_OVERRIDE_SETTING_SAVE_PATH:
return p_rarch->has_set_save_path;
case RARCH_OVERRIDE_SETTING_STATE_PATH:
return p_rarch->has_set_state_path;
#ifdef HAVE_NETWORKING
case RARCH_OVERRIDE_SETTING_NETPLAY_MODE:
return p_rarch->has_set_netplay_mode;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS:
return p_rarch->has_set_netplay_ip_address;
case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
return p_rarch->has_set_netplay_ip_port;
case RARCH_OVERRIDE_SETTING_NETPLAY_STATELESS_MODE:
return p_rarch->has_set_netplay_stateless_mode;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
return p_rarch->has_set_netplay_check_frames;
#endif
#ifdef HAVE_PATCH
case RARCH_OVERRIDE_SETTING_UPS_PREF:
return p_rarch->has_set_ups_pref;
case RARCH_OVERRIDE_SETTING_BPS_PREF:
return p_rarch->has_set_bps_pref;
case RARCH_OVERRIDE_SETTING_IPS_PREF:
return p_rarch->has_set_ips_pref;
#endif
case RARCH_OVERRIDE_SETTING_LOG_TO_FILE:
return p_rarch->has_set_log_to_file;
case RARCH_OVERRIDE_SETTING_NONE:
default:
break;
}
return false;
}
int retroarch_get_capabilities(enum rarch_capabilities type,
char *s, size_t len)
{
switch (type)
{
case RARCH_CAPABILITIES_CPU:
{
uint64_t cpu = cpu_features_get();
if (cpu & RETRO_SIMD_MMX)
strlcat(s, " MMX", len);
if (cpu & RETRO_SIMD_MMXEXT)
strlcat(s, " MMXEXT", len);
if (cpu & RETRO_SIMD_SSE)
strlcat(s, " SSE", len);
if (cpu & RETRO_SIMD_SSE2)
strlcat(s, " SSE2", len);
if (cpu & RETRO_SIMD_SSE3)
strlcat(s, " SSE3", len);
if (cpu & RETRO_SIMD_SSSE3)
strlcat(s, " SSSE3", len);
if (cpu & RETRO_SIMD_SSE4)
strlcat(s, " SSE4", len);
if (cpu & RETRO_SIMD_SSE42)
strlcat(s, " SSE4.2", len);
if (cpu & RETRO_SIMD_AES)
strlcat(s, " AES", len);
if (cpu & RETRO_SIMD_AVX)
strlcat(s, " AVX", len);
if (cpu & RETRO_SIMD_AVX2)
strlcat(s, " AVX2", len);
if (cpu & RETRO_SIMD_NEON)
strlcat(s, " NEON", len);
if (cpu & RETRO_SIMD_VFPV3)
strlcat(s, " VFPv3", len);
if (cpu & RETRO_SIMD_VFPV4)
strlcat(s, " VFPv4", len);
if (cpu & RETRO_SIMD_VMX)
strlcat(s, " VMX", len);
if (cpu & RETRO_SIMD_VMX128)
strlcat(s, " VMX128", len);
if (cpu & RETRO_SIMD_VFPU)
strlcat(s, " VFPU", len);
if (cpu & RETRO_SIMD_PS)
strlcat(s, " PS", len);
if (cpu & RETRO_SIMD_ASIMD)
strlcat(s, " ASIMD", len);
}
break;
case RARCH_CAPABILITIES_COMPILER:
#if defined(_MSC_VER)
snprintf(s, len, "%s: MSVC (%d) %u-bit",
msg_hash_to_str(MSG_COMPILER),
_MSC_VER, (unsigned)
(CHAR_BIT * sizeof(size_t)));
#elif defined(__SNC__)
snprintf(s, len, "%s: SNC (%d) %u-bit",
msg_hash_to_str(MSG_COMPILER),
__SN_VER__, (unsigned)(CHAR_BIT * sizeof(size_t)));
#elif defined(_WIN32) && defined(__GNUC__)
snprintf(s, len, "%s: MinGW (%d.%d.%d) %u-bit",
msg_hash_to_str(MSG_COMPILER),
__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, (unsigned)
(CHAR_BIT * sizeof(size_t)));
#elif defined(__clang__)
snprintf(s, len, "%s: Clang/LLVM (%s) %u-bit",
msg_hash_to_str(MSG_COMPILER),
__clang_version__, (unsigned)(CHAR_BIT * sizeof(size_t)));
#elif defined(__GNUC__)
snprintf(s, len, "%s: GCC (%d.%d.%d) %u-bit",
msg_hash_to_str(MSG_COMPILER),
__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, (unsigned)
(CHAR_BIT * sizeof(size_t)));
#else
snprintf(s, len, "%s %u-bit",
msg_hash_to_str(MSG_UNKNOWN_COMPILER),
(unsigned)(CHAR_BIT * sizeof(size_t)));
#endif
break;
default:
case RARCH_CAPABILITIES_NONE:
break;
}
return 0;
}
void retroarch_set_current_core_type(
enum rarch_core_type type, bool explicitly_set)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch->has_set_core)
return;
if (explicitly_set)
{
p_rarch->has_set_core = true;
p_rarch->explicit_current_core_type = type;
}
p_rarch->current_core_type = type;
}
/**
* retroarch_fail:
* @error_code : Error code.
* @error : Error message to show.
*
* Sanely kills the program.
**/
static void retroarch_fail(struct rarch_state *p_rarch,
int error_code, const char *error)
{
/* We cannot longjmp unless we're in retroarch_main_init().
* If not, something went very wrong, and we should
* just exit right away. */
retro_assert(p_rarch->rarch_error_on_init);
strlcpy(p_rarch->error_string,
error, sizeof(p_rarch->error_string));
longjmp(p_rarch->error_sjlj_context, error_code);
}
bool retroarch_main_quit(void)
{
struct rarch_state *p_rarch = &rarch_st;
global_t *global = &p_rarch->g_extern;
#ifdef HAVE_DISCORD
discord_state_t *discord_st = &p_rarch->discord_st;
if (discord_is_inited)
{
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_SHUTDOWN;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
}
if (discord_st->ready)
{
Discord_ClearPresence();
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
Discord_Shutdown();
discord_st->ready = false;
}
discord_is_inited = false;
#endif
/* Restore original refresh rate, if it has been changed
* automatically in SET_SYSTEM_AV_INFO */
if (p_rarch->video_refresh_rate_original)
video_display_server_restore_refresh_rate();
if (!runloop_state.shutdown_initiated)
{
command_event_save_auto_state(
p_rarch->configuration_settings->bools.savestate_auto_save,
global,
p_rarch->current_core_type);
/* If any save states are in progress, wait
* until all tasks are complete (otherwise
* save state file may be truncated) */
content_wait_for_save_state_task();
#ifdef HAVE_CONFIGFILE
if (runloop_state.overrides_active)
{
/* Reload the original config */
config_unload_override();
runloop_state.overrides_active = false;
}
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
p_rarch->runtime_shader_preset[0] = '\0';
#endif
if ( runloop_state.remaps_core_active
|| runloop_state.remaps_content_dir_active
|| runloop_state.remaps_game_active
)
{
input_remapping_deinit();
input_remapping_set_defaults(true);
}
else
input_remapping_restore_global_config(true);
}
runloop_state.shutdown_initiated = true;
retroarch_menu_running_finished(true);
return true;
}
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)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(HAVE_GFX_WIDGETS)
bool widgets_active = p_rarch->widgets_active;
#endif
#ifdef HAVE_ACCESSIBILITY
settings_t *settings = p_rarch->configuration_settings;
bool accessibility_enable = settings->bools.accessibility_enable;
unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed;
#endif
RUNLOOP_MSG_QUEUE_LOCK(runloop_state);
#ifdef HAVE_ACCESSIBILITY
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
accessibility_speak_priority(p_rarch,
accessibility_enable,
accessibility_narrator_speech_speed,
(char*) msg, 0);
#endif
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
gfx_widgets_msg_queue_push(
&p_rarch->dispwidget_st,
NULL,
msg,
roundf((float)duration / 60.0f * 1000.0f),
title,
icon,
category,
prio,
flush,
#ifdef HAVE_MENU
p_rarch->menu_driver_alive
#else
false
#endif
);
duration = duration * 60 / 1000;
}
else
#endif
{
if (flush)
msg_queue_clear(&runloop_state.msg_queue);
msg_queue_push(&runloop_state.msg_queue, msg,
prio, duration,
title, icon, category);
runloop_state.msg_queue_size = msg_queue_size(
&runloop_state.msg_queue);
}
ui_companion_driver_msg_queue_push(p_rarch,
msg,
prio, duration, flush);
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
}
#ifdef HAVE_MENU
/* Display the libretro core's framebuffer onscreen. */
static bool menu_display_libretro(
struct rarch_state *p_rarch,
float slowmotion_ratio,
bool libretro_running,
retro_time_t current_time)
{
bool runloop_idle = runloop_state.idle;
if ( p_rarch->video_driver_poke &&
p_rarch->video_driver_poke->set_texture_enable)
p_rarch->video_driver_poke->set_texture_enable(
p_rarch->video_driver_data,
true, false);
if (libretro_running)
{
if (!p_rarch->input_driver_block_libretro_input)
p_rarch->input_driver_block_libretro_input = true;
core_run();
p_rarch->libretro_core_runtime_usec +=
rarch_core_runtime_tick(p_rarch, slowmotion_ratio, current_time);
p_rarch->input_driver_block_libretro_input = false;
return false;
}
if (runloop_idle)
{
#ifdef HAVE_DISCORD
discord_userdata_t userdata;
userdata.status = DISCORD_PRESENCE_GAME_PAUSED;
command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
#endif
return false;
}
return true;
}
#endif
static void runloop_apply_fastmotion_override(
struct rarch_state *p_rarch, runloop_state_t *p_runloop,
settings_t *settings)
{
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 =
(p_runloop->fastmotion_override.current.fastforward &&
(p_runloop->fastmotion_override.current.ratio >= 0.0f)) ?
p_runloop->fastmotion_override.current.ratio :
fastforward_ratio_default;
float fastforward_ratio_current;
memcpy(&p_runloop->fastmotion_override.current,
&p_runloop->fastmotion_override.next,
sizeof(p_runloop->fastmotion_override.current));
/* Check if 'fastmotion' state has changed */
if (p_runloop->fastmotion !=
p_runloop->fastmotion_override.current.fastforward)
{
input_driver_state_t *input_driver_st = &p_rarch->input_driver_state;
p_runloop->fastmotion =
p_runloop->fastmotion_override.current.fastforward;
if (p_runloop->fastmotion)
{
if (input_driver_st)
input_driver_st->nonblocking_flag = true;
}
else
{
if (input_driver_st)
input_driver_st->nonblocking_flag = false;
p_rarch->fastforward_after_frames = 1;
}
driver_set_nonblock_state();
/* Reset frame time counter when toggling
* fast-forward off, if required */
if (!p_runloop->fastmotion &&
frame_time_counter_reset_after_fastforwarding)
p_rarch->video_driver_frame_time_count = 0;
/* Ensure fast forward widget is disabled when
* toggling fast-forward off
* (required if RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE
* is called during core de-initialisation) */
#if defined(HAVE_GFX_WIDGETS)
if (p_rarch->widgets_active && !p_runloop->fastmotion)
p_rarch->gfx_widgets_fast_forward = false;
#endif
}
/* Update frame limit, if required */
fastforward_ratio_current = (p_runloop->fastmotion_override.current.fastforward &&
(p_runloop->fastmotion_override.current.ratio >= 0.0f)) ?
p_runloop->fastmotion_override.current.ratio :
fastforward_ratio_default;
if (fastforward_ratio_current != fastforward_ratio_last)
p_rarch->frame_limit_minimum_time =
retroarch_set_frame_limit(&p_rarch->video_driver_av_info,
fastforward_ratio_current);
}
static enum runloop_state runloop_check_state(
struct rarch_state *p_rarch,
settings_t *settings,
retro_time_t current_time)
{
input_bits_t current_bits;
#ifdef HAVE_MENU
static input_bits_t last_input = {{0}};
#endif
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
static bool old_focus = true;
struct retro_callbacks *cbs = &p_rarch->retro_ctx;
bool is_focused = false;
bool is_alive = false;
uint64_t frame_count = 0;
bool focused = true;
bool rarch_is_initialized = p_rarch->rarch_is_inited;
bool runloop_paused = runloop_state.paused;
bool pause_nonactive = settings->bools.pause_nonactive;
#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;
unsigned quit_gamepad_combo = settings->uints.input_quit_gamepad_combo;
bool menu_driver_binding_state = p_rarch->menu_driver_is_binding;
bool menu_is_alive = p_rarch->menu_driver_alive;
bool display_kb = menu_input_dialog_get_display_kb();
#endif
#if defined(HAVE_GFX_WIDGETS)
bool widgets_active = p_rarch->widgets_active;
#endif
#ifdef HAVE_CHEEVOS
bool cheevos_hardcore_active = rcheevos_hardcore_active();
#endif
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
if (p_rarch->dispwidget_st.ai_service_overlay_state == 3)
{
command_event(CMD_EVENT_PAUSE, NULL);
p_rarch->dispwidget_st.ai_service_overlay_state = 1;
}
#endif
#ifdef HAVE_LIBNX
/* Should be called once per frame */
if (!appletMainLoop())
return RUNLOOP_STATE_QUIT;
#endif
#ifdef _3DS
/* Should be called once per frame */
if (!aptMainLoop())
return RUNLOOP_STATE_QUIT;
#endif
BIT256_CLEAR_ALL_PTR(&current_bits);
p_rarch->input_driver_block_libretro_input = false;
p_rarch->input_driver_block_hotkey = false;
if (p_rarch->keyboard_mapping_blocked)
p_rarch->input_driver_block_hotkey = true;
{
rarch_joypad_info_t joypad_info;
unsigned port = 0;
int input_hotkey_block_delay = settings->uints.input_hotkey_block_delay;
input_driver_t *current_input = p_rarch->input_driver_state.current_driver;
const struct retro_keybind *binds_norm = &input_config_binds[port][RARCH_ENABLE_HOTKEY];
const struct retro_keybind *binds_auto = &input_autoconf_binds[port][RARCH_ENABLE_HOTKEY];
const struct retro_keybind *binds = input_config_binds[port];
struct retro_keybind *auto_binds = input_autoconf_binds[port];
struct retro_keybind *general_binds = input_config_binds[port];
#ifdef HAVE_MENU
bool menu_input_active = menu_is_alive && !(settings->bools.menu_unified_controls && !display_kb);
#else
bool menu_input_active = false;
#endif
const input_device_driver_t *joypad =
input_driver_st->primary_joypad;
#ifdef HAVE_MFI
const input_device_driver_t
*sec_joypad = input_driver_st->secondary_joypad;
#else
const input_device_driver_t
*sec_joypad = NULL;
#endif
joypad_info.joy_idx = settings->uints.input_joypad_index[port];
joypad_info.auto_binds = input_autoconf_binds[joypad_info.joy_idx];
joypad_info.axis_threshold = settings->floats.input_axis_threshold;
#ifdef HAVE_MENU
if (menu_input_active)
{
unsigned k;
unsigned x_plus = RARCH_ANALOG_LEFT_X_PLUS;
unsigned y_plus = RARCH_ANALOG_LEFT_Y_PLUS;
unsigned x_minus = RARCH_ANALOG_LEFT_X_MINUS;
unsigned y_minus = RARCH_ANALOG_LEFT_Y_MINUS;
/* Push analog to D-Pad mappings to binds. */
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;
}
}
#endif
input_keys_pressed(port,
menu_input_active, input_hotkey_block_delay, p_rarch,
&current_bits, &binds, binds_norm, binds_auto,
joypad, sec_joypad,
&joypad_info);
#ifdef HAVE_MENU
if (menu_input_active)
{
unsigned j;
/* Restores analog D-pad binds temporarily overridden. */
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;
}
if ( !display_kb
&& current_input->input_state)
{
unsigned i;
unsigned ids[][2] =
{
{RETROK_SPACE, RETRO_DEVICE_ID_JOYPAD_START },
{RETROK_SLASH, RETRO_DEVICE_ID_JOYPAD_X },
{RETROK_RSHIFT, RETRO_DEVICE_ID_JOYPAD_SELECT },
{RETROK_RIGHT, RETRO_DEVICE_ID_JOYPAD_RIGHT },
{RETROK_LEFT, RETRO_DEVICE_ID_JOYPAD_LEFT },
{RETROK_DOWN, RETRO_DEVICE_ID_JOYPAD_DOWN },
{RETROK_UP, RETRO_DEVICE_ID_JOYPAD_UP },
{RETROK_PAGEUP, RETRO_DEVICE_ID_JOYPAD_L },
{RETROK_PAGEDOWN, RETRO_DEVICE_ID_JOYPAD_R },
{0, RARCH_QUIT_KEY },
{0, RARCH_FULLSCREEN_TOGGLE_KEY },
{RETROK_BACKSPACE, RETRO_DEVICE_ID_JOYPAD_B },
{RETROK_RETURN, RETRO_DEVICE_ID_JOYPAD_A },
{RETROK_DELETE, RETRO_DEVICE_ID_JOYPAD_Y },
{0, RARCH_UI_COMPANION_TOGGLE },
{0, RARCH_FPS_TOGGLE },
{0, RARCH_SEND_DEBUG_INFO },
{0, RARCH_NETPLAY_HOST_TOGGLE },
{0, RARCH_MENU_TOGGLE },
};
ids[9][0] = input_config_binds[port][RARCH_QUIT_KEY].key;
ids[10][0] = input_config_binds[port][RARCH_FULLSCREEN_TOGGLE_KEY].key;
ids[14][0] = input_config_binds[port][RARCH_UI_COMPANION_TOGGLE].key;
ids[15][0] = input_config_binds[port][RARCH_FPS_TOGGLE].key;
ids[16][0] = input_config_binds[port][RARCH_SEND_DEBUG_INFO].key;
ids[17][0] = input_config_binds[port][RARCH_NETPLAY_HOST_TOGGLE].key;
ids[18][0] = input_config_binds[port][RARCH_MENU_TOGGLE].key;
if (settings->bools.input_menu_swap_ok_cancel_buttons)
{
ids[11][1] = RETRO_DEVICE_ID_JOYPAD_A;
ids[12][1] = RETRO_DEVICE_ID_JOYPAD_B;
}
for (i = 0; i < ARRAY_SIZE(ids); i++)
{
if (current_input->input_state(
p_rarch->input_driver_state.current_data,
joypad,
sec_joypad,
&joypad_info, &binds,
p_rarch->keyboard_mapping_blocked,
port,
RETRO_DEVICE_KEYBOARD, 0, ids[i][0]))
BIT256_SET_PTR(&current_bits, ids[i][1]);
}
}
}
else
#endif
{
#ifdef HAVE_ACCESSIBILITY
#ifdef HAVE_TRANSLATE
if (settings->bools.ai_service_enable)
{
unsigned i;
p_rarch->gamepad_input_override = 0;
for (i = 0; i < MAX_USERS; i++)
{
/* Set gamepad input override */
if (p_rarch->ai_gamepad_state[i] == 2)
p_rarch->gamepad_input_override |= (1 << i);
p_rarch->ai_gamepad_state[i] = 0;
}
}
#endif
#endif
}
}
#ifdef HAVE_MENU
last_input = current_bits;
if (
((menu_toggle_gamepad_combo != INPUT_COMBO_NONE) &&
input_driver_button_combo(
input_driver_st,
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));
menu_st->input_driver_flushing_input = 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())
{
const ui_application_t *application = p_rarch->ui_companion
? p_rarch->ui_companion->application
: NULL;
if (application)
application->process_events();
}
frame_count = p_rarch->video_driver_frame_count;
is_alive = p_rarch->current_video
? p_rarch->current_video->alive(p_rarch->video_driver_data)
: true;
is_focused = VIDEO_HAS_FOCUS(p_rarch);
#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_state.focused &&
!p_rarch->input_driver_grab_mouse_state)
command_event(CMD_EVENT_GRAB_MOUSE_TOGGLE, NULL);
runloop_state.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 = p_rarch->video_driver_width;
unsigned video_driver_height = p_rarch->video_driver_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)
retroarch_overlay_deinit(p_rarch);
else
retroarch_overlay_init(p_rarch);
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 (p_rarch->input_driver_keyboard_linefeed_enable)
prev_overlay_restore = true;
else if (prev_overlay_restore)
{
if (!input_overlay_hide_in_menu)
retroarch_overlay_init(p_rarch);
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_(
p_rarch->video_driver_width,
p_rarch->video_driver_height,
settings->bools.input_overlay_enable,
p_rarch->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 = p_rarch->video_driver_width;
unsigned video_driver_height = p_rarch->video_driver_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(
input_driver_st,
quit_gamepad_combo,
current_time,
&current_bits)))
trig_quit_key = true;
old_quit_key = quit_key;
quit_press_twice = settings->bools.quit_press_twice;
/* Check double press if enabled */
if (trig_quit_key && quit_press_twice)
{
static retro_time_t quit_key_time = 0;
retro_time_t cur_time = current_time;
trig_quit_key = (cur_time - quit_key_time < QUIT_DELAY_USEC);
quit_key_time = cur_time;
if (!trig_quit_key)
{
float target_hz = 0.0;
rarch_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_state.max_frames;
if ((runloop_max_frames != 0)
&& (frame_count >= runloop_max_frames)
&& runloop_state.max_frames_screenshot)
{
const char *screenshot_path = NULL;
bool fullpath = false;
if (string_is_empty(runloop_state.max_frames_screenshot_path))
screenshot_path = path_get(RARCH_PATH_BASENAME);
else
{
fullpath = true;
screenshot_path = runloop_state.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_state.core_shutdown_initiated &&
settings->bools.load_dummy_on_core_shutdown)
{
content_ctx_info_t content_info;
content_info.argc = 0;
content_info.argv = NULL;
content_info.args = NULL;
content_info.environ_get = NULL;
if (task_push_start_dummy_core(&content_info))
{
/* Loads dummy core instead of exiting RetroArch completely.
* Aborts core shutdown if invoked. */
runloop_state.shutdown_initiated = false;
runloop_state.core_shutdown_initiated = false;
}
else
quit_runloop = true;
}
else
quit_runloop = true;
runloop_state.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,
p_rarch->video_driver_width,
p_rarch->video_driver_height);
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
{
bool rarch_force_fullscreen = p_rarch->rarch_force_fullscreen;
bool video_is_fullscreen = settings->bools.video_fullscreen ||
rarch_force_fullscreen;
RUNLOOP_MSG_QUEUE_LOCK(runloop_state);
gfx_widgets_iterate(
&p_rarch->dispwidget_st,
&p_rarch->dispgfx,
settings,
p_rarch->video_driver_width,
p_rarch->video_driver_height,
video_is_fullscreen,
settings->paths.directory_assets,
settings->paths.path_font,
VIDEO_DRIVER_IS_THREADED_INTERNAL());
RUNLOOP_MSG_QUEUE_UNLOCK(runloop_state);
}
#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;
global_t *global = &p_rarch->g_extern;
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(
p_rarch, settings,
&current_bits, &trigger_input, display_kb);
focused = pause_nonactive ? is_focused : true;
focused = focused &&
!p_rarch->main_ui_companion_is_on_foreground;
if (global)
{
if (action == old_action)
{
retro_time_t press_time = current_time;
if (action == MENU_ACTION_NOOP)
global->menu.noop_press_time = press_time - global->menu.noop_start_time;
else
global->menu.action_press_time = press_time - global->menu.action_start_time;
}
else
{
if (action == MENU_ACTION_NOOP)
{
global->menu.noop_start_time = current_time;
global->menu.noop_press_time = 0;
if (global->menu_prev_action == old_action)
global->menu.action_start_time = global->menu.prev_start_time;
else
global->menu.action_start_time = current_time;
}
else
{
if ( global->menu_prev_action == action &&
global->menu.noop_press_time < 200000) /* 250ms */
{
global->menu.action_start_time = global->menu.prev_start_time;
global->menu.action_press_time = current_time - global->menu.action_start_time;
}
else
{
global->menu.prev_start_time = current_time;
global->menu_prev_action = action;
global->menu.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 (menu_st->pending_quick_menu)
{
/* 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.
*/
menu_entries_flush_stack(NULL, MENU_SETTINGS);
disp_get_ptr()->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(p_rarch,
menu_st,
&p_rarch->dispgfx,
anim_get_ptr(),
settings,
action, current_time))
{
if (p_rarch->rarch_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_state.idle)
{
bool rarch_is_inited = p_rarch->rarch_is_inited;
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
bool libretro_running = !menu_pause_libretro
&& rarch_is_inited
&& (p_rarch->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_rarch->dispgfx.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 (p_rarch->main_ui_companion_is_on_foreground)
{
if ( p_rarch->ui_companion &&
p_rarch->ui_companion->render_messagebox)
p_rarch->ui_companion->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,
p_rarch->video_driver_width,
p_rarch->video_driver_height,
runloop_state.idle);
}
if (p_rarch->menu_driver_alive && !runloop_state.idle)
if (menu_display_libretro(p_rarch,
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_state.idle)
return RUNLOOP_STATE_POLLED_AND_SLEEP;
}
else
#endif
#endif
{
if (runloop_state.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 = p_rarch->current_core_type == CORE_TYPE_DUMMY;
if (menu_st->kb_key_state[RETROK_F1] == 1)
{
if (p_rarch->menu_driver_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 (p_rarch->menu_driver_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);
/* Check if we have pressed the netplay host toggle button */
HOTKEY_CHECK(RARCH_NETPLAY_HOST_TOGGLE, CMD_EVENT_NETPLAY_HOST_TOGGLE, true, NULL);
if (p_rarch->menu_driver_alive)
{
float fastforward_ratio = retroarch_get_runloop_fastforward_ratio(
settings,
&runloop_state.fastmotion_override.current);
if (!settings->bools.menu_throttle_framerate && !fastforward_ratio)
return RUNLOOP_STATE_MENU_ITERATE;
return RUNLOOP_STATE_END;
}
#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);
if (BIT256_GET(current_bits, RARCH_VOLUME_UP))
command_event(CMD_EVENT_VOLUME_UP, NULL);
else if (BIT256_GET(current_bits, RARCH_VOLUME_DOWN))
command_event(CMD_EVENT_VOLUME_DOWN, NULL);
#ifdef HAVE_NETWORKING
/* Check Netplay */
HOTKEY_CHECK(RARCH_NETPLAY_GAME_WATCH, CMD_EVENT_NETPLAY_GAME_WATCH, 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
if (cheevos_hardcore_active)
{
static int unpaused_frames = 0;
/* Frame advance is not allowed when achievement hardcore is active */
if (!runloop_state.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_state.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_state.paused)
{
bool toggle = !runloop_state.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)
{
p_rarch->ai_gamepad_state[0] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_B);
p_rarch->ai_gamepad_state[1] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_Y);
p_rarch->ai_gamepad_state[2] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_SELECT);
p_rarch->ai_gamepad_state[3] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_START);
p_rarch->ai_gamepad_state[4] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_UP);
p_rarch->ai_gamepad_state[5] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_DOWN);
p_rarch->ai_gamepad_state[6] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_LEFT);
p_rarch->ai_gamepad_state[7] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_RIGHT);
p_rarch->ai_gamepad_state[8] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_A);
p_rarch->ai_gamepad_state[9] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_X);
p_rarch->ai_gamepad_state[10] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L);
p_rarch->ai_gamepad_state[11] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R);
p_rarch->ai_gamepad_state[12] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L2);
p_rarch->ai_gamepad_state[13] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_R2);
p_rarch->ai_gamepad_state[14] = BIT256_GET(current_bits, RETRO_DEVICE_ID_JOYPAD_L3);
p_rarch->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_state.fastmotion_override.pending)
{
runloop_apply_fastmotion_override(
p_rarch, &runloop_state, settings);
runloop_state.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_state.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_driver_st->nonblocking_flag)
{
input_driver_st->nonblocking_flag = false;
runloop_state.fastmotion = false;
p_rarch->fastforward_after_frames = 1;
}
else
{
input_driver_st->nonblocking_flag = true;
runloop_state.fastmotion = true;
}
driver_set_nonblock_state();
/* Reset frame time counter when toggling
* fast-forward off, if required */
if (!runloop_state.fastmotion &&
settings->bools.frame_time_counter_reset_after_fastforwarding)
p_rarch->video_driver_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_state.fastmotion_override.current.fastforward ||
runloop_state.fastmotion_override.current.notification)
{
/* > Use widgets, if enabled */
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
p_rarch->gfx_widgets_fast_forward =
settings->bools.notification_show_fast_forward ?
runloop_state.fastmotion : false;
else
#endif
{
/* > If widgets are disabled, display fast-forward
* status via OSD text for 1 frame every frame */
if (runloop_state.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
p_rarch->gfx_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("%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(
&p_rarch->rewind_st,
BIT256_GET(current_bits, RARCH_REWIND),
settings->uints.rewind_granularity,
runloop_state.paused,
s, sizeof(s), &t);
#if defined(HAVE_GFX_WIDGETS)
if (widgets_active)
p_rarch->gfx_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_state.slowmotion = !runloop_state.slowmotion;
else if (old_slowmotion_hold_button_state != new_slowmotion_hold_button_state)
runloop_state.slowmotion = new_slowmotion_hold_button_state;
if (runloop_state.slowmotion)
{
if (settings->uints.video_black_frame_insertion)
if (!runloop_state.idle)
video_driver_cached_frame();
#if defined(HAVE_GFX_WIDGETS)
if (!widgets_active)
#endif
{
#ifdef HAVE_REWIND
struct state_manager_rewind_state
*rewind_st = &p_rarch->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;
}
}
/* 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)
{
uint64_t current_usec = cpu_features_get_time_usec();
RARCH_TIMER_BEGIN_NEW_TIME_USEC(timer,
current_usec,
SHADER_FILE_WATCH_DELAY_MSEC * 1000);
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)
{
RARCH_TIMER_TICK(timer, current_time);
if (!timer.timer_end && RARCH_TIMER_HAS_EXPIRED(timer))
{
RARCH_TIMER_END(timer);
need_to_apply = false;
command_event(CMD_EVENT_SHADERS_APPLY_CHANGES, NULL);
}
}
}
if ( settings->uints.video_shader_delay &&
!p_rarch->shader_delay_timer.timer_end)
{
if (!p_rarch->shader_delay_timer.timer_begin)
{
uint64_t current_usec = cpu_features_get_time_usec();
RARCH_TIMER_BEGIN_NEW_TIME_USEC(
p_rarch->shader_delay_timer,
current_usec,
settings->uints.video_shader_delay * 1000);
p_rarch->shader_delay_timer.timer_begin = true;
p_rarch->shader_delay_timer.timer_end = false;
}
else
{
RARCH_TIMER_TICK(p_rarch->shader_delay_timer, current_time);
if (RARCH_TIMER_HAS_EXPIRED(p_rarch->shader_delay_timer))
{
RARCH_TIMER_END(p_rarch->shader_delay_timer);
{
const char *preset = retroarch_get_shader_preset();
enum rarch_shader_type type = video_shader_parse_type(preset);
retroarch_apply_shader(p_rarch, 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)
{
unsigned i;
enum analog_dpad_mode dpad_mode[MAX_USERS];
struct rarch_state *p_rarch = &rarch_st;
input_driver_state_t *input_driver_st = &(p_rarch->input_driver_state);
settings_t *settings = p_rarch->configuration_settings;
unsigned video_frame_delay = settings->uints.video_frame_delay;
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
bool menu_pause_libretro = settings->bools.menu_pause_libretro;
bool core_paused = runloop_state.paused || (menu_pause_libretro && p_rarch->menu_driver_alive);
#else
bool core_paused = runloop_state.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 = &p_rarch->discord_st;
if (discord_is_inited)
{
Discord_RunCallbacks();
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
}
#endif
if (runloop_state.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_state.frame_time_last;
retro_time_t current = current_time;
bool is_locked_fps = (runloop_state.paused
|| input_driver_st->nonblocking_flag)
| !!p_rarch->recording_data;
retro_time_t delta = (!runloop_last_frame_time || is_locked_fps)
? runloop_state.frame_time.reference
: (current - runloop_last_frame_time);
if (is_locked_fps)
runloop_state.frame_time_last = 0;
else
{
runloop_state.frame_time_last = current;
if (runloop_state.slowmotion)
delta /= slowmotion_ratio;
}
if (!core_paused)
runloop_state.frame_time.callback(delta);
}
/* Update audio buffer occupancy if buffer status
* callback is in use by the core */
if (runloop_state.audio_buffer_status.callback)
{
bool audio_buf_active = false;
unsigned audio_buf_occupancy = 0;
bool audio_buf_underrun = false;
if (!(runloop_state.paused ||
!p_rarch->audio_driver_active ||
!p_rarch->audio_driver_output_samples_buf) &&
p_rarch->current_audio->write_avail &&
p_rarch->audio_driver_context_audio_data &&
p_rarch->audio_driver_buffer_size)
{
size_t audio_buf_avail;
if ((audio_buf_avail = p_rarch->current_audio->write_avail(
p_rarch->audio_driver_context_audio_data)) > p_rarch->audio_driver_buffer_size)
audio_buf_avail = p_rarch->audio_driver_buffer_size;
audio_buf_occupancy = (unsigned)(100 - (audio_buf_avail * 100) /
p_rarch->audio_driver_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_state.audio_buffer_status.callback(
audio_buf_active, audio_buf_occupancy, audio_buf_underrun);
}
switch ((enum runloop_state)runloop_check_state(p_rarch,
settings, current_time))
{
case RUNLOOP_STATE_QUIT:
p_rarch->frame_limit_last_time = 0.0;
runloop_state.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 (!p_rarch->main_ui_companion_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_state.core_running = true;
break;
}
#ifdef HAVE_THREADS
if (runloop_state.autosave)
autosave_lock();
#endif
#ifdef HAVE_BSV_MOVIE
/* Used for rewinding while playback/record. */
if (p_rarch->bsv_movie_state_handle)
p_rarch->bsv_movie_state_handle->frame_pos[p_rarch->bsv_movie_state_handle->frame_ptr]
= intfstream_tell(p_rarch->bsv_movie_state_handle->file);
#endif
if ( p_rarch->camera_cb.caps &&
p_rarch->camera_driver &&
p_rarch->camera_driver->poll &&
p_rarch->camera_data)
p_rarch->camera_driver->poll(p_rarch->camera_data,
p_rarch->camera_cb.frame_raw_framebuffer,
p_rarch->camera_cb.frame_opengl_texture);
/* Update binds for analog dpad modes. */
for (i = 0; i < 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 (p_rarch->input_driver_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 ((video_frame_delay > 0) && input_driver_st && !input_driver_st->nonblocking_flag)
retro_sleep(video_frame_delay);
{
#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;
#ifdef HAVE_NETWORKING
want_runahead = want_runahead && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
#endif
if (want_runahead)
do_runahead(
p_rarch,
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() */
p_rarch->libretro_core_runtime_usec += rarch_core_runtime_tick(
p_rarch,
slowmotion_ratio,
current_time);
#ifdef HAVE_CHEEVOS
if (cheevos_enable)
rcheevos_test();
#endif
#ifdef HAVE_CHEATS
cheat_manager_apply_retro_cheats();
#endif
#ifdef HAVE_DISCORD
if (discord_is_inited && discord_st->ready)
discord_update(DISCORD_PRESENCE_GAME);
#endif
/* Restores analog D-pad binds temporarily overridden. */
for (i = 0; i < max_users; i++)
{
if (dpad_mode[i] != ANALOG_DPAD_NONE)
{
unsigned 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 (p_rarch->bsv_movie_state_handle)
{
p_rarch->bsv_movie_state_handle->frame_ptr =
(p_rarch->bsv_movie_state_handle->frame_ptr + 1)
& p_rarch->bsv_movie_state_handle->frame_mask;
p_rarch->bsv_movie_state_handle->first_rewind =
!p_rarch->bsv_movie_state_handle->did_rewind;
p_rarch->bsv_movie_state_handle->did_rewind = false;
}
#endif
#ifdef HAVE_THREADS
if (runloop_state.autosave)
autosave_unlock();
#endif
end:
if (vrr_runloop_enable)
{
/* Sync on video only, block audio later. */
if (p_rarch->fastforward_after_frames && audio_sync)
{
if (p_rarch->fastforward_after_frames == 1)
{
/* Nonblocking audio */
if (p_rarch->audio_driver_active &&
p_rarch->audio_driver_context_audio_data)
p_rarch->current_audio->set_nonblock_state(
p_rarch->audio_driver_context_audio_data, true);
p_rarch->audio_driver_chunk_size =
p_rarch->audio_driver_chunk_nonblock_size;
}
p_rarch->fastforward_after_frames++;
if (p_rarch->fastforward_after_frames == 6)
{
/* Blocking audio */
if (p_rarch->audio_driver_active &&
p_rarch->audio_driver_context_audio_data)
p_rarch->current_audio->set_nonblock_state(
p_rarch->audio_driver_context_audio_data,
audio_sync ? false : true);
p_rarch->audio_driver_chunk_size =
p_rarch->audio_driver_chunk_block_size;
p_rarch->fastforward_after_frames = 0;
}
}
if (runloop_state.fastmotion)
p_rarch->frame_limit_minimum_time =
retroarch_set_frame_limit(&p_rarch->video_driver_av_info,
retroarch_get_runloop_fastforward_ratio(
settings,
&runloop_state.fastmotion_override.current));
else
p_rarch->frame_limit_minimum_time =
retroarch_set_frame_limit(&p_rarch->video_driver_av_info,
1.0f);
}
/* if there's a fast forward limit, inject sleeps to keep from going too fast. */
if (p_rarch->frame_limit_minimum_time)
{
const retro_time_t end_frame_time = cpu_features_get_time_usec();
const retro_time_t to_sleep_ms = (
(p_rarch->frame_limit_last_time + p_rarch->frame_limit_minimum_time)
- end_frame_time) / 1000;
if (to_sleep_ms > 0)
{
unsigned sleep_ms = (unsigned)to_sleep_ms;
/* Combat jitter a bit. */
p_rarch->frame_limit_last_time += p_rarch->frame_limit_minimum_time;
if (sleep_ms > 0)
{
#if defined(HAVE_COCOATOUCH)
if (!p_rarch->main_ui_companion_is_on_foreground)
#endif
retro_sleep(sleep_ms);
}
return 1;
}
p_rarch->frame_limit_last_time = end_frame_time;
}
return 0;
}
runloop_state_t *runloop_state_get_ptr(void)
{
return &runloop_state;
}
void retroarch_force_video_driver_fallback(const char *driver)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
ui_msg_window_t *msg_window = NULL;
configuration_set_string(settings,
settings->arrays.video_driver,
driver);
command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) && !defined(WINAPI_FAMILY)
/* UI companion driver is not inited yet, just call into it directly */
msg_window = &ui_msg_window_win32;
#endif
if (msg_window)
{
char text[PATH_MAX_LENGTH];
ui_msg_window_state window_state;
char *title = strdup(msg_hash_to_str(MSG_ERROR));
text[0] = '\0';
snprintf(text, sizeof(text),
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_DRIVER_FALLBACK),
driver);
window_state.buttons = UI_MSG_WINDOW_OK;
window_state.text = strdup(text);
window_state.title = title;
window_state.window = NULL;
msg_window->error(&window_state);
free(title);
}
exit(1);
}
enum retro_language rarch_get_language_from_iso(const char *iso639)
{
unsigned i;
enum retro_language lang = RETRO_LANGUAGE_ENGLISH;
struct lang_pair
{
const char *iso639;
enum retro_language lang;
};
const struct lang_pair pairs[] =
{
{"en", RETRO_LANGUAGE_ENGLISH},
{"ja", RETRO_LANGUAGE_JAPANESE},
{"fr", RETRO_LANGUAGE_FRENCH},
{"es", RETRO_LANGUAGE_SPANISH},
{"de", RETRO_LANGUAGE_GERMAN},
{"it", RETRO_LANGUAGE_ITALIAN},
{"nl", RETRO_LANGUAGE_DUTCH},
{"pt_BR", RETRO_LANGUAGE_PORTUGUESE_BRAZIL},
{"pt_PT", RETRO_LANGUAGE_PORTUGUESE_PORTUGAL},
{"pt", RETRO_LANGUAGE_PORTUGUESE_PORTUGAL},
{"ru", RETRO_LANGUAGE_RUSSIAN},
{"ko", RETRO_LANGUAGE_KOREAN},
{"zh_CN", RETRO_LANGUAGE_CHINESE_SIMPLIFIED},
{"zh_SG", RETRO_LANGUAGE_CHINESE_SIMPLIFIED},
{"zh_HK", RETRO_LANGUAGE_CHINESE_TRADITIONAL},
{"zh_TW", RETRO_LANGUAGE_CHINESE_TRADITIONAL},
{"zh", RETRO_LANGUAGE_CHINESE_SIMPLIFIED},
{"eo", RETRO_LANGUAGE_ESPERANTO},
{"pl", RETRO_LANGUAGE_POLISH},
{"vi", RETRO_LANGUAGE_VIETNAMESE},
{"ar", RETRO_LANGUAGE_ARABIC},
{"el", RETRO_LANGUAGE_GREEK},
{"tr", RETRO_LANGUAGE_TURKISH},
{"sk", RETRO_LANGUAGE_SLOVAK},
{"fa", RETRO_LANGUAGE_PERSIAN},
{"he", RETRO_LANGUAGE_HEBREW},
{"ast", RETRO_LANGUAGE_ASTURIAN},
{"fi", RETRO_LANGUAGE_FINNISH},
};
if (string_is_empty(iso639))
return lang;
for (i = 0; i < ARRAY_SIZE(pairs); i++)
{
if (strcasestr(iso639, pairs[i].iso639))
{
lang = pairs[i].lang;
break;
}
}
return lang;
}
void rarch_favorites_init(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
int content_favorites_size = settings ? settings->ints.content_favorites_size : 0;
const char *path_content_favorites = settings ? settings->paths.path_content_favorites : NULL;
bool playlist_sort_alphabetical = settings ? settings->bools.playlist_sort_alphabetical : false;
playlist_config_t playlist_config;
enum playlist_sort_mode current_sort_mode;
playlist_config.capacity = COLLECTION_SIZE;
playlist_config.old_format = settings ? settings->bools.playlist_use_old_format : false;
playlist_config.compress = settings ? settings->bools.playlist_compression : false;
playlist_config.fuzzy_archive_match = settings ? settings->bools.playlist_fuzzy_archive_match : false;
playlist_config_set_base_content_directory(&playlist_config, NULL);
if (!settings)
return;
if (content_favorites_size >= 0)
playlist_config.capacity = (size_t)content_favorites_size;
rarch_favorites_deinit();
RARCH_LOG("[Playlist]: %s: [%s].\n",
msg_hash_to_str(MSG_LOADING_FAVORITES_FILE),
path_content_favorites);
playlist_config_set_path(&playlist_config, path_content_favorites);
g_defaults.content_favorites = playlist_init(&playlist_config);
/* Get current per-playlist sort mode */
current_sort_mode = playlist_get_sort_mode(g_defaults.content_favorites);
/* Ensure that playlist is sorted alphabetically,
* if required */
if ((playlist_sort_alphabetical && (current_sort_mode == PLAYLIST_SORT_MODE_DEFAULT)) ||
(current_sort_mode == PLAYLIST_SORT_MODE_ALPHABETICAL))
playlist_qsort(g_defaults.content_favorites);
}
void rarch_favorites_deinit(void)
{
if (!g_defaults.content_favorites)
return;
playlist_write_file(g_defaults.content_favorites);
playlist_free(g_defaults.content_favorites);
g_defaults.content_favorites = NULL;
}
/* Libretro core loader */
static void retro_run_null(void) { }
static void retro_frame_null(const void *data, unsigned width,
unsigned height, size_t pitch) { }
static void retro_input_poll_null(void) { }
static int16_t core_input_state_poll_late(unsigned port,
unsigned device, unsigned idx, unsigned id)
{
struct rarch_state *p_rarch = &rarch_st;
if (!p_rarch->current_core.input_polled)
input_driver_poll();
p_rarch->current_core.input_polled = true;
return input_state(port, device, idx, id);
}
static retro_input_state_t core_input_state_poll_return_cb(void)
{
struct rarch_state *p_rarch = &rarch_st;
const enum poll_type_override_t
core_poll_type_override = p_rarch->core_poll_type_override;
unsigned new_poll_type = (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
? (core_poll_type_override - 1)
: p_rarch->current_core.poll_type;
if (new_poll_type == POLL_TYPE_LATE)
return core_input_state_poll_late;
return input_state;
}
static void core_input_state_poll_maybe(void)
{
struct rarch_state *p_rarch = &rarch_st;
const enum poll_type_override_t
core_poll_type_override = p_rarch->core_poll_type_override;
unsigned new_poll_type = (core_poll_type_override > POLL_TYPE_OVERRIDE_DONTCARE)
? (core_poll_type_override - 1)
: p_rarch->current_core.poll_type;
if (new_poll_type == POLL_TYPE_NORMAL)
input_driver_poll();
}
/**
* 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 rarch_state *p_rarch,
struct retro_callbacks *cbs)
{
retro_input_state_t state_cb = core_input_state_poll_return_cb();
p_rarch->current_core.retro_set_video_refresh(video_driver_frame);
p_rarch->current_core.retro_set_audio_sample(audio_driver_sample);
p_rarch->current_core.retro_set_audio_sample_batch(audio_driver_sample_batch);
p_rarch->current_core.retro_set_input_state(state_cb);
p_rarch->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;
}
/**
* core_set_default_callbacks:
* @data : pointer to retro_callbacks object
*
* Binds the libretro callbacks to default callback functions.
**/
static bool core_set_default_callbacks(struct retro_callbacks *cbs)
{
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_REWIND
/**
* core_set_rewind_callbacks:
*
* Sets the audio sampling callbacks based on whether or not
* rewinding is currently activated.
**/
bool core_set_rewind_callbacks(void)
{
struct rarch_state *p_rarch = &rarch_st;
struct state_manager_rewind_state
*rewind_st = &p_rarch->rewind_st;
if (rewind_st->frame_is_reversed)
{
p_rarch->current_core.retro_set_audio_sample(audio_driver_sample_rewind);
p_rarch->current_core.retro_set_audio_sample_batch(audio_driver_sample_batch_rewind);
}
else
{
p_rarch->current_core.retro_set_audio_sample(audio_driver_sample);
p_rarch->current_core.retro_set_audio_sample_batch(audio_driver_sample_batch);
}
return true;
}
#endif
#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)
{
struct rarch_state *p_rarch = &rarch_st;
/* Force normal poll type for netplay. */
p_rarch->current_core.poll_type = POLL_TYPE_NORMAL;
/* And use netplay's interceding callbacks */
p_rarch->current_core.retro_set_video_refresh(video_frame_net);
p_rarch->current_core.retro_set_audio_sample(audio_sample_net);
p_rarch->current_core.retro_set_audio_sample_batch(audio_sample_batch_net);
p_rarch->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;
struct rarch_state *p_rarch = &rarch_st;
if (!core_set_default_callbacks(&cbs))
return false;
p_rarch->current_core.retro_set_video_refresh(cbs.frame_cb);
p_rarch->current_core.retro_set_audio_sample(cbs.sample_cb);
p_rarch->current_core.retro_set_audio_sample_batch(cbs.sample_batch_cb);
p_rarch->current_core.retro_set_input_state(cbs.state_cb);
return true;
}
#endif
bool core_set_cheat(retro_ctx_cheat_info_t *info)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
settings_t *settings = p_rarch->configuration_settings;
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);
#ifdef HAVE_NETWORKING
if (want_runahead)
want_runahead = !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
#endif
}
#endif
p_rarch->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 &&
p_rarch->runahead_secondary_core_available &&
secondary_core_ensure_exists(p_rarch, settings) &&
p_rarch->secondary_core.retro_cheat_set)
p_rarch->secondary_core.retro_cheat_set(info->index, info->enabled, info->code);
#endif
return true;
}
bool core_reset_cheat(void)
{
struct rarch_state *p_rarch = &rarch_st;
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
settings_t *settings = p_rarch->configuration_settings;
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);
#ifdef HAVE_NETWORKING
if (want_runahead)
want_runahead = !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
#endif
}
#endif
p_rarch->current_core.retro_cheat_reset();
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
if (want_runahead &&
run_ahead_secondary_instance &&
p_rarch->runahead_secondary_core_available &&
secondary_core_ensure_exists(p_rarch, settings) &&
p_rarch->secondary_core.retro_cheat_reset)
p_rarch->secondary_core.retro_cheat_reset();
#endif
return true;
}
bool core_set_poll_type(unsigned type)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->current_core.poll_type = type;
return true;
}
bool core_set_controller_port_device(retro_ctx_controller_info_t *pad)
{
struct rarch_state *p_rarch = &rarch_st;
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(&p_rarch->input_driver_analog_requested, 0,
sizeof(p_rarch->input_driver_analog_requested));
#ifdef HAVE_RUNAHEAD
remember_controller_port_device(p_rarch, pad->port, pad->device);
#endif
p_rarch->current_core.retro_set_controller_port_device(pad->port, pad->device);
return true;
}
bool core_get_memory(retro_ctx_memory_info_t *info)
{
struct rarch_state *p_rarch = &rarch_st;
if (!info)
return false;
info->size = p_rarch->current_core.retro_get_memory_size(info->id);
info->data = p_rarch->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;
struct rarch_state *p_rarch = &rarch_st;
video_driver_set_cached_frame_ptr(NULL);
#ifdef HAVE_RUNAHEAD
set_load_content_info(p_rarch, load_info);
clear_controller_port_map(p_rarch);
#endif
content_get_status(&contentless, &is_inited);
set_save_state_in_background(false);
if (load_info && load_info->special)
game_loaded = p_rarch->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 = p_rarch->current_core.retro_load_game(load_info->info);
else if (contentless)
game_loaded = p_rarch->current_core.retro_load_game(NULL);
p_rarch->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)
{
struct rarch_state *p_rarch = &rarch_st;
if (!system)
return false;
p_rarch->current_core.retro_get_system_info(system);
return true;
}
bool core_unserialize(retro_ctx_serialize_info_t *info)
{
struct rarch_state *p_rarch = &rarch_st;
if (!info || !p_rarch->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)
{
struct rarch_state *p_rarch = &rarch_st;
if (!info || !p_rarch->current_core.retro_serialize(info->data, info->size))
return false;
return true;
}
bool core_serialize_size(retro_ctx_size_info_t *info)
{
struct rarch_state *p_rarch = &rarch_st;
if (!info)
return false;
info->size = p_rarch->current_core.retro_serialize_size();
return true;
}
uint64_t core_serialization_quirks(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->current_core.serialization_quirks_v;
}
bool core_reset(void)
{
struct rarch_state *p_rarch = &rarch_st;
video_driver_set_cached_frame_ptr(NULL);
p_rarch->current_core.retro_reset();
return true;
}
static bool core_unload_game(struct rarch_state *p_rarch)
{
video_driver_free_hw_context(p_rarch);
video_driver_set_cached_frame_ptr(NULL);
if (p_rarch->current_core.game_loaded)
{
RARCH_LOG("[Core]: Unloading game..\n");
p_rarch->current_core.retro_unload_game();
p_rarch->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
p_rarch->current_core.game_loaded = false;
}
audio_driver_stop(p_rarch);
return true;
}
bool core_run(void)
{
struct rarch_state
*p_rarch = &rarch_st;
struct retro_core_t *
current_core = &p_rarch->current_core;
const enum poll_type_override_t
core_poll_type_override = p_rarch->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;
}
static bool core_verify_api_version(struct rarch_state *p_rarch)
{
unsigned api_version = p_rarch->current_core.retro_api_version();
RARCH_LOG("%s: %u\n%s %s: %u\n",
msg_hash_to_str(MSG_VERSION_OF_LIBRETRO_API),
api_version,
FILE_PATH_LOG_INFO,
msg_hash_to_str(MSG_COMPILED_AGAINST_API),
RETRO_API_VERSION
);
if (api_version != RETRO_API_VERSION)
{
RARCH_WARN("%s\n", msg_hash_to_str(MSG_LIBRETRO_ABI_BREAK));
return false;
}
return true;
}
static bool core_load(
struct rarch_state *p_rarch,
unsigned poll_type_behavior)
{
p_rarch->current_core.poll_type = poll_type_behavior;
if (!core_verify_api_version(p_rarch))
return false;
if (!core_init_libretro_cbs(p_rarch,
&p_rarch->retro_ctx))
return false;
p_rarch->current_core.retro_get_system_av_info(
&p_rarch->video_driver_av_info);
return true;
}
bool core_has_set_input_descriptor(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->current_core.has_set_input_descriptors;
}
unsigned int retroarch_get_rotation(void)
{
struct rarch_state *p_rarch = &rarch_st;
settings_t *settings = p_rarch->configuration_settings;
unsigned video_rotation = settings->uints.video_rotation;
return video_rotation + runloop_state.system.rotation;
}
#ifdef HAVE_ACCESSIBILITY
static bool accessibility_speak_priority(
struct rarch_state *p_rarch,
bool accessibility_enable,
unsigned accessibility_narrator_speech_speed,
const char* speak_text, int priority)
{
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
{
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
RARCH_LOG("Spoke: %s\n", speak_text);
if (frontend && frontend->accessibility_speak)
return frontend->accessibility_speak(accessibility_narrator_speech_speed, speak_text,
priority);
RARCH_LOG("Platform not supported for accessibility.\n");
/* The following method is a fallback for other platforms to use the
AI Service url to do the TTS. However, since the playback is done
via the audio mixer, which only processes the audio while the
core is running, this playback method won't work. When the audio
mixer can handle playing streams while the core is paused, then
we can use this. */
#if 0
#if defined(HAVE_NETWORKING)
return accessibility_speak_ai_service(speak_text, voice, priority);
#endif
#endif
}
return true;
}
#ifdef HAVE_TRANSLATE
static bool is_narrator_running(struct rarch_state *p_rarch,
bool accessibility_enable)
{
if (is_accessibility_enabled(
accessibility_enable,
p_rarch->accessibility_enabled))
{
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->is_narrator_running)
return frontend->is_narrator_running();
}
return true;
}
#endif
#endif
/* Creates folder and core options stub file for subsequent runs */
bool core_options_create_override(bool game_specific)
{
char options_path[PATH_MAX_LENGTH];
config_file_t *conf = NULL;
options_path[0] = '\0';
if (!game_specific)
{
/* Sanity check - cannot create a folder-specific
* override if a game-specific override is
* already active */
if (runloop_state.game_options_active)
goto error;
/* Get options file path (folder-specific) */
if (!retroarch_validate_folder_options(
options_path,
sizeof(options_path), true))
goto error;
}
else
{
/* Get options file path (game-specific) */
if (!retroarch_validate_game_options(
runloop_state.system.info.library_name,
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_state.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_state.game_options_active = game_specific;
runloop_state.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];
struct rarch_state *p_rarch = &rarch_st;
core_option_manager_t *coreopts = runloop_state.core_options;
settings_t *settings = p_rarch->configuration_settings;
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_state.game_options_active &&
!runloop_state.folder_options_active))
return true;
/* Sanity check 2 - can only remove an override
* if the specified type is currently active */
if (game_specific && !runloop_state.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 &&
retroarch_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_state.system.info.library_name;
per_core_options = retroarch_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 core_option *option = NULL;
struct config_entry_list *entry = NULL;
option = (struct core_option*)&coreopts->opts[i];
if (!option)
continue;
entry = config_get_entry(conf, option->key);
if (!entry || 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_state.game_options_active = false;
runloop_state.folder_options_active = true;
}
else
{
path_clear(RARCH_PATH_CORE_OPTIONS);
runloop_state.game_options_active = false;
runloop_state.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)
{
core_option_manager_t *coreopts = runloop_state.core_options;
size_t i;
/* 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)
{
core_option_manager_t *coreopts = runloop_state.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;
/* Attempt to load existing file */
if (path_is_valid(path_core_options))
conf_tmp = config_file_new_from_path_to_string(path_core_options);
/* Create new file if required */
if (!conf_tmp)
conf_tmp = config_file_new_alloc();
if (conf_tmp)
{
core_option_manager_flush(
runloop_state.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_state.core_options->conf_path;
if (!string_is_empty(path_core_options))
{
core_option_manager_flush(
runloop_state.core_options,
runloop_state.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_state.core_options->conf->modified = true;
success = config_file_write(runloop_state.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(path_core_options);
if (string_is_empty(core_options_file))
core_options_file = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN);
/* Log result */
RARCH_LOG(success ?
"[Core Options]: Saved core options to \"%s\"\n" :
"[Core Options]: Failed to save core options to \"%s\"\n",
path_core_options ? path_core_options : "UNKNOWN");
snprintf(msg, sizeof(msg), "%s \"%s\"",
success ?
msg_hash_to_str(MSG_CORE_OPTIONS_FLUSHED) :
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 menu_content_environment_get(int *argc, char *argv[],
void *args, void *params_data)
{
struct rarch_state *p_rarch = &rarch_st;
struct rarch_main_wrap *wrap_args = (struct rarch_main_wrap*)params_data;
rarch_system_info_t *sys_info = &runloop_state.system;
if (!wrap_args)
return;
wrap_args->no_content = sys_info->load_no_content;
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_VERBOSITY, NULL))
wrap_args->verbose = verbosity_is_enabled();
wrap_args->touched = true;
wrap_args->config_path = NULL;
wrap_args->sram_path = NULL;
wrap_args->state_path = NULL;
wrap_args->content_path = NULL;
if (!path_is_empty(RARCH_PATH_CONFIG))
wrap_args->config_path = path_get(RARCH_PATH_CONFIG);
if (!string_is_empty(p_rarch->dir_savefile))
wrap_args->sram_path = p_rarch->dir_savefile;
if (!string_is_empty(p_rarch->dir_savestate))
wrap_args->state_path = p_rarch->dir_savestate;
if (!path_is_empty(RARCH_PATH_CONTENT))
wrap_args->content_path = path_get(RARCH_PATH_CONTENT);
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL))
wrap_args->libretro_path = string_is_empty(path_get(RARCH_PATH_CORE)) ? NULL :
path_get(RARCH_PATH_CORE);
}
frontend_ctx_driver_t *frontend_get_ptr(void)
{
struct rarch_state *p_rarch = &rarch_st;
return p_rarch->current_frontend_ctx;
}
int frontend_driver_parse_drive_list(void *data, bool load_content)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->parse_drive_list)
return -1;
return frontend->parse_drive_list(data, load_content);
}
void frontend_driver_content_loaded(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->content_loaded)
return;
frontend->content_loaded();
}
bool frontend_driver_has_fork(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->set_fork)
return false;
return true;
}
bool frontend_driver_set_fork(enum frontend_fork fork_mode)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend_driver_has_fork())
return false;
return frontend->set_fork(fork_mode);
}
void frontend_driver_process_args(int *argc, char *argv[])
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->process_args)
frontend->process_args(argc, argv);
}
bool frontend_driver_is_inited(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
return frontend != NULL;
}
void frontend_driver_init_first(void *args)
{
struct rarch_state *p_rarch = &rarch_st;
p_rarch->current_frontend_ctx = (frontend_ctx_driver_t*)
frontend_ctx_init_first();
if (p_rarch->current_frontend_ctx && p_rarch->current_frontend_ctx->init)
p_rarch->current_frontend_ctx->init(args);
}
void frontend_driver_free(void)
{
struct rarch_state *p_rarch = &rarch_st;
if (p_rarch)
p_rarch->current_frontend_ctx = NULL;
}
bool frontend_driver_has_get_video_driver_func(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_video_driver)
return false;
return true;
}
const struct video_driver *frontend_driver_get_video_driver(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_video_driver)
return NULL;
return frontend->get_video_driver();
}
void frontend_driver_exitspawn(char *s, size_t len, char *args)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->exitspawn)
frontend->exitspawn(s, len, args);
}
void frontend_driver_deinit(void *args)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->deinit)
frontend->deinit(args);
}
void frontend_driver_shutdown(bool a)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->shutdown)
frontend->shutdown(a);
}
enum frontend_architecture frontend_driver_get_cpu_architecture(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_architecture)
return FRONTEND_ARCH_NONE;
return frontend->get_architecture();
}
const void *frontend_driver_get_cpu_architecture_str(
char *architecture, size_t size)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
enum frontend_architecture arch = frontend_driver_get_cpu_architecture();
switch (arch)
{
case FRONTEND_ARCH_X86:
strcpy_literal(architecture, "x86");
break;
case FRONTEND_ARCH_X86_64:
strcpy_literal(architecture, "x64");
break;
case FRONTEND_ARCH_PPC:
strcpy_literal(architecture, "PPC");
break;
case FRONTEND_ARCH_ARM:
strcpy_literal(architecture, "ARM");
break;
case FRONTEND_ARCH_ARMV7:
strcpy_literal(architecture, "ARMv7");
break;
case FRONTEND_ARCH_ARMV8:
strcpy_literal(architecture, "ARMv8");
break;
case FRONTEND_ARCH_MIPS:
strcpy_literal(architecture, "MIPS");
break;
case FRONTEND_ARCH_TILE:
strcpy_literal(architecture, "Tilera");
break;
case FRONTEND_ARCH_NONE:
default:
strcpy_literal(architecture, "N/A");
break;
}
return frontend;
}
uint64_t frontend_driver_get_total_memory(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_total_mem)
return 0;
return frontend->get_total_mem();
}
uint64_t frontend_driver_get_free_memory(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_free_mem)
return 0;
return frontend->get_free_mem();
}
void frontend_driver_install_signal_handler(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->install_signal_handler)
frontend->install_signal_handler();
}
int frontend_driver_get_signal_handler_state(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_signal_handler_state)
return -1;
return frontend->get_signal_handler_state();
}
void frontend_driver_set_signal_handler_state(int value)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->set_signal_handler_state)
frontend->set_signal_handler_state(value);
}
void frontend_driver_attach_console(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->attach_console)
frontend->attach_console();
}
void frontend_driver_set_screen_brightness(int value)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->set_screen_brightness)
frontend->set_screen_brightness(value);
}
bool frontend_driver_can_set_screen_brightness()
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
return (frontend && frontend->set_screen_brightness);
}
void frontend_driver_detach_console(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->detach_console)
frontend->detach_console();
}
void frontend_driver_destroy_signal_handler_state(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->destroy_signal_handler_state)
frontend->destroy_signal_handler_state();
}
bool frontend_driver_can_watch_for_changes(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->watch_path_for_changes)
return false;
return true;
}
void frontend_driver_watch_path_for_changes(
struct string_list *list, int flags,
path_change_data_t **change_data)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->watch_path_for_changes)
frontend->watch_path_for_changes(list, flags, change_data);
}
bool frontend_driver_check_for_path_changes(path_change_data_t *change_data)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->check_for_path_changes)
return false;
return frontend->check_for_path_changes(change_data);
}
void frontend_driver_set_sustained_performance_mode(bool on)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (frontend && frontend->set_sustained_performance_mode)
frontend->set_sustained_performance_mode(on);
}
const char* frontend_driver_get_cpu_model_name(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_cpu_model_name)
return NULL;
return frontend->get_cpu_model_name();
}
enum retro_language frontend_driver_get_user_language(void)
{
struct rarch_state *p_rarch = &rarch_st;
frontend_ctx_driver_t *frontend = p_rarch->current_frontend_ctx;
if (!frontend || !frontend->get_user_language)
return RETRO_LANGUAGE_ENGLISH;
return frontend->get_user_language();
}