/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
 *  Copyright (C) 2011-2021 - Daniel De Matteis
 *  Copyright (C) 2012-2015 - Michael Lelli
 *  Copyright (C) 2014-2017 - Jean-Andr� Santoni
 *  Copyright (C) 2016-2019 - Brad Parker
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef _WIN32
#ifdef _XBOX
#include <xtl.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#if defined(DEBUG) && defined(HAVE_DRMINGW)
#include "exchndl.h"
#endif
#endif

#if defined(DINGUX)
#include <sys/types.h>
#include <unistd.h>
#endif

#if (defined(__linux__) || defined(__unix__) || defined(DINGUX)) && !defined(EMSCRIPTEN)
#include <signal.h>
#endif

#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
#ifndef LEGACY_WIN32
#define LEGACY_WIN32
#endif
#endif

#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
#include <objbase.h>
#include <process.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <locale.h>

#include <boolean.h>
#include <clamping.h>
#include <string/stdstring.h>
#include <dynamic/dylib.h>
#include <file/config_file.h>
#include <lists/string_list.h>
#include <memalign.h>
#include <retro_math.h>
#include <retro_timers.h>
#include <encodings/utf.h>
#include <time/rtime.h>

#include <libretro.h>
#define VFS_FRONTEND
#include <vfs/vfs_implementation.h>

#include <features/features_cpu.h>

#include <compat/strl.h>
#include <compat/strcasestr.h>
#include <compat/getopt.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
#include <retro_miscellaneous.h>
#include <lists/dir_list.h>

#ifdef EMSCRIPTEN
#include <emscripten/emscripten.h>
#endif

#ifdef HAVE_LIBNX
#include <switch.h>
#include "switch_performance_profiles.h"
#endif

#if defined(ANDROID)
#include "play_feature_delivery/play_feature_delivery.h"
#endif

#ifdef HAVE_PRESENCE
#include "network/presence.h"
#endif
#ifdef HAVE_DISCORD
#include "network/discord.h"
#endif

#ifdef HAVE_MIST
#include "steam/steam.h"
#endif

#include "config.def.h"

#ifdef HAVE_MENU
#include "menu/menu_driver.h"
#endif

#include "location_driver.h"

#include "runloop.h"
#include "camera/camera_driver.h"
#include "location_driver.h"
#include "record/record_driver.h"

#ifdef HAVE_MICROPHONE
#include "audio/microphone_driver.h"
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_NETWORKING
#include <net/net_compat.h>
#include <net/net_socket.h>
#endif

#include <audio/audio_resampler.h>

#include "audio/audio_driver.h"

#ifdef HAVE_GFX_WIDGETS
#include "gfx/gfx_widgets.h"
#endif

#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_NETWORKING
#include "network/netplay/netplay.h"
#include "network/netplay/netplay_private.h"
#ifdef HAVE_WIFI
#include "network/wifi_driver.h"
#endif
#ifdef HAVE_CLOUDSYNC
#include "network/cloud_sync_driver.h"
#endif
#endif

#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#endif

#include "autosave.h"
#include "config.features.h"
#include "content.h"
#include "core_info.h"
#include "dynamic.h"
#include "defaults.h"
#include "driver.h"
#include "msg_hash.h"
#include "paths.h"
#include "file_path_special.h"
#include "ui/ui_companion_driver.h"
#include "verbosity.h"

#include "gfx/video_driver.h"
#include "gfx/video_display_server.h"
#ifdef HAVE_BLUETOOTH
#include "bluetooth/bluetooth_driver.h"
#endif
#include "misc/cpufreq/cpufreq.h"
#include "led/led_driver.h"
#include "midi_driver.h"
#include "core.h"
#include "configuration.h"
#include "list_special.h"
#ifdef HAVE_CHEATS
#include "cheat_manager.h"
#endif
#include "tasks/task_content.h"
#include "tasks/tasks_internal.h"

#include "version.h"
#include "version_git.h"

#include "retroarch.h"

#include "accessibility.h"

#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX)
#include "SDL.h"
#endif

#ifdef HAVE_LAKKA
#include "lakka.h"
#endif

#define _PSUPP(var, name, desc) printf("  %s:\n\t\t%s: %s\n", name, desc, var ? "yes" : "no")

#define FAIL_CPU(simd_type) do { \
   RARCH_ERR(simd_type " code is compiled in, but CPU does not support this feature. Cannot continue.\n"); \
   retroarch_fail(1, "validate_cpu_features()"); \
} while (0)

#define FFMPEG_RECORD_ARG "r:"

#ifdef HAVE_DYNAMIC
#define DYNAMIC_ARG "L:"
#else
#define DYNAMIC_ARG
#endif

#ifdef HAVE_NETWORKING
#define NETPLAY_ARG "HC:F:"
#else
#define NETPLAY_ARG
#endif

#ifdef HAVE_CONFIGFILE
#define CONFIG_FILE_ARG "c:"
#else
#define CONFIG_FILE_ARG
#endif

#ifdef HAVE_BSV_MOVIE
#define BSV_MOVIE_ARG "P:R:M:"
#else
#define BSV_MOVIE_ARG
#endif

/* Griffin hack */
#ifdef HAVE_QT
#ifndef HAVE_MAIN
#define HAVE_MAIN
#endif
#endif

#define MIDI_DRIVER_BUF_SIZE 4096
#define MIDI_DRIVER_OFF "OFF"

/* Descriptive names for options without short variant.
 *
 * Please keep the name in sync with the option name.
 * Order does not matter. */
enum
{
   RA_OPT_MENU = 256, /* must be outside the range of a char */
   RA_OPT_CHECK_FRAMES,
   RA_OPT_PORT,
   RA_OPT_SPECTATE,
   RA_OPT_NICK,
   RA_OPT_COMMAND,
   RA_OPT_APPENDCONFIG,
   RA_OPT_BPS,
   RA_OPT_IPS,
   RA_OPT_XDELTA,
   RA_OPT_NO_PATCH,
   RA_OPT_RECORDCONFIG,
   RA_OPT_SUBSYSTEM,
   RA_OPT_SIZE,
   RA_OPT_FEATURES,
   RA_OPT_VERSION,
   RA_OPT_EOF_EXIT,
   RA_OPT_LOG_FILE,
   RA_OPT_MAX_FRAMES,
   RA_OPT_MAX_FRAMES_SCREENSHOT,
   RA_OPT_MAX_FRAMES_SCREENSHOT_PATH,
   RA_OPT_SET_SHADER,
   RA_OPT_DATABASE_SCAN,
   RA_OPT_ACCESSIBILITY,
   RA_OPT_LOAD_MENU_ON_ERROR
};

/* DRIVERS */
#ifdef HAVE_BLUETOOTH
extern const bluetooth_driver_t *bluetooth_drivers[];
#endif

/* MAIN GLOBAL VARIABLES */
struct rarch_state
{
   char *connect_host; /* Netplay hostname passed from CLI */
   char *connect_mitm_id; /* Netplay MITM address from CLI */

   struct retro_perf_counter *perf_counters_rarch[MAX_COUNTERS];

#ifdef HAVE_THREAD_STORAGE
   sthread_tls_t rarch_tls;               /* unsigned alignment */
#endif
   unsigned perf_ptr_rarch;
   uint16_t flags;

   char launch_arguments[4096];
   char path_default_shader_preset[PATH_MAX_LENGTH];
   char path_content[PATH_MAX_LENGTH];
   char path_libretro[PATH_MAX_LENGTH];
   char path_config_file[PATH_MAX_LENGTH];
   char path_config_append_file[PATH_MAX_LENGTH];
   char path_config_override_file[PATH_MAX_LENGTH];
   char path_core_options_file[PATH_MAX_LENGTH];
   char dir_system[PATH_MAX_LENGTH];
   char dir_savefile[PATH_MAX_LENGTH];
   char dir_savestate[PATH_MAX_LENGTH];
};

/* Forward declarations */
#ifdef HAVE_LIBNX
void libnx_apply_overclock(void);
#endif

static struct rarch_state rarch_st        = {0};

#ifdef HAVE_THREAD_STORAGE
static const void *MAGIC_POINTER          = (void*)(uintptr_t)0x0DEFACED;
#endif

static access_state_t access_state_st     = {0};
static struct global global_driver_st     = {0}; /* retro_time_t alignment */

static void retro_frame_null(const void *data, unsigned width,
      unsigned height, size_t pitch) { }
void retro_input_poll_null(void) { }

static location_driver_t location_null = {
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   "null",
};

static const location_driver_t *location_drivers[] = {
#ifdef ANDROID
   &location_android,
#endif
   &location_null,
   NULL,
};

static location_driver_state_t location_driver_st     = {0};

location_driver_state_t *location_state_get_ptr(void)
{
   return &location_driver_st;
}

const char *config_get_location_driver_options(void)
{
   return char_list_new_special(STRING_LIST_LOCATION_DRIVERS, NULL);
}

static void location_driver_find_driver(
      settings_t *settings,
      location_driver_state_t *location_st,
      const char *prefix,
      bool verbosity_enabled)
{
   int i                        = (int)driver_find_index(
         "location_driver",
         settings->arrays.location_driver);

   if (i >= 0)
      location_st->driver       = (const location_driver_t*)
         location_drivers[i];
   else
   {
      if (verbosity_enabled)
      {
         unsigned d;
         RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
               settings->arrays.location_driver);
         RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
         for (d = 0; location_drivers[d]; d++)
            RARCH_LOG_OUTPUT("\t%s\n", location_drivers[d]->ident);

         RARCH_WARN("Going to default to first %s...\n", prefix);
      }

      location_st->driver = (const location_driver_t*)location_drivers[0];
   }
}

bool driver_location_start(void)
{
   location_driver_state_t
      *location_st              = &location_driver_st;
   if (     location_st
         && location_st->data
         && location_st->driver
         && location_st->driver->start)
   {
      settings_t *settings = config_get_ptr();
      bool location_allow  = settings->bools.location_allow;
      if (location_allow)
         return location_st->driver->start(location_st->data);

      runloop_msg_queue_push("Location is explicitly disabled.\n",
            1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT,
            MESSAGE_QUEUE_CATEGORY_INFO);
   }
   return false;
}

void driver_location_stop(void)
{
   location_driver_state_t
      *location_st              = &location_driver_st;
   if (     location_st
         && location_st->driver
         && location_st->driver->stop
         && location_st->data)
      location_st->driver->stop(location_st->data);
}

void driver_location_set_interval(unsigned interval_msecs,
      unsigned interval_distance)
{
   location_driver_state_t
      *location_st              = &location_driver_st;
   if (     location_st
         && location_st->driver
         && location_st->driver->set_interval
         && location_st->data)
      location_st->driver->set_interval(location_st->data,
            interval_msecs, interval_distance);
}

bool driver_location_get_position(double *lat, double *lon,
      double *horiz_accuracy, double *vert_accuracy)
{
   location_driver_state_t
      *location_st              = &location_driver_st;
   if (     location_st
         && location_st->driver
         && location_st->driver->get_position
         && location_st->data)
      return location_st->driver->get_position(location_st->data,
            lat, lon, horiz_accuracy, vert_accuracy);

   *lat            = 0.0;
   *lon            = 0.0;
   *horiz_accuracy = 0.0;
   *vert_accuracy  = 0.0;
   return false;
}

static bool init_location(
      void *data,
      location_driver_state_t *location_st,
      settings_t *settings,
      bool verbosity_enabled)
{
   /* Resource leaks will follow if location
      interface is initialized twice. */
   if (!location_st->data)
   {
      rarch_system_info_t *sys_info = (rarch_system_info_t*)data;
      location_driver_find_driver(settings,
            &location_driver_st,
            "location driver", verbosity_enabled);

      if (!(location_st->data = location_st->driver->init()))
      {
         RARCH_ERR("Failed to initialize location driver. Will continue without location.\n");
         return false;
      }

      if (sys_info->location_cb.initialized)
         sys_info->location_cb.initialized();
   }

   return true;
}

static void uninit_location(void *data, location_driver_state_t *location_st)
{
   if (location_st->data && location_st->driver)
   {
      rarch_system_info_t *sys_info = (rarch_system_info_t*)data;
      if (sys_info->location_cb.deinitialized)
         sys_info->location_cb.deinitialized();

      if (location_st->driver->free)
         location_st->driver->free(location_st->data);
   }

   location_st->active = false;
   location_st->data   = NULL;
}

static void *rarch_midi_drv_data;
static struct string_list *rarch_midi_drv_inputs;
static struct string_list *rarch_midi_drv_outputs;
static uint8_t *rarch_midi_drv_input_buffer;
static uint8_t *rarch_midi_drv_output_buffer;

static midi_event_t rarch_midi_drv_input_event;  /* ptr alignment */
static midi_event_t rarch_midi_drv_output_event; /* ptr alignment */

static bool rarch_midi_drv_input_enabled;
static bool rarch_midi_drv_output_enabled;
static bool rarch_midi_drv_output_pending;

static void null_midi_free(void *p) { }
static void *null_midi_init(const char *input, const char *output) { return (void*)-1; }
static bool null_midi_get_avail_inputs(struct string_list *inputs) { union string_list_elem_attr attr = {0}; return string_list_append(inputs, "Null", attr); }
static bool null_midi_get_avail_outputs(struct string_list *outputs) { union string_list_elem_attr attr = {0}; return string_list_append(outputs, "Null", attr); }
static bool null_midi_set_input(void *p, const char *input) { return input == NULL || string_is_equal(input, "Null"); }
static bool null_midi_set_output(void *p, const char *output) { return output == NULL || string_is_equal(output, "Null"); }
static bool null_midi_read(void *p, midi_event_t *event) { return false; }
static bool null_midi_write(void *p, const midi_event_t *event) { return true; }
static bool null_midi_flush(void *p) { return true; }

static midi_driver_t midi_null = {
   "null",
   null_midi_get_avail_inputs,
   null_midi_get_avail_outputs,
   null_midi_init,
   null_midi_free,
   null_midi_set_input,
   null_midi_set_output,
   null_midi_read,
   null_midi_write,
   null_midi_flush
};

static midi_driver_t *midi_drv  = &midi_null;

midi_driver_t *midi_drivers[]   = {
#if defined(HAVE_ALSA) && !defined(HAVE_HAKCHI) && !defined(HAVE_SEGAM) && !defined(DINGUX)
   &midi_alsa,
#endif
#ifdef HAVE_WINMM
   &midi_winmm,
#endif
   &midi_null
};

static midi_driver_t *midi_driver_find_driver(const char *ident)
{
   unsigned i;

   for (i = 0; i < ARRAY_SIZE(midi_drivers); ++i)
   {
      if (string_is_equal(midi_drivers[i]->ident, ident))
         return midi_drivers[i];
   }

   RARCH_ERR("[MIDI]: Unknown driver \"%s\", falling back to \"null\" driver.\n", ident);

   return &midi_null;
}

static const void *midi_driver_find_handle(int index)
{
   if (index < 0 || index >= (int)ARRAY_SIZE(midi_drivers))
      return NULL;

   return midi_drivers[index];
}

struct string_list *midi_driver_get_avail_inputs(void)
{
   return rarch_midi_drv_inputs;
}

struct string_list *midi_driver_get_avail_outputs(void)
{
   return rarch_midi_drv_outputs;
}

bool midi_driver_set_all_sounds_off(void)
{
   midi_event_t event;
   uint8_t i;
   uint8_t data[3]     = { 0xB0, 120, 0 };
   bool result         = true;

   if (!rarch_midi_drv_data || !rarch_midi_drv_output_enabled)
      return false;

#ifdef HAVE_WASAPI
   /* FIXME: Due to some mysterious reason Frame Delay does not
    * work with WASAPI unless MIDI output is active, even when
    * MIDI is not used. Frame Delay also breaks if MIDI sounds
    * are "set off", which happens on menu toggle, therefore
    * skip this if WASAPI is used and Frame Delay is active.. */
   if (string_is_equal(audio_state_get_ptr()->current_audio->ident, "wasapi"))
   {
      if (video_state_get_ptr()->frame_delay_target > 0)
         return false;
   }
#endif

   event.data       = data;
   event.data_size  = sizeof(data);
   event.delta_time = 0;

   for (i = 0; i < 16; ++i)
   {
      data[0] = 0xB0 | i;

      if (!midi_drv->write(rarch_midi_drv_data, &event))
         result = false;
   }

   if (!midi_drv->flush(rarch_midi_drv_data))
      result = false;

   if (!result)
      RARCH_ERR("[MIDI]: All sounds off failed.\n");

   return result;
}

bool midi_driver_set_volume(unsigned volume)
{
   midi_event_t event;
   uint8_t         data[8]     = {
      0xF0, 0x7F, 0x7F, 0x04, 0x01, 0, 0, 0xF7};

   if (!rarch_midi_drv_data || !rarch_midi_drv_output_enabled)
      return false;

   volume           = (unsigned)(163.83 * volume + 0.5);
   if (volume > 16383)
      volume        = 16383;

   data[5]          = (uint8_t)(volume & 0x7F);
   data[6]          = (uint8_t)(volume >> 7);

   event.data       = data;
   event.data_size  = sizeof(data);
   event.delta_time = 0;

   if (!midi_drv->write(rarch_midi_drv_data, &event))
   {
      RARCH_ERR("[MIDI]: Volume change failed.\n");
      return false;
   }

   return true;
}

static bool midi_driver_init_io_buffers(void)
{
   uint8_t *midi_drv_input_buffer  = (uint8_t*)malloc(MIDI_DRIVER_BUF_SIZE);
   uint8_t *midi_drv_output_buffer = (uint8_t*)malloc(MIDI_DRIVER_BUF_SIZE);

   if (!midi_drv_input_buffer || !midi_drv_output_buffer)
   {
      if (midi_drv_input_buffer)
         free(midi_drv_input_buffer);
      if (midi_drv_output_buffer)
         free(midi_drv_output_buffer);
      return false;
   }

   rarch_midi_drv_input_buffer           = midi_drv_input_buffer;
   rarch_midi_drv_output_buffer          = midi_drv_output_buffer;

   rarch_midi_drv_input_event.data       = midi_drv_input_buffer;
   rarch_midi_drv_input_event.data_size  = 0;

   rarch_midi_drv_output_event.data      = midi_drv_output_buffer;
   rarch_midi_drv_output_event.data_size = 0;

   return true;
}

static void midi_driver_free(void)
{
   if (rarch_midi_drv_data)
   {
      midi_drv->free(rarch_midi_drv_data);
      rarch_midi_drv_data = NULL;
   }

   if (rarch_midi_drv_inputs)
   {
      string_list_free(rarch_midi_drv_inputs);
      rarch_midi_drv_inputs = NULL;
   }

   if (rarch_midi_drv_outputs)
   {
      string_list_free(rarch_midi_drv_outputs);
      rarch_midi_drv_outputs = NULL;
   }

   if (rarch_midi_drv_input_buffer)
   {
      free(rarch_midi_drv_input_buffer);
      rarch_midi_drv_input_buffer = NULL;
   }

   if (rarch_midi_drv_output_buffer)
   {
      free(rarch_midi_drv_output_buffer);
      rarch_midi_drv_output_buffer = NULL;
   }

   rarch_midi_drv_input_enabled  = false;
   rarch_midi_drv_output_enabled = false;
}

static bool midi_driver_init(void *data)
{
   union string_list_elem_attr
      attr                        = {0};
   bool ret                       = true;
   settings_t *settings           = (settings_t*)data;

   rarch_midi_drv_inputs          = string_list_new();
   rarch_midi_drv_outputs         = string_list_new();

   if (!rarch_midi_drv_inputs || !rarch_midi_drv_outputs)
      ret = false;
   else if (!string_list_append(rarch_midi_drv_inputs, MIDI_DRIVER_OFF, attr) ||
            !string_list_append(rarch_midi_drv_outputs, MIDI_DRIVER_OFF, attr))
      ret = false;
   else
   {
      char * input  = NULL;
      char * output = NULL;

      midi_drv      = midi_driver_find_driver(
            settings->arrays.midi_driver);

      if (strcmp(midi_drv->ident, settings->arrays.midi_driver))
      {
         configuration_set_string(settings,
               settings->arrays.midi_driver, midi_drv->ident);
      }

      if (!midi_drv->get_avail_inputs(rarch_midi_drv_inputs))
         ret = false;
      else if (!midi_drv->get_avail_outputs(rarch_midi_drv_outputs))
         ret = false;
      else
      {
         if (string_is_not_equal(settings->arrays.midi_input, MIDI_DRIVER_OFF))
         {
            if (string_list_find_elem(rarch_midi_drv_inputs, settings->arrays.midi_input))
               input = settings->arrays.midi_input;
            else
            {
               RARCH_WARN("[MIDI]: Input device \"%s\" unavailable.\n",
                     settings->arrays.midi_input);
               configuration_set_string(settings,
                     settings->arrays.midi_input, MIDI_DRIVER_OFF);
            }
         }

         if (string_is_not_equal(settings->arrays.midi_output, MIDI_DRIVER_OFF))
         {
            if (string_list_find_elem(rarch_midi_drv_outputs, settings->arrays.midi_output))
               output = settings->arrays.midi_output;
            else
            {
               RARCH_WARN("[MIDI]: Output device \"%s\" unavailable.\n",
                     settings->arrays.midi_output);
               configuration_set_string(settings,
                     settings->arrays.midi_output, MIDI_DRIVER_OFF);
            }
         }

         rarch_midi_drv_data = midi_drv->init(input, output);
         if (!rarch_midi_drv_data)
            ret = false;
         else
         {
            rarch_midi_drv_input_enabled  = (input  != NULL);
            rarch_midi_drv_output_enabled = (output != NULL);

            if (!midi_driver_init_io_buffers())
               ret = false;
            else
            {
               if (input)
                  RARCH_LOG("[MIDI]: Input device: \"%s\".\n", input);

               if (output)
               {
                  RARCH_LOG("[MIDI]: Output device: \"%s\".\n", output);
                  midi_driver_set_volume(settings->uints.midi_volume);
               }
            }
         }
      }
   }

   if (!ret)
   {
      midi_driver_free();
      RARCH_ERR("[MIDI]: Initialization failed.\n");
      return false;
   }
   return true;
}

bool midi_driver_set_input(const char *input)
{
   if (!rarch_midi_drv_data)
   {
#ifdef DEBUG
      RARCH_ERR("[MIDI]: midi_driver_set_input called on uninitialized driver.\n");
#endif
      return false;
   }

   if (string_is_equal(input, MIDI_DRIVER_OFF))
      input = NULL;

   if (!midi_drv->set_input(rarch_midi_drv_data, input))
   {
      if (input)
         RARCH_ERR("[MIDI]: Failed to change input device to \"%s\".\n", input);
      else
         RARCH_ERR("[MIDI]: Failed to disable input.\n");
      return false;
   }

   if (input)
      RARCH_LOG("[MIDI]: Input device changed to \"%s\".\n", input);
   else
      RARCH_LOG("[MIDI]: Input disabled.\n");

   rarch_midi_drv_input_enabled = input != NULL;

   return true;
}

bool midi_driver_set_output(void *settings_data, const char *output)
{
   settings_t        *settings = (settings_t*)settings_data;

   if (!rarch_midi_drv_data)
   {
#ifdef DEBUG
      RARCH_ERR("[MIDI]: midi_driver_set_output called on uninitialized driver.\n");
#endif
      return false;
   }

   if (string_is_equal(output, MIDI_DRIVER_OFF))
      output = NULL;

   if (!midi_drv->set_output(rarch_midi_drv_data, output))
   {
      if (output)
         RARCH_ERR("[MIDI]: Failed to change output device to \"%s\".\n", output);
      else
         RARCH_ERR("[MIDI]: Failed to disable output.\n");
      return false;
   }

   if (output)
   {
      rarch_midi_drv_output_enabled = true;
      RARCH_LOG("[MIDI]: Output device changed to \"%s\".\n", output);

      midi_driver_set_volume(settings->uints.midi_volume);
   }
   else
   {
      rarch_midi_drv_output_enabled = false;
      RARCH_LOG("[MIDI]: Output disabled.\n");
   }

   return true;
}

bool midi_driver_input_enabled(void)
{
   return rarch_midi_drv_input_enabled;
}

bool midi_driver_output_enabled(void)
{
   return rarch_midi_drv_output_enabled;
}

bool midi_driver_read(uint8_t *byte)
{
   static int i = 0;

   if (!rarch_midi_drv_data || !rarch_midi_drv_input_enabled || !byte)
   {
#ifdef DEBUG
      if (!rarch_midi_drv_data)
         RARCH_ERR("[MIDI]: midi_driver_read called on uninitialized driver.\n");
      else if (!rarch_midi_drv_input_enabled)
         RARCH_ERR("[MIDI]: midi_driver_read called when input is disabled.\n");
      else
         RARCH_ERR("[MIDI]: midi_driver_read called with null pointer.\n");
#endif
      return false;
   }

   if (i == (int)rarch_midi_drv_input_event.data_size)
   {
      rarch_midi_drv_input_event.data_size = MIDI_DRIVER_BUF_SIZE;
      if (!midi_drv->read(rarch_midi_drv_data, &rarch_midi_drv_input_event))
      {
         rarch_midi_drv_input_event.data_size = i;
         return false;
      }

      i = 0;

#ifdef DEBUG
      if (rarch_midi_drv_input_event.data_size == 1)
         RARCH_LOG("[MIDI]: In [0x%02X].\n",
               rarch_midi_drv_input_event.data[0]);
      else if (rarch_midi_drv_input_event.data_size == 2)
         RARCH_LOG("[MIDI]: In [0x%02X, 0x%02X].\n",
               rarch_midi_drv_input_event.data[0],
               rarch_midi_drv_input_event.data[1]);
      else if (rarch_midi_drv_input_event.data_size == 3)
         RARCH_LOG("[MIDI]: In [0x%02X, 0x%02X, 0x%02X].\n",
               rarch_midi_drv_input_event.data[0],
               rarch_midi_drv_input_event.data[1],
               rarch_midi_drv_input_event.data[2]);
      else
         RARCH_LOG("[MIDI]: In [0x%02X, ...], size %u.\n",
               rarch_midi_drv_input_event.data[0],
               rarch_midi_drv_input_event.data_size);
#endif
   }

   *byte = rarch_midi_drv_input_event.data[i++];

   return true;
}

bool midi_driver_write(uint8_t byte, uint32_t delta_time)
{
   static int event_size;

   if (!rarch_midi_drv_data || !rarch_midi_drv_output_enabled)
   {
#ifdef DEBUG
      if (!rarch_midi_drv_data)
         RARCH_ERR("[MIDI]: midi_driver_write called on uninitialized driver.\n");
      else
         RARCH_ERR("[MIDI]: midi_driver_write called when output is disabled.\n");
#endif
      return false;
   }

   if (byte >= 0x80)
   {
      if (     rarch_midi_drv_output_event.data_size
            && rarch_midi_drv_output_event.data[0] == 0xF0)
      {
         if (byte == 0xF7)
            event_size = (int)rarch_midi_drv_output_event.data_size + 1;
         else
         {
            if (!midi_drv->write(rarch_midi_drv_data,
                     &rarch_midi_drv_output_event))
               return false;

#ifdef DEBUG
            switch (rarch_midi_drv_output_event.data_size)
            {
               case 1:
                  RARCH_LOG("[MIDI]: Out [0x%02X].\n",
                        rarch_midi_drv_output_event.data[0]);
                  break;
               case 2:
                  RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X].\n",
                        rarch_midi_drv_output_event.data[0],
                        rarch_midi_drv_output_event.data[1]);
                  break;
               case 3:
                  RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X, 0x%02X].\n",
                        rarch_midi_drv_output_event.data[0],
                        rarch_midi_drv_output_event.data[1],
                        rarch_midi_drv_output_event.data[2]);
                  break;
               default:
                  RARCH_LOG("[MIDI]: Out [0x%02X, ...], size %u.\n",
                        rarch_midi_drv_output_event.data[0],
                        rarch_midi_drv_output_event.data_size);
                  break;
            }
#endif

            rarch_midi_drv_output_pending             = true;
            event_size                                = (int)midi_driver_get_event_size(byte);
            rarch_midi_drv_output_event.data_size     = 0;
            rarch_midi_drv_output_event.delta_time    = 0;
         }
      }
      else
      {
         event_size                                   = (int)midi_driver_get_event_size(byte);
         rarch_midi_drv_output_event.data_size        = 0;
         rarch_midi_drv_output_event.delta_time       = 0;
      }
   }

   if (rarch_midi_drv_output_event.data_size < MIDI_DRIVER_BUF_SIZE)
   {
      rarch_midi_drv_output_event.data[rarch_midi_drv_output_event.data_size] = byte;
      ++rarch_midi_drv_output_event.data_size;
      rarch_midi_drv_output_event.delta_time += delta_time;
   }
   else
   {
#ifdef DEBUG
      RARCH_ERR("[MIDI]: Output event dropped.\n");
#endif
      return false;
   }

   if (event_size == (int)rarch_midi_drv_output_event.data_size)
   {
      if (!midi_drv->write(rarch_midi_drv_data, &rarch_midi_drv_output_event))
         return false;

#ifdef DEBUG
      switch (rarch_midi_drv_output_event.data_size)
      {
         case 1:
            RARCH_LOG("[MIDI]: Out [0x%02X].\n",
                  rarch_midi_drv_output_event.data[0]);
            break;
         case 2:
            RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X].\n",
                  rarch_midi_drv_output_event.data[0],
                  rarch_midi_drv_output_event.data[1]);
            break;
         case 3:
            RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X, 0x%02X].\n",
                  rarch_midi_drv_output_event.data[0],
                  rarch_midi_drv_output_event.data[1],
                  rarch_midi_drv_output_event.data[2]);
            break;
         default:
            RARCH_LOG("[MIDI]: Out [0x%02X, ...], size %u.\n",
                  rarch_midi_drv_output_event.data[0],
                  rarch_midi_drv_output_event.data_size);
            break;
      }
#endif

      rarch_midi_drv_output_pending             = true;
      rarch_midi_drv_output_event.data_size     = 0;
      rarch_midi_drv_output_event.delta_time    = 0;
   }

   return true;
}

bool midi_driver_flush(void)
{
   if (!rarch_midi_drv_data)
      return false;

   if (rarch_midi_drv_output_pending)
      rarch_midi_drv_output_pending = !midi_drv->flush(rarch_midi_drv_data);

   return !rarch_midi_drv_output_pending;
}

size_t midi_driver_get_event_size(uint8_t status)
{
   static const uint8_t midi_drv_ev_sizes[128]                     =
   {
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
      2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
   };

   if (status < 0x80)
   {
#ifdef DEBUG
      RARCH_ERR("[MIDI]: midi_driver_get_event_size called with invalid status.\n");
#endif
      return 0;
   }

   return midi_drv_ev_sizes[status - 0x80];
}

/**
 * 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];
      }
   }
#ifdef HAVE_MICROPHONE
   else if (string_is_equal(label, "microphone_driver"))
   {
      if (microphone_drivers[i])
      {
         const char *ident = microphone_drivers[i]->ident;

         strlcpy(s, ident, len);
         return microphone_drivers[i];
      }
   }
#endif
   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);
      }
   }
#ifdef HAVE_BLUETOOTH
   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];
      }
   }
#endif
#ifdef HAVE_WIFI
   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];
      }
   }
#endif
#ifdef HAVE_CLOUDSYNC
   else if (string_is_equal(label, "cloud_sync_driver"))
   {
      if (cloud_sync_drivers[i])
      {
         const char *ident = cloud_sync_drivers[i]->ident;

         strlcpy(s, ident, len);
         return cloud_sync_drivers[i];
      }
   }
#endif
   return NULL;
}

int driver_find_index(const char *label, const char *drv)
{
   unsigned i;
   char str[NAME_MAX_LENGTH];

   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;
}

static float audio_driver_monitor_adjust_system_rates(
      double input_sample_rate,
      double input_fps,
      float video_refresh_rate,
      unsigned video_swap_interval,
      unsigned black_frame_insertion,
      unsigned shader_subframes,
      float audio_max_timing_skew)
{
   float inp_sample_rate        = input_sample_rate;

   /*  This much like the auto swap interval algorithm and will
    *  find the correct desired target rate the majority of sane
    *  cases. Any failures should be no worse than the previous
    *  very incomplete high hz skew adjustments. */
   float refresh_ratio                   = video_refresh_rate/input_fps;
   unsigned refresh_closest_multiple     = (unsigned)(refresh_ratio + 0.5f);
   float target_video_sync_rate          = video_refresh_rate;
   float timing_skew                     = 0.0f;

   if (refresh_closest_multiple > 1)
      target_video_sync_rate /= (((float)black_frame_insertion + 1.0f) * (float)video_swap_interval * (float)shader_subframes);

   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;
}

static bool video_driver_monitor_adjust_system_rates(
      unsigned _video_swap_interval,
      float timing_skew_hz,
      float video_refresh_rate,
      bool vrr_runloop_enable,
      float audio_max_timing_skew,
      unsigned video_swap_interval,
      unsigned black_frame_insertion,
      unsigned shader_subframes,
      double input_fps)
{
   float target_video_sync_rate = timing_skew_hz;

   /* Same concept as for audio driver adjust. */
   float refresh_ratio                   = target_video_sync_rate/input_fps;
   unsigned refresh_closest_multiple     = (unsigned)(refresh_ratio + 0.5f);
   float timing_skew                     = 0.0f;

   if (refresh_closest_multiple > 1)
      target_video_sync_rate /= (((float)black_frame_insertion + 1.0f) * (float)video_swap_interval * (float)shader_subframes);

   if (!vrr_runloop_enable)
   {
      timing_skew         =
         fabs(1.0f - input_fps / target_video_sync_rate);
      /* 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."
            " (Target = %.2f Hz, Game = %.2f Hz)\n",
            target_video_sync_rate,
            (float)input_fps);
   }
   return input_fps <= target_video_sync_rate;
}

static void driver_adjust_system_rates(
      runloop_state_t *runloop_st,
      video_driver_state_t *video_st,
      settings_t *settings,
      bool vrr_runloop_enable,
      float video_refresh_rate,
      float audio_max_timing_skew,
      bool video_adaptive_vsync,
      unsigned video_swap_interval,
      unsigned black_frame_insertion,
      unsigned shader_subframes)
{
   struct retro_system_av_info *av_info   = &video_st->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;

   /* Update video swap interval if automatic
    * switching is enabled */
   runloop_set_video_swap_interval(
         vrr_runloop_enable,
         (video_st->flags & VIDEO_FLAG_CRT_SWITCHING_ACTIVE) ? true : false,
         video_swap_interval,
         black_frame_insertion,
         shader_subframes,
         audio_max_timing_skew,
         video_refresh_rate,
         input_fps);
   video_swap_interval = runloop_get_video_swap_interval(
         video_swap_interval);

   if (input_sample_rate > 0.0)
   {
      audio_driver_state_t *audio_st      = audio_state_get_ptr();
      if (vrr_runloop_enable)
         audio_st->input = input_sample_rate;
      else
         audio_st->input =
            audio_driver_monitor_adjust_system_rates(
                  input_sample_rate,
                  input_fps,
                  video_refresh_rate,
                  video_swap_interval,
                  black_frame_insertion,
                  shader_subframes,
                  audio_max_timing_skew);

      RARCH_LOG("[Audio]: Set audio input rate to: %.2f Hz.\n",
            audio_st->input);
   }

   runloop_st->flags &= ~RUNLOOP_FLAG_FORCE_NONBLOCK;

   if (input_fps > 0.0)
   {
      float timing_skew_hz          = video_refresh_rate;

      if (video_st->flags & VIDEO_FLAG_CRT_SWITCHING_ACTIVE)
         timing_skew_hz             = input_fps;
      video_st->core_hz             = input_fps;

      if (!video_driver_monitor_adjust_system_rates(
               settings->uints.video_swap_interval,
               timing_skew_hz,
               video_refresh_rate,
               vrr_runloop_enable,
               audio_max_timing_skew,
               video_swap_interval,
               black_frame_insertion,
               shader_subframes,
               input_fps))
      {
         /* We won't be able to do VSync reliably
            when game FPS > monitor FPS. */
         runloop_st->flags |= RUNLOOP_FLAG_FORCE_NONBLOCK;
         RARCH_LOG("[Video]: Game FPS > Monitor FPS. Cannot rely on VSync.\n");

         if (VIDEO_DRIVER_GET_PTR_INTERNAL(video_st))
         {
            if (video_st->current_video->set_nonblock_state)
               video_st->current_video->set_nonblock_state(
                     video_st->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(video_st))
      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)
{
   runloop_state_t *runloop_st = runloop_state_get_ptr();
   input_driver_state_t
      *input_st                = input_state_get_ptr();
   audio_driver_state_t
      *audio_st                = audio_state_get_ptr();
   video_driver_state_t
      *video_st                = video_state_get_ptr();
   bool                 enable = input_st ?
      (input_st->flags & INP_FLAG_NONBLOCKING) : false;
   settings_t       *settings  = config_get_ptr();
   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      = runloop_get_video_swap_interval(
         settings->uints.video_swap_interval);
   bool video_driver_active    = (video_st->flags  & VIDEO_FLAG_ACTIVE) ? true : false;
   bool audio_driver_active    = (audio_st->flags  & AUDIO_FLAG_ACTIVE) ? true : false;
   bool runloop_force_nonblock = (runloop_st->flags & RUNLOOP_FLAG_FORCE_NONBLOCK) ? true : false;

   /* Only apply non-block-state for video if we're using vsync. */
   if (video_driver_active && VIDEO_DRIVER_GET_PTR_INTERNAL(video_st))
   {
      if (video_st->current_video->set_nonblock_state)
      {
         bool video_nonblock        = enable;
         if (!video_vsync || runloop_force_nonblock)
            video_nonblock = true;
         video_st->current_video->set_nonblock_state(video_st->data,
               video_nonblock,
               video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC)
               && adaptive_vsync, swap_interval);
      }
   }

   if (audio_driver_active && audio_st->context_audio_data)
      audio_st->current_audio->set_nonblock_state(
            audio_st->context_audio_data,
            audio_sync ? enable : true);

   audio_st->chunk_size = enable
      ? audio_st->chunk_nonblock_size
      : audio_st->chunk_block_size;
}

void drivers_init(
      settings_t *settings,
      int flags,
      enum driver_lifetime_flags lifetime_flags,
      bool verbosity_enabled)
{
   runloop_state_t *runloop_st       = runloop_state_get_ptr();
   audio_driver_state_t *audio_st    = audio_state_get_ptr();
   input_driver_state_t *input_st    = input_state_get_ptr();
   video_driver_state_t *video_st    = video_state_get_ptr();
#ifdef HAVE_MICROPHONE
   microphone_driver_state_t *mic_st = microphone_state_get_ptr();
#endif
#ifdef HAVE_MENU
   struct menu_state *menu_st     = menu_state_get_ptr();
#endif
   camera_driver_state_t
      *camera_st                  = camera_state_get_ptr();
   location_driver_state_t
      *location_st                = location_state_get_ptr();
   bool video_is_threaded         = VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st);
   gfx_display_t *p_disp          = disp_get_ptr();
#if defined(HAVE_GFX_WIDGETS)
   bool video_font_enable         = settings->bools.video_font_enable;
   bool menu_enable_widgets       = settings->bools.menu_enable_widgets;
   dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr();
   /* By default, we want display widgets to persist through driver reinits. */
   p_dispwidget->flags           |= DISPGFX_WIDGET_FLAG_PERSISTING;
#endif
#ifdef HAVE_MENU
   /* By default, we want the menu to persist through driver reinits. */
   if (menu_st)
      menu_st->flags             |= MENU_ST_FLAG_DATA_OWN;
#endif

   if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
      driver_adjust_system_rates(runloop_st, video_st, settings,
                                 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,
                                 settings->uints.video_black_frame_insertion,
                                 settings->uints.video_shader_subframes
                                 );

   /* Initialize video driver */
   if (flags & DRIVER_VIDEO_MASK)
   {
      struct retro_hw_render_callback *hwr   =
         VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(video_st);

      video_st->frame_time_count = 0;

      video_driver_lock_new();
#ifdef HAVE_VIDEO_FILTER
      video_driver_filter_free();
#endif
      video_st->frame_cache_data  = NULL;
      if (!video_driver_init_internal(&video_is_threaded,
               verbosity_enabled))
         retroarch_fail(1, "video_driver_init_internal()");

      if (   !(video_st->flags & VIDEO_FLAG_CACHE_CONTEXT_ACK)
            && hwr->context_reset)
         hwr->context_reset();
      video_st->flags            &= ~VIDEO_FLAG_CACHE_CONTEXT_ACK;
      runloop_st->frame_time_last = 0;
   }

   /* Initialize audio driver */
   if (flags & DRIVER_AUDIO_MASK)
   {
      audio_driver_init_internal(
            settings,
            audio_st->callback.callback != NULL);
      if (     audio_st->current_audio
            && audio_st->current_audio->device_list_new
            && audio_st->context_audio_data)
         audio_st->devices_list = (struct string_list*)
            audio_st->current_audio->device_list_new(
                  audio_st->context_audio_data);
   }

#ifdef HAVE_MICROPHONE
   if (flags & DRIVER_MICROPHONE_MASK)
   {
      microphone_driver_init_internal(settings);
      if (mic_st->driver && mic_st->driver->device_list_new && mic_st->driver_context)
         mic_st->devices_list = mic_st->driver->device_list_new(mic_st->driver_context);
   }
#endif

   /* Regular display refresh rate startup autoswitch based on content av_info */
   if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
   {
      struct retro_system_av_info *av_info = &video_st->av_info;
      float refresh_rate                   = av_info->timing.fps;
      unsigned autoswitch_refresh_rate     = settings->uints.video_autoswitch_refresh_rate;
      bool exclusive_fullscreen            = settings->bools.video_fullscreen && !settings->bools.video_windowed_fullscreen;
      bool windowed_fullscreen             = settings->bools.video_fullscreen &&  settings->bools.video_windowed_fullscreen;
      bool all_fullscreen                  = settings->bools.video_fullscreen ||  settings->bools.video_windowed_fullscreen;

      /* Making a switch from PC standard 60 Hz to NTSC 59.94 is excluded by the last condition. */
      if (     (refresh_rate > 0.0f)
            && !settings->uints.crt_switch_resolution
            && !settings->bools.vrr_runloop_enable
            && video_display_server_has_resolution_list()
            && (autoswitch_refresh_rate != AUTOSWITCH_REFRESH_RATE_OFF)
            && (fabs(settings->floats.video_refresh_rate - refresh_rate) > 1))
      {
         if (   ((autoswitch_refresh_rate == AUTOSWITCH_REFRESH_RATE_EXCLUSIVE_FULLSCREEN) && exclusive_fullscreen)
             || ((autoswitch_refresh_rate == AUTOSWITCH_REFRESH_RATE_WINDOWED_FULLSCREEN)  && windowed_fullscreen)
             || ((autoswitch_refresh_rate == AUTOSWITCH_REFRESH_RATE_ALL_FULLSCREEN)       && all_fullscreen))
         {
            bool video_switch_refresh_rate = false;

            video_switch_refresh_rate_maybe(&refresh_rate, &video_switch_refresh_rate);

            if (video_switch_refresh_rate && video_display_server_set_refresh_rate(refresh_rate))
            {
               int reinit_flags = DRIVER_AUDIO_MASK;
               video_monitor_set_refresh_rate(refresh_rate);
               /* Audio must reinit after successful rate switch */
               command_event(CMD_EVENT_REINIT, &reinit_flags);
            }
         }
      }
   }

   if (flags & DRIVER_CAMERA_MASK)
   {
      /* Only initialize camera driver if we're ever going to use it. */
      if (camera_st->active)
      {
         /* Resource leaks will follow if camera is initialized twice. */
         if (!camera_st->data)
         {
            if (!camera_driver_find_driver("camera driver",
                     verbosity_enabled))
               retroarch_fail(1, "find_camera_driver()");

            if (camera_st->driver)
            {
               camera_st->data = camera_st->driver->init(
                     *settings->arrays.camera_device ?
                     settings->arrays.camera_device : NULL,
                     camera_st->cb.caps,
                     settings->uints.camera_width ?
                     settings->uints.camera_width  : camera_st->cb.width,
                     settings->uints.camera_height ?
                     settings->uints.camera_height : camera_st->cb.height);

               if (!camera_st->data)
               {
                  RARCH_ERR("Failed to initialize camera driver. Will continue without camera.\n");
                  camera_st->active = false;
               }

               if (camera_st->cb.initialized)
                  camera_st->cb.initialized();
            }
         }
      }
   }

#ifdef HAVE_BLUETOOTH
   if (flags & DRIVER_BLUETOOTH_MASK)
      bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_INIT, NULL);
#endif
#ifdef HAVE_WIFI
   if ((flags & DRIVER_WIFI_MASK))
      wifi_driver_ctl(RARCH_WIFI_CTL_INIT, NULL);
#endif

   if (flags & DRIVER_LOCATION_MASK)
   {
      /* Only initialize location driver if we're ever going to use it. */
      if (location_st->active)
         if (!init_location(&runloop_state_get_ptr()->system,
                  &location_driver_st,
                  settings, verbosity_is_enabled()))
            location_st->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_st->current_video
       && video_st->current_video->gfx_widgets_enabled
       && video_st->current_video->gfx_widgets_enabled(video_st->data))
   {
      bool rarch_force_fullscreen = (video_st->flags &
         VIDEO_FLAG_FORCE_FULLSCREEN) ? true : false;
      bool video_is_fullscreen    = settings->bools.video_fullscreen
                                 || rarch_force_fullscreen;

      p_dispwidget->active= gfx_widgets_init(
            p_disp,
            anim_get_ptr(),
            settings,
            (uintptr_t)&p_dispwidget->active,
            video_is_threaded,
            video_st->width,
            video_st->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
         menu_contentless_cores_context_init();
      }
   }

   /* 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

   /* Keep non-throttled state as good as possible. */
   if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
      if (input_st && (input_st->flags & INP_FLAG_NONBLOCKING))
         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
}

void driver_uninit(int flags, enum driver_lifetime_flags lifetime_flags)
{
   runloop_state_t *runloop_st      = runloop_state_get_ptr();
   video_driver_state_t *video_st   = video_state_get_ptr();
   camera_driver_state_t *camera_st = camera_state_get_ptr();
#if defined(HAVE_GFX_WIDGETS)
   dispgfx_widget_t *p_dispwidget   = dispwidget_get_ptr();
#endif

   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_dispwidget->flags & DISPGFX_WIDGET_FLAG_INITED)
   {
      gfx_widgets_deinit(p_dispwidget->flags & DISPGFX_WIDGET_FLAG_PERSISTING);
      p_dispwidget->active = false;
   }
#endif

#ifdef HAVE_MENU
   if (flags & DRIVER_MENU_MASK)
   {
#ifdef HAVE_LIBRETRODB
      menu_explore_context_deinit();
#endif
      menu_contentless_cores_context_deinit();

#ifdef HAVE_CHEEVOS
      rcheevos_menu_reset_badges();
#endif

      menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
   }
#endif

   if ((flags & DRIVER_LOCATION_MASK))
      uninit_location(&runloop_st->system, &location_driver_st);

   if ((flags & DRIVER_CAMERA_MASK))
   {
      if (camera_st->data && camera_st->driver)
      {
         if (camera_st->cb.deinitialized)
            camera_st->cb.deinitialized();

         if (camera_st->driver->free)
            camera_st->driver->free(camera_st->data);
      }

      camera_st->data = NULL;
   }

#ifdef HAVE_BLUETOOTH
   if ((flags & DRIVER_BLUETOOTH_MASK))
      bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_DEINIT, NULL);
#endif
#ifdef HAVE_WIFI
   if ((flags & DRIVER_WIFI_MASK))
      wifi_driver_ctl(RARCH_WIFI_CTL_DEINIT, NULL);
#endif

   if (flags & DRIVER_LED)
      led_driver_free();

   if (flags & DRIVERS_VIDEO_INPUT)
   {
      video_driver_free_internal();
      VIDEO_DRIVER_LOCK_FREE(video_st);
      video_st->data              = NULL;
      video_st->frame_cache_data  = NULL;
   }

   if (flags & DRIVER_AUDIO_MASK)
      audio_driver_deinit();

   if ((flags & DRIVER_VIDEO_MASK))
      video_st->data = NULL;

   if ((flags & DRIVER_INPUT_MASK))
      input_state_get_ptr()->current_data = NULL;

   if ((flags & DRIVER_AUDIO_MASK))
      audio_state_get_ptr()->context_audio_data = NULL;

#ifdef HAVE_MICROPHONE
   if (flags & DRIVER_MICROPHONE_MASK)
      microphone_driver_deinit(lifetime_flags & DRIVER_LIFETIME_RESET);
#endif

   if (flags & DRIVER_MIDI_MASK)
      midi_driver_free();

#ifdef HAVE_LAKKA
   cpu_scaling_driver_free();
#endif
}

static void retroarch_deinit_drivers(struct retro_callbacks *cbs)
{
   input_driver_state_t *input_st  = input_state_get_ptr();
   video_driver_state_t *video_st  = video_state_get_ptr();
   camera_driver_state_t *camera_st= camera_state_get_ptr();
   location_driver_state_t
      *location_st                 = location_state_get_ptr();
   runloop_state_t     *runloop_st = runloop_state_get_ptr();
#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) */
   dispgfx_widget_t *p_dispwidget  = dispwidget_get_ptr();
   if (p_dispwidget->flags & DISPGFX_WIDGET_FLAG_INITED)
   {
      gfx_widgets_deinit(
            p_dispwidget->flags & DISPGFX_WIDGET_FLAG_PERSISTING);
      p_dispwidget->active         = false;
   }
#endif

#if defined(HAVE_CRTSWITCHRES)
   /* Switchres deinit */
   if (video_st->flags & VIDEO_FLAG_CRT_SWITCHING_ACTIVE)
      crt_destroy_modes(&video_st->crt_switch_st);
#endif

   /* Video */
   video_display_server_destroy();

   video_st->flags &= ~(VIDEO_FLAG_ACTIVE      | VIDEO_FLAG_USE_RGBA      |
                        VIDEO_FLAG_HDR_SUPPORT | VIDEO_FLAG_CACHE_CONTEXT |
                        VIDEO_FLAG_CACHE_CONTEXT_ACK
                       );
   video_st->record_gpu_buffer          = NULL;
   video_st->current_video              = NULL;
   video_st->frame_cache_data           = NULL;

   /* Audio */
   audio_state_get_ptr()->flags        &= ~AUDIO_FLAG_ACTIVE;
   audio_state_get_ptr()->current_audio = NULL;

   if (input_st)
   {
      /* Input */
      input_st->flags &= ~(INP_FLAG_KB_LINEFEED_ENABLE
                         | INP_FLAG_BLOCK_HOTKEY
                         | INP_FLAG_BLOCK_LIBRETRO_INPUT
                         | INP_FLAG_NONBLOCKING);

      memset(&input_st->turbo_btns, 0, sizeof(turbo_buttons_t));
      memset(&input_st->analog_requested, 0,
         sizeof(input_st->analog_requested));
      input_st->current_driver           = NULL;
   }

#ifdef HAVE_MENU
   menu_driver_destroy(
         menu_state_get_ptr());
#endif
   location_st->active                              = false;
   location_st->driver                              = NULL;

   /* Camera */
   camera_st->active                                = false;
   camera_st->driver                                = NULL;
   camera_st->data                                  = NULL;

#ifdef HAVE_BLUETOOTH
   bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_DESTROY, NULL);
#endif
#ifdef HAVE_WIFI
   wifi_driver_ctl(RARCH_WIFI_CTL_DESTROY, NULL);
#endif

   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;

   runloop_st->current_core.flags  &= ~RETRO_CORE_FLAG_INITED;
}

bool driver_ctl(enum driver_ctl_state state, void *data)
{
   driver_ctx_info_t      *drv = (driver_ctx_info_t*)data;

   switch (state)
   {
      case RARCH_DRIVER_CTL_SET_REFRESH_RATE:
         {
            float *hz                     = (float*)data;
            audio_driver_state_t
               *audio_st                  = audio_state_get_ptr();
            settings_t *settings          = config_get_ptr();
            runloop_state_t *runloop_st   = runloop_state_get_ptr();
            video_driver_state_t*video_st = video_state_get_ptr();
            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;
            unsigned
               black_frame_insertion      = settings->uints.video_black_frame_insertion;
            unsigned shader_subframes     = settings->uints.video_shader_subframes;

            video_monitor_set_refresh_rate(*hz);

            /* Sets audio monitor rate to new value. */
            audio_st->source_ratio_original   =
            audio_st->source_ratio_current    =
            (double)audio_output_sample_rate / audio_st->input;

            driver_adjust_system_rates(runloop_st, video_st, settings,
                                       vrr_runloop_enable,
                                       video_refresh_rate,
                                       audio_max_timing_skew,
                                       video_adaptive_vsync,
                                       video_swap_interval,
                                       black_frame_insertion,
                                       shader_subframes
                                       );
         }
         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;
}

access_state_t *access_state_get_ptr(void)
{
   return &access_state_st;
}

/* GLOBAL POINTER GETTERS */
global_t *global_get_ptr(void)
{
   return &global_driver_st;
}

uint16_t retroarch_get_flags(void)
{
   struct rarch_state *p_rarch = &rarch_st;
   return p_rarch->flags;
}

struct retro_perf_counter **retro_get_perf_counter_rarch(void)
{
   struct rarch_state *p_rarch = &rarch_st;
   return p_rarch->perf_counters_rarch;
}

unsigned retro_get_perf_count_rarch(void)
{
   struct rarch_state *p_rarch = &rarch_st;
   return p_rarch->perf_ptr_rarch;
}

void rarch_perf_register(struct retro_perf_counter *perf)
{
   struct rarch_state *p_rarch = &rarch_st;
   runloop_state_t *runloop_st = runloop_state_get_ptr();
   if (
            !runloop_st->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;
}

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[16];
   const char *exts                  = NULL;
   bool recursive                    = false;

   switch (type)
   {
      case DIR_LIST_AUTOCONFIG:
         exts = filter;
         break;
      case DIR_LIST_CORES:
         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;
#ifdef HAVE_MICROPHONE
      case STRING_LIST_MICROPHONE_DRIVERS:
         for (i = 0; microphone_drivers[i]; i++)
         {
            const char *opt  = microphone_drivers[i]->ident;
            *len            += strlen(opt) + 1;

            string_list_append(s, opt, attr);
         }
         break;
#endif
      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;
      case STRING_LIST_CLOUD_SYNC_DRIVERS:
#ifdef HAVE_CLOUDSYNC
         for (i = 0; cloud_sync_drivers[i]; i++)
         {
            const char *opt  = cloud_sync_drivers[i]->ident;
            *len            += strlen(opt) + 1;

            string_list_append(s, opt, attr);
         }
#endif
         break;
#ifdef HAVE_LAKKA
      case STRING_LIST_TIMEZONES:
         {
            const char *opt  = DEFAULT_TIMEZONE;
            *len            += STRLEN_CONST(DEFAULT_TIMEZONE) + 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)
            {
               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 (zone_desc && zone_desc[0] != '\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;
}

char *path_get_ptr(enum rarch_path_type type)
{
   struct rarch_state *p_rarch = &rarch_st;
   runloop_state_t *runloop_st = runloop_state_get_ptr();

   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 runloop_st->runtime_content_path_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 runloop_st->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_CONFIG_OVERRIDE:
         if (!path_is_empty(RARCH_PATH_CONFIG_OVERRIDE))
            return p_rarch->path_config_override_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;
   runloop_state_t *runloop_st = runloop_state_get_ptr();

   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 runloop_st->runtime_content_path_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 runloop_st->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_CONFIG_OVERRIDE:
         if (!path_is_empty(RARCH_PATH_CONFIG_OVERRIDE))
            return p_rarch->path_config_override_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(runloop_state_get_ptr()->runtime_content_path_basename);
      case RARCH_PATH_CORE_OPTIONS:
         return sizeof(p_rarch->path_core_options_file);
      case RARCH_PATH_SUBSYSTEM:
         return sizeof(runloop_state_get_ptr()->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_CONFIG_OVERRIDE:
         return sizeof(p_rarch->path_config_override_file);
      case RARCH_PATH_CORE:
         return sizeof(p_rarch->path_libretro);
      case RARCH_PATH_NONE:
      case RARCH_PATH_NAMES:
         break;
   }

   return 0;
}

bool path_set(enum rarch_path_type type, const char *path)
{
   struct rarch_state *p_rarch = &rarch_st;
   runloop_state_t *runloop_st = NULL;

   if (!path)
      return false;

   switch (type)
   {
      case RARCH_PATH_NAMES:
         runloop_path_set_basename(path);
         runloop_path_set_names();
         runloop_path_set_redirect(config_get_ptr(), p_rarch->dir_savefile,
               p_rarch->dir_savestate);
         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_CONFIG_OVERRIDE:
         strlcpy(p_rarch->path_config_override_file, path,
               sizeof(p_rarch->path_config_override_file));
         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_BASENAME:
         runloop_st = runloop_state_get_ptr();
         strlcpy(runloop_st->runtime_content_path_basename, path,
               sizeof(runloop_st->runtime_content_path_basename));
         break;
      case RARCH_PATH_SUBSYSTEM:
         runloop_st = runloop_state_get_ptr();
         strlcpy(runloop_st->subsystem_path, path,
               sizeof(runloop_st->subsystem_path));
         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_CONFIG:
         if (string_is_empty(p_rarch->path_config_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_CONFIG_OVERRIDE:
         if (string_is_empty(p_rarch->path_config_override_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_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(runloop_state_get_ptr()->runtime_content_path_basename))
            return true;
         break;
      case RARCH_PATH_SUBSYSTEM:
         if (string_is_empty(runloop_state_get_ptr()->subsystem_path))
            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;
   runloop_state_t *runloop_st = NULL;

   switch (type)
   {
      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_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_CONFIG_OVERRIDE:
         *p_rarch->path_config_override_file = '\0';
         break;
      case RARCH_PATH_NONE:
      case RARCH_PATH_NAMES:
         break;
      case RARCH_PATH_BASENAME:
         runloop_st = runloop_state_get_ptr();
         *runloop_st->runtime_content_path_basename = '\0';
         break;
      case RARCH_PATH_SUBSYSTEM:
         runloop_st = runloop_state_get_ptr();
         *runloop_st->subsystem_path = '\0';
         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_CONFIG_OVERRIDE);
   path_clear(RARCH_PATH_CORE_OPTIONS);
   path_clear(RARCH_PATH_BASENAME);
}

static void ram_state_to_file(void)
{
   char state_path[PATH_MAX_LENGTH];

   if (!content_ram_state_pending())
      return;

   state_path[0] = '\0';

   if (runloop_get_current_savestate_path(state_path, sizeof(state_path)))
      command_event(CMD_EVENT_RAM_STATE_TO_FILE, state_path);
}

enum rarch_content_type path_is_media_type(const char *path)
{
   char ext_lower[16];
   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_M4A:
#endif
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV) || defined(HAVE_AUDIOMIXER)
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_STB_VORBIS)
      case FILE_TYPE_OGG:
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_DR_MP3)
      case FILE_TYPE_MP3:
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_DR_FLAC)
      case FILE_TYPE_FLAC:
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_RWAV)
      case FILE_TYPE_WAV:
#endif
#if !defined(HAVE_AUDIOMIXER) || defined(HAVE_IBXM)
      case FILE_TYPE_MOD:
      case FILE_TYPE_S3M:
      case FILE_TYPE_XM:
#endif
         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
      case FILE_TYPE_NONE:
      default:
         break;
   }

   return RARCH_CONTENT_NONE;
}

/* 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(runloop_state_get_ptr()->savestate_dir);
      case RARCH_DIR_SAVEFILE:
         return sizeof(p_rarch->dir_savefile);
      case RARCH_DIR_CURRENT_SAVEFILE:
         return sizeof(runloop_state_get_ptr()->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;
   runloop_state_t *runloop_st = NULL;

   switch (type)
   {
      case RARCH_DIR_SAVEFILE:
         *p_rarch->dir_savefile = '\0';
         break;
      case RARCH_DIR_SAVESTATE:
         *p_rarch->dir_savestate = '\0';
         break;
      case RARCH_DIR_SYSTEM:
         *p_rarch->dir_system = '\0';
         break;
      case RARCH_DIR_NONE:
         break;
      case RARCH_DIR_CURRENT_SAVEFILE:
         runloop_st = runloop_state_get_ptr();
         *runloop_st->savefile_dir = '\0';
         break;
      case RARCH_DIR_CURRENT_SAVESTATE:
         runloop_st = runloop_state_get_ptr();
         *runloop_st->savestate_dir = '\0';
         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 runloop_state_get_ptr()->savefile_dir;
      case RARCH_DIR_SAVESTATE:
         return p_rarch->dir_savestate;
      case RARCH_DIR_CURRENT_SAVESTATE:
         return runloop_state_get_ptr()->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;
   runloop_state_t *runloop_st = NULL;

   switch (type)
   {
      case RARCH_DIR_SAVEFILE:
         strlcpy(p_rarch->dir_savefile, path,
               sizeof(p_rarch->dir_savefile));
         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;
      case RARCH_DIR_CURRENT_SAVEFILE:
         runloop_st = runloop_state_get_ptr();
         strlcpy(runloop_st->savefile_dir, path,
               sizeof(runloop_st->savefile_dir));
         break;
      case RARCH_DIR_CURRENT_SAVESTATE:
         runloop_st = runloop_state_get_ptr();
         strlcpy(runloop_st->savestate_dir, path,
               sizeof(runloop_st->savestate_dir));
         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;

      fill_pathname_expand_special(new_path,
            dir_path, sizeof(new_path));

      if (!path_is_directory(new_path))
         path_mkdir(new_path);
   }
}

#ifdef __APPLE__
static void dir_check_config(void)
{
   settings_t *settings            = config_get_ptr();

#define ENSURE_DIRECTORY(DIRPATH)               \
   if (!string_is_empty(DIRPATH))               \
      if (!path_is_directory(DIRPATH))          \
         path_mkdir(DIRPATH)

   /* the order here mimics the order of enum default_dirs */
   /* TODO: not all of these are necessary/used and many are created on demand, make fewer */
   ENSURE_DIRECTORY(settings->paths.directory_menu_content);
   ENSURE_DIRECTORY(settings->paths.directory_core_assets);
   ENSURE_DIRECTORY(settings->paths.directory_menu_config);
   ENSURE_DIRECTORY(settings->paths.directory_autoconfig);
   ENSURE_DIRECTORY(settings->paths.directory_audio_filter);
   ENSURE_DIRECTORY(settings->paths.directory_video_filter);
   ENSURE_DIRECTORY(settings->paths.directory_assets);
#ifdef _3DS
   ENSURE_DIRECTORY(settings->paths.directory_bottom_assets);
#endif
   ENSURE_DIRECTORY(settings->paths.directory_libretro);
   ENSURE_DIRECTORY(settings->paths.path_libretro_info);
   ENSURE_DIRECTORY(settings->paths.directory_overlay);
   ENSURE_DIRECTORY(settings->paths.directory_osk_overlay);
   /* PORT */
   ENSURE_DIRECTORY(settings->paths.directory_video_shader);
   ENSURE_DIRECTORY(dir_get_ptr(RARCH_DIR_SAVESTATE));
   ENSURE_DIRECTORY(dir_get_ptr(RARCH_DIR_SAVEFILE));
   ENSURE_DIRECTORY(settings->paths.directory_screenshot);
   ENSURE_DIRECTORY(settings->paths.directory_system);
   ENSURE_DIRECTORY(settings->paths.directory_playlist);
   ENSURE_DIRECTORY(settings->paths.directory_content_favorites);
   ENSURE_DIRECTORY(settings->paths.directory_content_history);
   ENSURE_DIRECTORY(settings->paths.directory_content_image_history);
   ENSURE_DIRECTORY(settings->paths.directory_content_music_history);
   ENSURE_DIRECTORY(settings->paths.directory_content_video_history);
   ENSURE_DIRECTORY(settings->paths.directory_input_remapping);
   ENSURE_DIRECTORY(settings->paths.directory_cache);
   ENSURE_DIRECTORY(settings->paths.directory_dynamic_wallpapers);
   ENSURE_DIRECTORY(settings->paths.directory_thumbnails);
   ENSURE_DIRECTORY(settings->paths.path_content_database);
   ENSURE_DIRECTORY(settings->paths.path_cheat_database);
   /* RECORD_CONFIG */
   /* RECORD_OUTPUT */
   ENSURE_DIRECTORY(settings->paths.log_dir);

#undef ENSURE_DIRECTORY
}
#endif

#ifdef HAVE_ACCESSIBILITY
bool is_accessibility_enabled(bool accessibility_enable, bool accessibility_enabled)
{
   return accessibility_enabled || accessibility_enable;
}
#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)
{
   struct rarch_state *p_rarch     = &rarch_st;
   runloop_state_t *runloop_st     = runloop_state_get_ptr();
   uico_driver_state_t *uico_st    = uico_state_get_ptr();
#if defined(HAVE_ACCESSIBILITY) || defined(HAVE_TRANSLATE)
   access_state_t *access_st       = access_state_get_ptr();
#endif
#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
   dispgfx_widget_t *p_dispwidget  = dispwidget_get_ptr();
#endif
#ifdef HAVE_MENU
   struct menu_state *menu_st      = menu_state_get_ptr();
#endif
   video_driver_state_t *video_st  = video_state_get_ptr();
   settings_t *settings            = config_get_ptr();
   recording_state_t *recording_st = recording_state_get_ptr();

   switch (cmd)
   {
      case CMD_EVENT_SAVE_FILES:
         event_save_files(runloop_st->flags & RUNLOOP_FLAG_USE_SRAM);
         break;
      case CMD_EVENT_OVERLAY_UNLOAD:
#ifdef HAVE_OVERLAY
         input_overlay_unload();
#endif
#ifdef HAVE_TRANSLATE
         translation_release(true);
#ifdef HAVE_GFX_WIDGETS
         if (p_dispwidget->ai_service_overlay_state != 0)
            gfx_widgets_ai_service_overlay_unload();
#endif
#endif
         break;
      case CMD_EVENT_OVERLAY_INIT:
#ifdef HAVE_OVERLAY
         input_overlay_init();
#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)
#ifdef HAVE_MENU
         video_shader_dir_check_shader(menu_st->driver_data, settings,
               &video_st->dir_shader_list, true, false);
#else
         video_shader_dir_check_shader(NULL, settings,
               &video_st->dir_shader_list, true, false);
#endif
#endif
         break;
      case CMD_EVENT_SHADER_PREV:
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
#ifdef HAVE_MENU
         video_shader_dir_check_shader(menu_st->driver_data, settings,
               &video_st->dir_shader_list, false, true);
#else
         video_shader_dir_check_shader(NULL, settings,
               &video_st->dir_shader_list, false, true);
#endif
#endif
         break;
      case CMD_EVENT_SHADER_TOGGLE:
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
         video_shader_toggle(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)
            {
               /* Unpause on second press */
               if (runloop_st->flags & RUNLOOP_FLAG_PAUSED)
               {
#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,
                           access_st->enabled))
                     navigation_say(
                           accessibility_enable,
                           accessibility_narrator_speech_speed,
                           (char*)msg_hash_to_str(MSG_UNPAUSED), 10);
#endif
#ifdef HAVE_GFX_WIDGETS
                  if (p_dispwidget->ai_service_overlay_state != 0)
                     gfx_widgets_ai_service_overlay_unload();
#endif
                  translation_release(true);
                  command_event(CMD_EVENT_UNPAUSE, NULL);
               }
               else /* Pause on call */
               {
                  command_event(CMD_EVENT_PAUSE, NULL);
                  command_event(CMD_EVENT_AI_SERVICE_CALL, 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 (access_st->ai_service_auto != 0)
               {
                  /* Auto mode was turned on, but we pressed the
                   * toggle button, so turn it off now. */
                  translation_release(true);
#ifdef HAVE_GFX_WIDGETS
                  if (p_dispwidget->ai_service_overlay_state != 0)
                     gfx_widgets_ai_service_overlay_unload();
#endif
               }
               else
               {
#ifdef HAVE_GFX_WIDGETS
                  if (p_dispwidget->ai_service_overlay_state != 0)
                     gfx_widgets_ai_service_overlay_unload();
                  else
#endif
                     command_event(CMD_EVENT_AI_SERVICE_CALL, NULL);
               }
            }
#endif
            break;
         }
      case CMD_EVENT_STREAMING_TOGGLE:
         if (recording_st->streaming_enable)
            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:
#if HAVE_RUNAHEAD
         {
            if (!core_info_current_supports_runahead())
            {
               runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_RUNAHEAD),
                     1, 100, false,
                     NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
               break;
            }

            settings->bools.run_ahead_enabled =
               !(settings->bools.run_ahead_enabled);

            if (settings->bools.run_ahead_enabled)
            {
               char msg[256];
               if (settings->bools.run_ahead_secondary_instance)
                  snprintf(msg, sizeof(msg),
                        msg_hash_to_str(MSG_RUNAHEAD_ENABLED_WITH_SECOND_INSTANCE),
                        settings->uints.run_ahead_frames);
               else
                  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);

               /* Disable preemptive frames */
               settings->bools.preemptive_frames_enable = false;
               preempt_deinit(runloop_st);
            }
            else
               runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_DISABLED),
                     1, 100, false,
                     NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
         }
#endif
         break;
      case CMD_EVENT_PREEMPT_TOGGLE:
#if HAVE_RUNAHEAD
         {
            bool old_warn   = settings->bools.preemptive_frames_hide_warnings;
            bool old_inited = runloop_st->preempt_data != NULL;

            /* Toggle with warnings shown */
            settings->bools.preemptive_frames_hide_warnings = false;
            settings->bools.preemptive_frames_enable        =
                  !(settings->bools.preemptive_frames_enable);
            command_event(CMD_EVENT_PREEMPT_UPDATE, NULL);

            settings->bools.preemptive_frames_hide_warnings = old_warn;

            if (old_inited && !runloop_st->preempt_data)
               runloop_msg_queue_push(msg_hash_to_str(MSG_PREEMPT_DISABLED),
                     1, 100, false,
                     NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
            else if (runloop_st->preempt_data)
            {
               char msg[256];

               snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_PREEMPT_ENABLED),
                        settings->uints.run_ahead_frames);
               runloop_msg_queue_push(
                     msg, 1, 100, false,
                     NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);

               /* Disable runahead */
               settings->bools.run_ahead_enabled        = false;
            }
            else /* Failed to init */
               settings->bools.preemptive_frames_enable = false;
         }
#endif
         break;
      case CMD_EVENT_PREEMPT_UPDATE:
#if HAVE_RUNAHEAD
         preempt_deinit(runloop_st);
         preempt_init(runloop_st);
#endif
         break;
      case CMD_EVENT_PREEMPT_RESET_BUFFER:
#if HAVE_RUNAHEAD
         if (runloop_st->preempt_data)
            runloop_st->preempt_data->frame_count = 0;
#endif
         break;
      case CMD_EVENT_RECORDING_TOGGLE:
         if (recording_st->enable)
            command_event(CMD_EVENT_RECORD_DEINIT, NULL);
         else
            command_event(CMD_EVENT_RECORD_INIT, NULL);
         break;
      case CMD_EVENT_SET_PER_GAME_RESOLUTION:
#if defined(GEKKO)
         {
            unsigned width = 0, height = 0;
            char desc[64] = {0};

            command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);

            if (video_driver_get_video_output_size(&width, &height, desc, sizeof(desc)))
            {
               char msg[128];

               video_driver_set_video_mode(width, height, true);

               if (width == 0 || height == 0)
                  strlcpy(msg, msg_hash_to_str(MSG_SCREEN_RESOLUTION_DEFAULT), sizeof(msg));
               else
               {
                  msg[0] = '\0';
                  if (!string_is_empty(desc))
                     snprintf(msg, sizeof(msg),
                        msg_hash_to_str(MSG_SCREEN_RESOLUTION_DESC),
                        width, height, desc);
                  else
                     snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_SCREEN_RESOLUTION_NO_DESC),
                        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 *sys_info     = &runloop_st->system;
            struct retro_system_info *sysinfo = &sys_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(
                     core_path,
                     sysinfo,
                     &sys_info->load_no_content))
               return false;

            if (!core_info_load(core_path))
            {
#ifdef HAVE_DYNAMIC
               return false;
#endif
            }
         }
         break;
      case CMD_EVENT_LOAD_CORE:
         runloop_st->subsystem_current_count = 0;
         content_clear_subsystem();
#ifdef HAVE_DYNAMIC
         if (!(command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL)))
            return false;
#else
         command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
         command_event(CMD_EVENT_QUIT, NULL);
#endif
         break;
#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB))
      case CMD_EVENT_LOAD_SECOND_CORE:
         if (   !(runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING)
             || !(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE))
            return false;

         if (!runloop_st->secondary_lib_handle)
         {
            if (!secondary_core_ensure_exists(runloop_st, settings))
            {
               runahead_secondary_core_destroy(runloop_st);
               runloop_st->flags &=
                  ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
               return false;
            }
         }
         break;
#endif
      case CMD_EVENT_LOAD_STATE:
         {
#ifdef HAVE_CHEEVOS
            if (rcheevos_hardcore_active())
            {
               runloop_msg_queue_push(msg_hash_to_str(MSG_CHEEVOS_LOAD_STATE_PREVENTED_BY_HARDCORE_MODE), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
               return false;
            }
#endif
#ifdef HAVE_NETWORKING
            if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL))
               return false;
#endif
            if (!command_event_main_state(cmd))
               return false;
            /* Run next frame to see the core output while paused */
            else if (runloop_st->flags & RUNLOOP_FLAG_PAUSED)
            {
               runloop_st->flags               &= ~RUNLOOP_FLAG_PAUSED;
               runloop_st->run_frames_and_pause = 1;
            }

#if HAVE_RUNAHEAD
            command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL);
#endif
         }
         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(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
             (settings,
              runloop_st->pending_windowed_scale))
            return false;
         break;
      case CMD_EVENT_MENU_TOGGLE:
#ifdef HAVE_MENU
         if (menu_st->flags & MENU_ST_FLAG_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(dispwidget_get_ptr()->active);
#else
         rcheevos_reset_game(false);
#endif
#endif
#ifdef HAVE_NETWORKING
         netplay_driver_ctl(RARCH_NETPLAY_CTL_RESET, NULL);
#endif
         /* Recalibrate frame delay target */
         if (settings->bools.video_frame_delay_auto)
            video_st->frame_delay_target = 0;

         /* Run a few frames to blank core output while paused */
         if (runloop_st->flags & RUNLOOP_FLAG_PAUSED)
         {
            runloop_st->flags               &= ~RUNLOOP_FLAG_PAUSED;
            runloop_st->run_frames_and_pause = 8;
         }

#if HAVE_RUNAHEAD
         command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL);
#endif
         return false;
      case CMD_EVENT_PLAY_REPLAY:
      {
#ifdef HAVE_BSV_MOVIE
         input_driver_state_t *input_st = input_state_get_ptr();
         char replay_path[PATH_MAX_LENGTH];
         bool res = true;
         /* TODO: Consider extending the current replay if we start recording during a playback */
         if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING)
            res = false;
         else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
            res = movie_stop(input_st);
         if (!runloop_get_current_replay_path(replay_path, sizeof(replay_path)))
            res = false;
         if (res)
            res = movie_start_playback(input_st, replay_path);
         if (!res)
         {
            const char *movie_fail_str        =
               msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE);
            runloop_msg_queue_push(movie_fail_str,
               1, 180, true,
               NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
            RARCH_ERR("%s.\n", movie_fail_str);
         }
         return res;
#else
         return false;
#endif
      }
      case CMD_EVENT_RECORD_REPLAY:
      {
#ifdef HAVE_BSV_MOVIE
         char replay_path[PATH_MAX_LENGTH];
         bool res                       = true;
         input_driver_state_t *input_st = input_state_get_ptr();
         int replay_slot                = settings->ints.replay_slot;
         if (settings->bools.replay_auto_index)
            replay_slot += 1;
         /* TODO: Consider cloning and extending the current replay if we start recording during a recording */
         if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING)
            res = false;
         else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
            res = movie_stop(input_st);
         if (!runloop_get_replay_path(replay_path, sizeof(replay_path), replay_slot))
            res = false;
         if (res)
            res = movie_start_record(input_st, replay_path);
         if (res && settings->bools.replay_auto_index)
            configuration_set_int(settings, settings->ints.replay_slot, replay_slot);
         if (!res)
         {
             const char *movie_rec_fail_str        =
               msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD);
            runloop_msg_queue_push(movie_rec_fail_str,
               1, 180, true,
               NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
            RARCH_ERR("%s.\n", movie_rec_fail_str);
         }
         return res;
#else
         break;
#endif
      }
      case CMD_EVENT_HALT_REPLAY:
#ifdef HAVE_BSV_MOVIE
         movie_stop(input_state_get_ptr());
#endif
         break;
      case CMD_EVENT_SAVE_STATE:
      case CMD_EVENT_SAVE_STATE_TO_RAM:
         {
            int state_slot            = settings->ints.state_slot;

            if (settings->bools.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(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_REPLAY_DECREMENT:
#ifdef HAVE_BSV_MOVIE
         {
            int slot            = settings->ints.replay_slot;

            /* Slot -1 is (auto) slot. */
            if (slot >= 0)
            {
               int new_slot = slot - 1;
               configuration_set_int(settings, settings->ints.replay_slot, new_slot);
            }
         }
#endif
         break;
      case CMD_EVENT_REPLAY_INCREMENT:
#ifdef HAVE_BSV_MOVIE
         {
            int new_slot        = settings->ints.replay_slot + 1;
            configuration_set_int(settings, settings->ints.replay_slot, new_slot);
         }
#endif
         break;
      case CMD_EVENT_TAKE_SCREENSHOT:
#ifdef HAVE_SCREENSHOTS
         {
            const char *dir_screenshot      = settings->paths.directory_screenshot;
            video_driver_state_t *video_st  = video_state_get_ptr();
            if (!take_screenshot(dir_screenshot,
                     path_get(RARCH_PATH_BASENAME),
                     false,
                     video_st->frame_cache_data && (video_st->frame_cache_data == RETRO_HW_FRAME_BUFFER_VALID),
                     false,
                     true))
               return false;
         }
#endif
         break;
      case CMD_EVENT_UNLOAD_CORE:
         {
            bool load_dummy_core            = data ? *(bool*)data : true;
            content_ctx_info_t content_info = {0};
            video_driver_state_t *video_st  = video_state_get_ptr();
            rarch_system_info_t *sys_info   = &runloop_st->system;
            uint8_t flags                   = content_get_flags();

            runloop_st->flags              &= ~RUNLOOP_FLAG_CORE_RUNNING;

            /* 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);

            runloop_runtime_log_deinit(runloop_st,
                  settings->bools.content_runtime_log,
                  settings->bools.content_runtime_log_aggregate,
                  settings->paths.directory_runtime_log,
                  settings->paths.directory_playlist);
            if (settings->bools.savestate_auto_save &&
                runloop_st->current_core_type != CORE_TYPE_DUMMY)
               command_event_save_auto_state();

            if (     (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CORE_ACTIVE)
                  || (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE)
                  || (runloop_st->flags & RUNLOOP_FLAG_REMAPS_GAME_ACTIVE)
                  || !string_is_empty(runloop_st->name.remapfile)
               )
            {
               input_remapping_deinit(settings->bools.remap_save_on_exit);
               input_remapping_set_defaults(true);
            }
            else
               input_remapping_restore_global_config(true, false);

#ifdef HAVE_CONFIGFILE
            if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE)
            {
               /* Reload the original config */
               config_unload_override();

               if (!settings->bools.video_fullscreen)
               {
                  input_driver_state_t *input_st = input_state_get_ptr();
                  if (     video_st->poke
                        && video_st->poke->show_mouse)
                     video_st->poke->show_mouse(video_st->data, true);
                  if (input_driver_ungrab_mouse())
                     input_st->flags &= ~INP_FLAG_GRAB_MOUSE_STATE;
               }
            }
#endif
#ifdef HAVE_CLOUDSYNC
            task_push_cloud_sync();
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
            runloop_st->runtime_shader_preset_path[0] = '\0';
#endif

            video_driver_restore_cached(settings);

            if (    (flags & CONTENT_ST_FLAG_IS_INITED)
                  && load_dummy_core)
            {
               if (!task_push_start_dummy_core(&content_info))
                  return false;
            }
#ifdef HAVE_PRESENCE
            {
               presence_userdata_t userdata;
               userdata.status = PRESENCE_NETPLAY_NETPLAY_STOPPED;
               command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
               userdata.status = PRESENCE_MENU;
               command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
            }
#endif
#ifdef HAVE_DYNAMIC
            path_clear(RARCH_PATH_CORE);
            runloop_system_info_free();
#endif
            {
               audio_driver_state_t
                  *audio_st                  = audio_state_get_ptr();
               audio_st->callback.callback   = NULL;
               audio_st->callback.set_state  = NULL;
            }
            if (flags & CONTENT_ST_FLAG_IS_INITED)
            {
               runloop_st->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 (!(menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE))
         {
            menu_state_get_ptr()->flags |= MENU_ST_FLAG_PENDING_CLOSE_CONTENT;
            command_event(CMD_EVENT_MENU_TOGGLE, NULL);
         }
         /* Check if we need to quit Retroarch */
         check_quit_on_close();
#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();
#endif
         break;
      case CMD_EVENT_REINIT_FROM_TOGGLE:
         video_st->flags &= ~VIDEO_FLAG_FORCE_FULLSCREEN;
         /* this fallthrough is on purpose, it should do
            a CMD_EVENT_REINIT too */
      case CMD_EVENT_REINIT:
         command_event_reinit(
               data ? *(const int*)data : DRIVERS_CMD_ALL);

#if defined(HAVE_AUDIOMIXER) && defined(HAVE_MENU)
         /* Menu sounds require audio reinit. */
         if (settings->bools.audio_enable_menu)
            command_event(CMD_EVENT_AUDIO_REINIT, NULL);
#endif

         /* Recalibrate frame delay target */
         if (settings->bools.video_frame_delay_auto)
            video_st->frame_delay_target = 0;

         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   = runloop_st->current_core_type == CORE_TYPE_DUMMY;

            if (core_type_is_dummy)
               return false;

            state_manager_event_deinit(&runloop_st->rewind_st,
                  &runloop_st->current_core);
         }
#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   = runloop_st->current_core_type == CORE_TYPE_DUMMY;

            if (core_type_is_dummy)
               return false;
#ifdef HAVE_CHEEVOS
            if (rcheevos_hardcore_active())
               return false;
#endif
#ifdef HAVE_NETWORKING
            if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_TIMESKIP, NULL))
               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(&runloop_st->rewind_st,
                        (unsigned)rewind_buf_size);
               }
            }
         }
#endif
         break;
      case CMD_EVENT_REWIND_REINIT:
#ifdef HAVE_REWIND
         /* to reinitialize the the rewind state manager, we have to recreate it.
          * the easiest way to do that is a full deinit followed by an init. */
         if (runloop_st->rewind_st.state != NULL)
         {
            command_event(CMD_EVENT_REWIND_DEINIT, NULL);
            command_event(CMD_EVENT_REWIND_INIT, NULL);
         }
#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 (runloop_st->flags & RUNLOOP_FLAG_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
            {
               if (autosave_init())
                  runloop_st->flags |=  RUNLOOP_FLAG_AUTOSAVE;
               else
                  runloop_st->flags &= ~RUNLOOP_FLAG_AUTOSAVE;
            }
         }
#endif
         break;
      case CMD_EVENT_AUDIO_STOP:
#if defined(HAVE_AUDIOMIXER) && defined(HAVE_MENU)
         if (     settings->bools.audio_enable_menu
               && menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
            return false;
#endif
         if (!audio_driver_stop())
            return false;
         break;
      case CMD_EVENT_AUDIO_START:
#if defined(HAVE_AUDIOMIXER) && defined(HAVE_MENU)
         if (     settings->bools.audio_enable_menu
               && menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
            return false;
#endif
         if (!audio_driver_start(runloop_st->flags &
                  RUNLOOP_FLAG_SHUTDOWN_INITIATED))
            return false;
         break;
#ifdef HAVE_MICROPHONE
      case CMD_EVENT_MICROPHONE_STOP:
         if (!microphone_driver_stop())
            return false;
         break;
      case CMD_EVENT_MICROPHONE_START:
         if (!microphone_driver_start())
            return false;
         break;
#endif
      case CMD_EVENT_AUDIO_MUTE_TOGGLE:
         {
            audio_driver_state_t
               *audio_st                       = audio_state_get_ptr();
            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);

            audio_st->mute_enable  =
               !audio_st->mute_enable;

#if defined(HAVE_GFX_WIDGETS)
            if (dispwidget_get_ptr()->active)
               gfx_widget_volume_update_and_show(
                     settings->floats.audio_volume,
                     audio_st->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_FPS_TOGGLE:
         settings->bools.video_fps_show = !(settings->bools.video_fps_show);
         break;
      case CMD_EVENT_STATISTICS_TOGGLE:
         settings->bools.video_statistics_show = !(settings->bools.video_statistics_show);
         break;
      case CMD_EVENT_OVERLAY_NEXT:
         /* Switch to the next available overlay screen. */
#ifdef HAVE_OVERLAY
         {
            bool *check_rotation           = (bool*)data;
            video_driver_state_t
               *video_st                   = video_state_get_ptr();
            input_driver_state_t *input_st = input_state_get_ptr();
            bool inp_overlay_auto_rotate   = settings->bools.input_overlay_auto_rotate;
            input_overlay_t *ol            = input_st->overlay_ptr;
            float input_overlay_opacity;
            if (!ol)
               return false;

            ol->index                      = ol->next_index;
            ol->active                     = &ol->overlays[ol->index];

            input_overlay_opacity          = (ol->flags & INPUT_OVERLAY_IS_OSK)
                  ? settings->floats.input_osk_overlay_opacity
                  : settings->floats.input_overlay_opacity;

            input_overlay_load_active(input_st->overlay_visibility,
                  ol, input_overlay_opacity);

            ol->flags                     |= INPUT_OVERLAY_BLOCKED;
            ol->next_index                 =
                  (unsigned)((ol->index + 1) % ol->size);

            /* Check orientation, if required */
            if (inp_overlay_auto_rotate)
               if (check_rotation)
                  if (*check_rotation)
                     input_overlay_auto_rotate_(
                           video_st->width,
                           video_st->height,
                           settings->bools.input_overlay_enable,
                           ol);
         }
#endif
         break;
      case CMD_EVENT_OSK_TOGGLE:
#ifdef HAVE_OVERLAY
         {
            settings_t *settings           = config_get_ptr();
            input_driver_state_t *input_st = input_state_get_ptr();

            if (input_st->flags & INP_FLAG_KB_LINEFEED_ENABLE)
               input_st->flags &= ~INP_FLAG_KB_LINEFEED_ENABLE;
            else if (!string_is_empty(settings->paths.path_osk_overlay))
               input_st->flags |=  INP_FLAG_KB_LINEFEED_ENABLE;
            else
               runloop_msg_queue_push(
                     msg_hash_to_str(MSG_OSK_OVERLAY_NOT_SET), 1, 100, false,
                     NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);

            command_event(CMD_EVENT_OVERLAY_INIT, NULL);

#ifdef HAVE_MENU
            /* Avoid stray menu input during transition */
            if (menu_st->flags & MENU_ST_FLAG_ALIVE)
            {
               menu_st->input_state.select_inhibit  = true;
               menu_st->input_state.cancel_inhibit  = true;
            }
#endif
         }
#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:
         recording_st->enable = false;
         streaming_set_state(false);
         if (!recording_deinit())
            return false;
         break;
      case CMD_EVENT_RECORD_INIT:
         recording_st->enable = true;
         if (!recording_init())
         {
            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;
            const char *_msg                       = NULL;
            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 || !playlist_config.capacity)
               return false;

            _msg = msg_hash_to_str(MSG_LOADING_HISTORY_FILE);

            /* Note: Sorting is disabled by default for
             * all content history playlists */
            RARCH_LOG("[Playlist]: %s: \"%s\".\n", _msg,
                  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,
                  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,
                  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,
                  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[16];
            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;

            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;
            video_driver_state_t
               *video_st                         = video_state_get_ptr();
            rarch_system_info_t *sys_info        = &runloop_st->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);

            runloop_runtime_log_deinit(runloop_st,
                  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(video_st);
#ifdef HAVE_CHEEVOS
            rcheevos_unload();
#endif
            runloop_event_deinit_core();

#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 (!(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE))
               runahead_clear_variables(runloop_st);

            /* Deallocate preemptive frames */
            preempt_deinit(runloop_st);
#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_st->system;
            input_driver_state_t *input_st = input_state_get_ptr();
            audio_driver_state_t *audio_st = audio_state_get_ptr();

            content_reset_savestate_backups();

            /* Ensure that disk control interface is reset */
            if (sys_info)
               disk_control_set_ext_callback(&sys_info->disk_control, NULL);

            /* Ensure that audio callback interface is reset */
            audio_st->callback.callback  = NULL;
            audio_st->callback.set_state = NULL;

            if (     !type
                  || !runloop_event_init_core(settings, input_st, *type,
                     p_rarch->dir_savefile, p_rarch->dir_savestate))
            {
               /* If core failed to initialise, audio callback
                * interface may be assigned invalid function
                * pointers -> ensure it is reset */
               audio_st->callback.callback  = NULL;
               audio_st->callback.set_state = NULL;
               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    = runloop_get_video_swap_interval(
                  settings->uints.video_swap_interval);
            video_driver_state_t
               *video_st              = video_state_get_ptr();

            if (video_st->current_video->set_nonblock_state)
               video_st->current_video->set_nonblock_state(
                     video_st->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;
            video_driver_state_t *video_st = video_state_get_ptr();
            input_driver_state_t *input_st = input_state_get_ptr();
            input_overlay_t *ol            = input_st->overlay_ptr;

            if (!ol)
               break;

            if (ol->flags & INPUT_OVERLAY_IS_OSK)
            {
               memset(&layout_desc, 0, sizeof(overlay_layout_desc_t));
               layout_desc.scale_landscape         = 1.0f;
               layout_desc.scale_portrait          = 1.0f;
               layout_desc.touch_scale             = 1.0f;
               layout_desc.auto_scale              = settings->bools.input_osk_overlay_auto_scale;
            }
            else
            {
               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(ol,
                  &layout_desc,
                  video_st->width,
                  video_st->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
         {
            input_driver_state_t *input_st = input_state_get_ptr();
            input_overlay_t *ol            = input_st->overlay_ptr;

            if (ol)
            {
               float input_overlay_opacity = (ol->flags & INPUT_OVERLAY_IS_OSK)
                     ? settings->floats.input_osk_overlay_opacity
                     : settings->floats.input_overlay_opacity;

               input_overlay_set_alpha_mod(input_st->overlay_visibility,
                        ol, input_overlay_opacity);
            }
         }
#endif
         break;
      case CMD_EVENT_OVERLAY_SET_EIGHTWAY_DIAGONAL_SENSITIVITY:
#ifdef HAVE_OVERLAY
         input_overlay_set_eightway_diagonal_sensitivity();
#endif
         break;
      case CMD_EVENT_AUDIO_REINIT:
         driver_uninit(DRIVER_AUDIO_MASK, DRIVER_LIFETIME_RESET);
         drivers_init(settings, DRIVER_AUDIO_MASK, DRIVER_LIFETIME_RESET, verbosity_is_enabled());
#if defined(HAVE_AUDIOMIXER)
         audio_driver_load_system_sounds();
#endif
         break;
#ifdef HAVE_MICROPHONE
      case CMD_EVENT_MICROPHONE_REINIT:
         driver_uninit(DRIVER_MICROPHONE_MASK, DRIVER_LIFETIME_RESET);
         drivers_init(settings, DRIVER_MICROPHONE_MASK, DRIVER_LIFETIME_RESET, verbosity_is_enabled());
         break;
#endif
      case CMD_EVENT_SHUTDOWN:
#if defined(__linux__) && !defined(ANDROID)
         if (settings->bools.config_save_on_exit)
         {
            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);
         }
#ifdef HAVE_LAKKA
         system("(sleep 1 && shutdown -P now) & disown");
#else
         command_event(CMD_EVENT_QUIT, NULL);
         system("shutdown -P now");
#endif /* HAVE_LAKKA */
#endif
         break;
      case CMD_EVENT_REBOOT:
#if defined(__linux__) && !defined(ANDROID)
         if (settings->bools.config_save_on_exit)
         {
            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);
         }
#ifdef HAVE_LAKKA
         system("(sleep 1 && shutdown -r now) & disown");
#else
         command_event(CMD_EVENT_QUIT, NULL);
         system("shutdown -r now");
#endif /* HAVE_LAKKA */
#endif
         break;
      case CMD_EVENT_RESUME:
#ifdef HAVE_MENU
         retroarch_menu_running_finished(false);
#endif
         if (uico_st->flags & UICO_ST_FLAG_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(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 true;
            }

            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);
#if TARGET_OS_TV
                     update_topshelf();
#endif
                  }
               }
            }
            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};
            unsigned i                     = 0;
#ifdef HAVE_MENU
            struct menu_state *menu_st     = menu_state_get_ptr();
#endif

            /* 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);

#ifdef HAVE_MENU
            /* Update playlist metadata */
            if (     menu_st->driver_ctx
                  && menu_st->driver_ctx->refresh_thumbnail_image)
               menu_st->driver_ctx->refresh_thumbnail_image(
                     menu_st->userdata, i);
#endif

            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(global_get_ptr());
         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_REMOVE_CURRENT_CONFIG_OVERRIDE_CORE:
#ifdef HAVE_CONFIGFILE
         command_event_remove_current_config(OVERRIDE_CORE);
#endif
         break;
      case CMD_EVENT_MENU_REMOVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR:
#ifdef HAVE_CONFIGFILE
         command_event_remove_current_config(OVERRIDE_CONTENT_DIR);
#endif
         break;
      case CMD_EVENT_MENU_REMOVE_CURRENT_CONFIG_OVERRIDE_GAME:
#ifdef HAVE_CONFIGFILE
         command_event_remove_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:
         {
            bool paused          = (runloop_st->flags & RUNLOOP_FLAG_PAUSED) ? true : false;
#ifdef HAVE_ACCESSIBILITY
            bool accessibility_enable
                                 = settings->bools.accessibility_enable;
            unsigned accessibility_narrator_speech_speed
                                 = settings->uints.accessibility_narrator_speech_speed;
#endif

#ifdef HAVE_NETWORKING
            if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
               break;
#endif

            paused               = !paused;

#ifdef HAVE_ACCESSIBILITY
            if (is_accessibility_enabled(
                  accessibility_enable,
                  access_st->enabled))
            {
               if (paused)
                  navigation_say(
                     accessibility_enable,
                     accessibility_narrator_speech_speed,
                     (char*)msg_hash_to_str(MSG_PAUSED), 10);
               else
                  navigation_say(
                     accessibility_enable,
                     accessibility_narrator_speech_speed,
                     (char*)msg_hash_to_str(MSG_UNPAUSED), 10);
            }
#endif
            if (paused)
               runloop_st->flags |=  RUNLOOP_FLAG_PAUSED;
            else
               runloop_st->flags &= ~RUNLOOP_FLAG_PAUSED;
            runloop_pause_checks();
         }
         break;
      case CMD_EVENT_UNPAUSE:
#ifdef HAVE_NETWORKING
         if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
            break;
#endif
         runloop_st->flags      &= ~RUNLOOP_FLAG_PAUSED;
         runloop_pause_checks();
         break;
      case CMD_EVENT_PAUSE:
#ifdef HAVE_NETWORKING
         if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL))
            break;
#endif
         runloop_st->flags      |= RUNLOOP_FLAG_PAUSED;
         runloop_pause_checks();
         break;
      case CMD_EVENT_MENU_PAUSE_LIBRETRO:
#ifdef HAVE_MENU
         {
#ifdef HAVE_NETWORKING
            bool menu_pause_libretro  = settings->bools.menu_pause_libretro
               && netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL);
#else
            bool menu_pause_libretro  = settings->bools.menu_pause_libretro;
#endif
            if (menu_pause_libretro)
            {
#ifdef HAVE_MICROPHONE
               command_event(CMD_EVENT_MICROPHONE_STOP, NULL);
#endif
            }
            else
            {
#ifdef HAVE_MICROPHONE
               command_event(CMD_EVENT_MICROPHONE_START, NULL);
#endif
            }
         }
#endif
         break;
#ifdef HAVE_NETWORKING
      case CMD_EVENT_NETPLAY_PING_TOGGLE:
         settings->bools.netplay_ping_show =
            !settings->bools.netplay_ping_show;
         break;
      case CMD_EVENT_NETPLAY_GAME_WATCH:
         netplay_driver_ctl(RARCH_NETPLAY_CTL_GAME_WATCH, NULL);
         break;
      case CMD_EVENT_NETPLAY_PLAYER_CHAT:
         netplay_driver_ctl(RARCH_NETPLAY_CTL_PLAYER_CHAT, NULL);
         break;
      case CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE:
         settings->bools.netplay_fade_chat =
            !settings->bools.netplay_fade_chat;
         break;
      case CMD_EVENT_NETPLAY_DEINIT:
         deinit_netplay();
         break;
      case CMD_EVENT_NETWORK_INIT:
         network_init();
         break;
         /* init netplay manually */
      case CMD_EVENT_NETPLAY_INIT:
         {
            char tmp_netplay_server[256];
            char tmp_netplay_session[256];
            char *netplay_server  = NULL;
            char *netplay_session = NULL;
            unsigned netplay_port = 0;

            command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);

            tmp_netplay_server[0]  = '\0';
            tmp_netplay_session[0] = '\0';
            if (netplay_decode_hostname(p_rarch->connect_host,
               tmp_netplay_server, &netplay_port, tmp_netplay_session,
               sizeof(tmp_netplay_server)))
            {
               netplay_server  = tmp_netplay_server;
               netplay_session = tmp_netplay_session;
            }

            if (p_rarch->connect_mitm_id)
                netplay_session = strdup(p_rarch->connect_mitm_id);

            if (p_rarch->connect_host)
            {
                free(p_rarch->connect_host);
                p_rarch->connect_host = NULL;
            }

            if (string_is_empty(netplay_server))
               netplay_server = settings->paths.netplay_server;
            if (!netplay_port)
               netplay_port   = settings->uints.netplay_port;

            if (!init_netplay(netplay_server, netplay_port, netplay_session))
            {
               command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
               if (p_rarch->connect_mitm_id)
               {
                  free(p_rarch->connect_mitm_id);
                  free(netplay_session);
                  p_rarch->connect_mitm_id = NULL;
                  netplay_session          = NULL;
               }
               return false;
            }

            if (p_rarch->connect_mitm_id)
            {
               free(p_rarch->connect_mitm_id);
               free(netplay_session);
               p_rarch->connect_mitm_id = NULL;
               netplay_session          = NULL;
            }

            /* Disable rewind & SRAM autosave if it was enabled
             * TODO/FIXME: Add a setting for these tweaks */
#ifdef HAVE_REWIND
            state_manager_event_deinit(&runloop_st->rewind_st,
                  &runloop_st->current_core);
#endif
#ifdef HAVE_THREADS
            autosave_deinit();
#endif
         }
         break;
         /* Initialize netplay via lobby when content is loaded */
      case CMD_EVENT_NETPLAY_INIT_DIRECT:
         {
            char netplay_server[256];
            char netplay_session[256];
            unsigned netplay_port = 0;

            command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);

            netplay_server[0]  = '\0';
            netplay_session[0] = '\0';
            netplay_decode_hostname((char*) data, netplay_server,
               &netplay_port, netplay_session, sizeof(netplay_server));

            if (!netplay_port)
               netplay_port = settings->uints.netplay_port;

            RARCH_LOG("[Netplay]: Connecting to %s|%d (direct)\n",
               netplay_server, netplay_port);

            if (!init_netplay(netplay_server, netplay_port, netplay_session))
            {
               command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
               return false;
            }

            /* Disable rewind if it was enabled
               TODO/FIXME: Add a setting for these tweaks */
#ifdef HAVE_REWIND
            state_manager_event_deinit(&runloop_st->rewind_st,
                  &runloop_st->current_core);
#endif
#ifdef HAVE_THREADS
            autosave_deinit();
#endif
         }
         break;
         /* init netplay via lobby when content is not loaded */
      case CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED:
         {
            char netplay_server[256];
            char netplay_session[256];
            unsigned netplay_port = 0;

            command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);

            netplay_server[0]  = '\0';
            netplay_session[0] = '\0';
            netplay_decode_hostname((char*) data, netplay_server,
               &netplay_port, netplay_session, sizeof(netplay_server));

            if (!netplay_port)
               netplay_port = settings->uints.netplay_port;

            RARCH_LOG("[Netplay]: Connecting to %s|%d (deferred)\n",
               netplay_server, netplay_port);

            if (!init_netplay_deferred(netplay_server, netplay_port, netplay_session))
            {
               command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
               return false;
            }

            /* Disable rewind if it was enabled
             * TODO/FIXME: Add a setting for these tweaks */
#ifdef HAVE_REWIND
            state_manager_event_deinit(&runloop_st->rewind_st,
                  &runloop_st->current_core);
#endif
#ifdef HAVE_THREADS
            autosave_deinit();
#endif
         }
         break;
      case CMD_EVENT_NETPLAY_ENABLE_HOST:
         {
            if (netplay_driver_ctl(RARCH_NETPLAY_CTL_USE_CORE_PACKET_INTERFACE, NULL))
            {
               netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
               command_event(CMD_EVENT_NETPLAY_INIT, NULL);
            }
            else if (!task_push_netplay_content_reload(NULL))
            {
#ifdef HAVE_DYNAMIC
               command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
               netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);

               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);
#else
               runloop_msg_queue_push(
                  msg_hash_to_str(MSG_NETPLAY_NEED_CONTENT_LOADED),
                  1, 480, true, NULL,
                  MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
#endif

               return false;
            }
#if HAVE_RUNAHEAD
            /* Deinit preemptive frames; not compatible with netplay */
            preempt_deinit(runloop_st);
#endif
         }
         break;
      case CMD_EVENT_NETPLAY_DISCONNECT:
         {
            bool rewind_enable         = settings->bools.rewind_enable;
            unsigned autosave_interval = settings->uints.autosave_interval;

            netplay_driver_ctl(RARCH_NETPLAY_CTL_DISCONNECT, NULL);
            netplay_driver_ctl(RARCH_NETPLAY_CTL_DISABLE, NULL);

#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))
         {
            if (     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_PING_TOGGLE:
      case CMD_EVENT_NETPLAY_GAME_WATCH:
      case CMD_EVENT_NETPLAY_PLAYER_CHAT:
      case CMD_EVENT_NETPLAY_FADE_CHAT_TOGGLE:
         return false;
#endif
      case CMD_EVENT_FULLSCREEN_TOGGLE:
         {
            audio_driver_state_t
               *audio_st              = audio_state_get_ptr();
            input_driver_state_t
               *input_st              = input_state_get_ptr();
            bool *userdata            = (bool*)data;
            bool video_fullscreen     = settings->bools.video_fullscreen;
            bool ra_is_forced_fs      = (video_st->flags &
               VIDEO_FLAG_FORCE_FULLSCREEN) ? true : false;
            bool new_fullscreen_state = !video_fullscreen && !ra_is_forced_fs;

            if (!video_driver_has_windowed())
               return false;

            audio_st->flags |= AUDIO_FLAG_SUSPENDED;
            video_st->flags |= VIDEO_FLAG_IS_SWITCHING_DISPLAY_MODE;

            /* 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)
               video_st->flags &= ~VIDEO_FLAG_FORCE_FULLSCREEN;

            /* If we go fullscreen we drop all drivers and
             * reinitialize to be safe. */
            command_event(CMD_EVENT_REINIT, NULL);
            if (video_fullscreen)
            {
               if (     video_st->poke
                     && video_st->poke->show_mouse)
                  video_st->poke->show_mouse(video_st->data, false);
               if (!settings->bools.video_windowed_fullscreen)
                  if (input_driver_grab_mouse())
                     input_st->flags |= INP_FLAG_GRAB_MOUSE_STATE;
            }
            else
            {
               if (     video_st->poke
                     && video_st->poke->show_mouse)
                  video_st->poke->show_mouse(video_st->data, true);
               if (!settings->bools.video_windowed_fullscreen)
                  if (input_driver_ungrab_mouse())
                     input_st->flags &= ~INP_FLAG_GRAB_MOUSE_STATE;
            }
#ifdef HAVE_OVERLAY
            input_overlay_check_mouse_cursor();
#endif

            video_st->flags &= ~VIDEO_FLAG_IS_SWITCHING_DISPLAY_MODE;
            audio_st->flags &= ~AUDIO_FLAG_SUSPENDED;

            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_st->system;

            if (string_is_empty(path) || !sys_info)
               return false;

            if (disk_control_enabled(&sys_info->disk_control))
            {
#if defined(HAVE_MENU)
               struct menu_state *menu_st = menu_state_get_ptr();
               /* Get initial disk eject state */
               bool initial_disk_ejected  = disk_control_get_eject_state(&sys_info->disk_control);
#endif
               /* Append disk image */
               bool success               =
                  command_event_disk_control_append_image(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_st->flags                 |=  MENU_ST_FLAG_ENTRIES_NEED_REFRESH
                                                  |  MENU_ST_FLAG_PREVENT_POPULATE;
#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_st->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;
#if defined(HAVE_MENU)
               struct menu_state *menu_st      = menu_state_get_ptr();
#endif

               if (show_msg)
                  verbose                      = *show_msg;

               if (!settings->bools.notification_show_disk_control)
                  verbose                      = false;

               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_st->flags                 |=  MENU_ST_FLAG_ENTRIES_NEED_REFRESH
                                               |  MENU_ST_FLAG_PREVENT_POPULATE;
#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_st->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;

               if (!settings->bools.notification_show_disk_control)
                  verbose     = false;

               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_st->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;

               if (!settings->bools.notification_show_disk_control)
                  verbose     = false;

               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_st->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:
         {
            unsigned i;

            for (i = 0; i < MAX_USERS; i++)
            {
               unsigned joy_idx = settings->uints.input_joypad_index[i];
               input_driver_set_rumble(i, joy_idx, RETRO_RUMBLE_STRONG, 0);
               input_driver_set_rumble(i, joy_idx, RETRO_RUMBLE_WEAK, 0);
            }
         }
         break;
      case CMD_EVENT_GRAB_MOUSE_TOGGLE:
         {
            bool ret              = false;
            input_driver_state_t
               *input_st          = input_state_get_ptr();
            bool grab_mouse_state = !(input_st->flags &
                  INP_FLAG_GRAB_MOUSE_STATE);

            if (grab_mouse_state)
            {
               if ((ret = input_driver_grab_mouse()))
                  input_st->flags |= INP_FLAG_GRAB_MOUSE_STATE;
            }
            else
            {
               if ((ret = input_driver_ungrab_mouse()))
                  input_st->flags &= ~INP_FLAG_GRAB_MOUSE_STATE;
            }

            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)
            {
               if (     video_st->poke
                     && video_st->poke->show_mouse)
                  video_st->poke->show_mouse(video_st->data, false);
            }
            else
            {
               if (     video_st->poke
                     && video_st->poke->show_mouse)
                  video_st->poke->show_mouse(video_st->data, true);
            }
         }
         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(desktop_menu_enable,
                  ui_companion_toggle, true);
         }
         break;
      case CMD_EVENT_GAME_FOCUS_TOGGLE:
         {
            bool video_fullscreen                         =
                  settings->bools.video_fullscreen
               || (video_st->flags & VIDEO_FLAG_FORCE_FULLSCREEN);
            enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_TOGGLE;
            input_driver_state_t
               *input_st                                  = input_state_get_ptr();
            bool current_enable_state                     = input_st->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 */
                  input_st->game_focus_state.enabled = false;
                  if (input_st->game_focus_state.enabled != current_enable_state)
                  {
                     apply_update = true;
                     show_message = true;
                  }
                  break;
               case GAME_FOCUS_CMD_ON:
                  /* Force game focus on */
                  input_st->game_focus_state.enabled = true;
                  if (input_st->game_focus_state.enabled != current_enable_state)
                  {
                     apply_update = true;
                     show_message = true;
                  }
                  break;
               case GAME_FOCUS_CMD_TOGGLE:
                  /* Invert current game focus state */
                  input_st->game_focus_state.enabled = !input_st->game_focus_state.enabled;
#ifdef HAVE_MENU
                  /* If menu is currently active, disable
                   * 'toggle on' functionality */
                  if (menu_st->flags & MENU_ST_FLAG_ALIVE)
                     input_st->game_focus_state.enabled = false;
#endif
                  if (input_st->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)
            {
               input_driver_state_t
                  *input_st          = input_state_get_ptr();

               if (input_st->game_focus_state.enabled)
               {
                  if (input_driver_grab_mouse())
                     input_st->flags |= INP_FLAG_GRAB_MOUSE_STATE;
                  if (     video_st->poke
                        && video_st->poke->show_mouse)
                     video_st->poke->show_mouse(video_st->data, false);
               }
               /* Ungrab only if windowed and auto mouse grab is disabled */
               else if (!video_fullscreen
                     && !settings->bools.input_auto_mouse_grab)
               {
                  if (input_driver_ungrab_mouse())
                     input_st->flags &= ~INP_FLAG_GRAB_MOUSE_STATE;
                  if (     video_st->poke
                        && video_st->poke->show_mouse)
                     video_st->poke->show_mouse(video_st->data, true);
               }

               if (input_st->game_focus_state.enabled)
                  input_st->flags |=  INP_FLAG_BLOCK_HOTKEY
                                   |  INP_FLAG_KB_MAPPING_BLOCKED;
               else
                  input_st->flags &= ~(INP_FLAG_BLOCK_HOTKEY
                                     | INP_FLAG_KB_MAPPING_BLOCKED);

               if (show_message)
                  runloop_msg_queue_push(
                        input_st->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",
                     input_st->game_focus_state.enabled ? "ON" : "OFF");
            }
         }
         break;
      case CMD_EVENT_VOLUME_UP:
         {
            audio_driver_state_t
               *audio_st              = audio_state_get_ptr();
            command_event_set_volume(settings, 0.5f,
#if defined(HAVE_GFX_WIDGETS)
                  dispwidget_get_ptr()->active,
#else
                  false,
#endif
                  audio_st->mute_enable);
         }
         break;
      case CMD_EVENT_VOLUME_DOWN:
         command_event_set_volume(settings, -0.5f,
#if defined(HAVE_GFX_WIDGETS)
               dispwidget_get_ptr()->active,
#else
               false,
#endif
               audio_state_get_ptr()->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:
         {
            video_driver_state_t
               *video_st                        = video_state_get_ptr();
            runloop_set_frame_limit(&video_st->av_info,
                  runloop_get_fastforward_ratio(
                     settings,
                     &runloop_st->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 = discord_state_get_ptr();
            if (!settings)
               return false;
            if (!discord_enable)
               return false;
            if (!discord_st->ready)
               discord_init(discord_app_id, p_rarch->launch_arguments);
         }
#endif
         break;
      case CMD_EVENT_PRESENCE_UPDATE:
         {
#ifdef HAVE_PRESENCE
            presence_userdata_t *userdata = NULL;
            if (!data)
               return false;

            userdata = (presence_userdata_t*)data;
            presence_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)
                  && audio_driver_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,
                        access_st->enabled))
                  navigation_say(
                        accessibility_enable,
                        accessibility_narrator_speech_speed,
                        (char*)msg_hash_to_str(MSG_AI_SERVICE_STOPPED),
                        10);
#endif
            }
            else
#endif
#ifdef HAVE_ACCESSIBILITY
            if (is_accessibility_enabled(
                     accessibility_enable,
                     access_st->enabled)
                  && (ai_service_mode == 2)
                  && is_narrator_running(accessibility_enable))
               navigation_say(
                     accessibility_enable,
                     accessibility_narrator_speech_speed,
                     (char*)msg_hash_to_str(MSG_AI_SERVICE_STOPPED),
                     10);
            else
#endif
            {
               bool paused = (runloop_st->flags & RUNLOOP_FLAG_PAUSED) ? true : false;
               if (data)
                  paused = *((bool*)data);

               run_translation_service(settings, paused);
            }
#endif
            break;
         }
      case CMD_EVENT_CONTROLLER_INIT:
         {
            rarch_system_info_t *sys_info = &runloop_st->system;
            if (sys_info)
               command_event_init_controllers(sys_info, settings,
                     settings->uints.input_max_users);
         }
         break;
      case CMD_EVENT_VRR_RUNLOOP_TOGGLE:
         settings->bools.vrr_runloop_enable = !(settings->bools.vrr_runloop_enable);
         runloop_msg_queue_push(
               msg_hash_to_str(
                     settings->bools.vrr_runloop_enable ? MSG_VRR_RUNLOOP_ENABLED
                                                        : MSG_VRR_RUNLOOP_DISABLED),
               1, 100, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
         break;
      case CMD_EVENT_NONE:
         return false;

      /* Deprecated */
      case CMD_EVENT_SEND_DEBUG_INFO:
         break;
   }

   return true;
}

/* FRONTEND */

void retroarch_override_setting_set(
      enum rarch_override_setting enum_idx, void *data)
{
   struct rarch_state            *p_rarch = &rarch_st;
#ifdef HAVE_NETWORKING
   net_driver_state_t *net_st  = networking_state_get_ptr();
#endif

   switch (enum_idx)
   {
      case RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE:
         {
            unsigned *val = (unsigned*)data;
            if (val)
            {
               unsigned                bit = *val;
               runloop_state_t *runloop_st = runloop_state_get_ptr();
               BIT256_SET(runloop_st->has_set_libretro_device, bit);
            }
         }
         break;
      case RARCH_OVERRIDE_SETTING_VERBOSITY:
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_VERBOSITY;
         break;
      case RARCH_OVERRIDE_SETTING_LIBRETRO:
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_LIBRETRO;
         break;
      case RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY:
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_LIBRETRO_DIRECTORY;
         break;
      case RARCH_OVERRIDE_SETTING_SAVE_PATH:
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_SAVE_PATH;
         break;
      case RARCH_OVERRIDE_SETTING_STATE_PATH:
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_STATE_PATH;
         break;
#ifdef HAVE_NETWORKING
     case RARCH_OVERRIDE_SETTING_NETPLAY_MODE:
         net_st->flags |= NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_MODE;
         break;
      case RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS:
         net_st->flags |= NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_IP_ADDRESS;
         break;
      case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
         net_st->flags |= NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_IP_PORT;
         break;
      case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
         net_st->flags |= NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_CHECK_FRAMES;
         break;
#endif
      case RARCH_OVERRIDE_SETTING_UPS_PREF:
#ifdef HAVE_PATCH
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_UPS_PREF;
#endif
         break;
      case RARCH_OVERRIDE_SETTING_BPS_PREF:
#ifdef HAVE_PATCH
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_BPS_PREF;
#endif
         break;
      case RARCH_OVERRIDE_SETTING_IPS_PREF:
#ifdef HAVE_PATCH
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_IPS_PREF;
#endif
         break;
       case RARCH_OVERRIDE_SETTING_XDELTA_PREF:
#if defined(HAVE_PATCH) && defined(HAVE_XDELTA)
           p_rarch->flags |= RARCH_FLAGS_HAS_SET_XDELTA_PREF;
#endif
           break;
      case RARCH_OVERRIDE_SETTING_LOG_TO_FILE:
         p_rarch->flags |= RARCH_FLAGS_HAS_SET_LOG_TO_FILE;
         break;
      case RARCH_OVERRIDE_SETTING_DATABASE_SCAN:
         p_rarch->flags |= RARCH_FLAGS_CLI_DATABASE_SCAN;
         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;
#ifdef HAVE_NETWORKING
   net_driver_state_t *net_st  = networking_state_get_ptr();
#endif

   switch (enum_idx)
   {
      case RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE:
         {
            unsigned *val = (unsigned*)data;
            if (val)
            {
               unsigned                bit = *val;
               runloop_state_t *runloop_st = runloop_state_get_ptr();
               BIT256_CLEAR(runloop_st->has_set_libretro_device, bit);
            }
         }
         break;
      case RARCH_OVERRIDE_SETTING_VERBOSITY:
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_VERBOSITY;
         break;
      case RARCH_OVERRIDE_SETTING_LIBRETRO:
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_LIBRETRO;
         break;
      case RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY:
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_LIBRETRO_DIRECTORY;
         break;
      case RARCH_OVERRIDE_SETTING_SAVE_PATH:
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_SAVE_PATH;
         break;
      case RARCH_OVERRIDE_SETTING_STATE_PATH:
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_STATE_PATH;
         break;
#ifdef HAVE_NETWORKING
    case RARCH_OVERRIDE_SETTING_NETPLAY_MODE:
         net_st->flags &= ~NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_MODE;
         break;
      case RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS:
         net_st->flags &= ~NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_IP_ADDRESS;
         break;
      case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
         net_st->flags &= ~NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_IP_PORT;
         break;
      case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
         net_st->flags &= ~NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_CHECK_FRAMES;
         break;
#endif
      case RARCH_OVERRIDE_SETTING_UPS_PREF:
#ifdef HAVE_PATCH
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_UPS_PREF;
#endif
         break;
      case RARCH_OVERRIDE_SETTING_BPS_PREF:
#ifdef HAVE_PATCH
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_BPS_PREF;
#endif
         break;
      case RARCH_OVERRIDE_SETTING_IPS_PREF:
#ifdef HAVE_PATCH
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_IPS_PREF;
#endif
         break;
       case RARCH_OVERRIDE_SETTING_XDELTA_PREF:
#if defined(HAVE_PATCH) && defined(HAVE_XDELTA)
           p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_XDELTA_PREF;
#endif
         break;
      case RARCH_OVERRIDE_SETTING_LOG_TO_FILE:
         p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_LOG_TO_FILE;
         break;
      case RARCH_OVERRIDE_SETTING_DATABASE_SCAN:
         p_rarch->flags &= ~RARCH_FLAGS_CLI_DATABASE_SCAN;
         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(
                  RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &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;
   runloop_state_t *runloop_st = runloop_state_get_ptr();

   content_deinit();

   runloop_path_deinit_subsystem();
   command_event(CMD_EVENT_RECORD_DEINIT, NULL);

   retro_main_log_file_deinit();

   runloop_st->flags &= ~(
                          RUNLOOP_FLAG_IS_SRAM_LOAD_DISABLED
                        | RUNLOOP_FLAG_IS_SRAM_SAVE_DISABLED
                        | RUNLOOP_FLAG_USE_SRAM);
#ifdef HAVE_PATCH
   p_rarch->flags    &= ~(
                         RARCH_FLAGS_BPS_PREF
                       | RARCH_FLAGS_IPS_PREF
                       | RARCH_FLAGS_UPS_PREF
                       | RARCH_FLAGS_XDELTA_PREF);
   runloop_st->flags &= ~RUNLOOP_FLAG_PATCH_BLOCKED;

#endif
#ifdef HAVE_CONFIGFILE
   p_rarch->flags    &= ~RARCH_FLAGS_BLOCK_CONFIG_READ;
   runloop_st->flags &= ~(RUNLOOP_FLAG_OVERRIDES_ACTIVE
                        | RUNLOOP_FLAG_REMAPS_CORE_ACTIVE
                        | RUNLOOP_FLAG_REMAPS_GAME_ACTIVE
                        | RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE);
#endif

   runloop_st->current_core.flags &= ~(RETRO_CORE_FLAG_HAS_SET_INPUT_DESCRIPTORS
                                     | RETRO_CORE_FLAG_HAS_SET_SUBSYSTEMS);

   global                                = global_get_ptr();
   path_clear_all();
   dir_clear_all();

   if (!string_is_empty(runloop_st->name.remapfile))
      free(runloop_st->name.remapfile);
   runloop_st->name.remapfile = NULL;
   *runloop_st->name.ups                 = '\0';
   *runloop_st->name.bps                 = '\0';
   *runloop_st->name.ips                 = '\0';
   *runloop_st->name.xdelta              = '\0';
   *runloop_st->name.savefile            = '\0';
   *runloop_st->name.savestate           = '\0';
   *runloop_st->name.replay              = '\0';
   *runloop_st->name.cheatfile           = '\0';
   *runloop_st->name.label               = '\0';

   if (global)
      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.
 *
 **/
void main_exit(void *args)
{
   struct rarch_state *p_rarch  = &rarch_st;
   runloop_state_t *runloop_st  = runloop_state_get_ptr();
#ifdef HAVE_MENU
   struct menu_state  *menu_st  = menu_state_get_ptr();
#endif
   settings_t     *settings     = config_get_ptr();

   video_driver_restore_cached(settings);

#if defined(HAVE_GFX_WIDGETS)
   /* Do not want display widgets to live any more. */
   dispwidget_get_ptr()->flags &= ~DISPGFX_WIDGET_FLAG_PERSISTING;
#endif
#ifdef HAVE_MENU
   /* Do not want menu context to live any more. */
   if (menu_st)
      menu_st->flags &= ~MENU_ST_FLAG_DATA_OWN;
#endif
   retroarch_ctl(RARCH_CTL_MAIN_DEINIT, NULL);

   if (runloop_st->perfcnt_enable)
   {
      RARCH_LOG("[PERF]: Performance counters (RetroArch):\n");
      runloop_log_counters(p_rarch->perf_counters_rarch, p_rarch->perf_ptr_rarch);
   }

#if defined(HAVE_LOGGER) && !defined(ANDROID)
   logger_shutdown();
#endif
#ifdef PS2
   /* PS2 frontend driver deinit also detaches filesystem,
    * so make sure logs are written in advance. */
   retro_main_log_file_deinit();
#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->flags                  &= ~RARCH_FLAGS_HAS_SET_USERNAME;
   runloop_st->flags               &= ~RUNLOOP_FLAG_IS_INITED;
   global_get_ptr()->error_on_init  = false;
#ifdef HAVE_CONFIGFILE
   p_rarch->flags                  &= ~RARCH_FLAGS_BLOCK_CONFIG_READ;
#endif

   runloop_msg_queue_deinit();
   driver_uninit(DRIVERS_CMD_ALL, 0);

   retro_main_log_file_deinit();

   retroarch_ctl(RARCH_CTL_STATE_FREE,  NULL);
   global_free(p_rarch);
   task_queue_deinit();

   ui_companion_driver_deinit();
   retroarch_config_deinit();

   frontend_driver_shutdown(false);

   retroarch_deinit_drivers(&runloop_st->retro_ctx);
   uico_state_get_ptr()->drv = NULL;
   frontend_driver_free();

   rtime_deinit();

#if defined(ANDROID)
   play_feature_delivery_deinit();
#endif

#if defined(HAVE_MIST)
   steam_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;
   runloop_state_t *runloop_st         = runloop_state_get_ptr();
   video_driver_state_t *video_st      = video_state_get_ptr();
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
   video_st->flags   |= VIDEO_FLAG_SHADER_PRESETS_NEED_RELOAD;
#endif
#ifdef HAVE_RUNAHEAD
   video_st->flags   |= VIDEO_FLAG_RUNAHEAD_IS_ACTIVE;
   runloop_st->flags |= (
                         RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE
                      |  RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
                      |  RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY
                        );
#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

#if defined(HAVE_MIST)
   steam_init();
#endif

   libretro_free_system_info(&runloop_st->system.info);
   command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
   retroarch_favorites_deinit();

   retroarch_config_init();

   retroarch_deinit_drivers(&runloop_st->retro_ctx);
   retroarch_ctl(RARCH_CTL_STATE_FREE,  NULL);
   global_free(p_rarch);

   frontend_driver_init_first(data);

   if (runloop_st->flags & RUNLOOP_FLAG_IS_INITED)
      driver_uninit(DRIVERS_CMD_ALL, 0);

#ifdef HAVE_THREAD_STORAGE
   sthread_tls_create(&p_rarch->rarch_tls);
   sthread_tls_set(&p_rarch->rarch_tls, MAGIC_POINTER);
#endif
   video_st->flags              |= VIDEO_FLAG_ACTIVE;
   audio_state_get_ptr()->flags |= AUDIO_FLAG_ACTIVE;

   {
      int i;
      for (i = 0; i < MAX_USERS; i++)
         input_config_set_device(i, RETRO_DEVICE_JOYPAD);
   }

   runloop_msg_queue_init();

   if (frontend_state_get_ptr()->current_frontend_ctx)
   {
      content_ctx_info_t info;

      info.argc            = argc;
      info.argv            = argv;
      info.args            = data;
      info.environ_get     = frontend_state_get_ptr()->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();
#if HAVE_CLOUDSYNC
   task_push_cloud_sync();
#endif
#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_MIST
   steam_poll();
#endif

#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"

#ifdef HAVE_RWEBAUDIO
void RWebAudioRecalibrateTime(void);
#endif

void emscripten_mainloop(void)
{
   int ret;
   static unsigned emscripten_frame_count = 0;
   video_driver_state_t *video_st         = video_state_get_ptr();
   settings_t        *settings            = config_get_ptr();
   input_driver_state_t *input_st         = input_state_get_ptr();
   bool black_frame_insertion             = settings->uints.video_black_frame_insertion;
   bool input_driver_nonblock_state       = input_st ?
      (input_st->flags & INP_FLAG_NONBLOCKING) : false;
   uint32_t runloop_flags                 = runloop_get_flags();
   bool runloop_is_slowmotion             = (runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false;
   bool runloop_is_paused                 = (runloop_flags & RUNLOOP_FLAG_PAUSED)     ? true : false;

#ifdef HAVE_RWEBAUDIO
   RWebAudioRecalibrateTime();
#endif

   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 (video_st->current_video_context.swap_buffers)
            video_st->current_video_context.swap_buffers(
                  video_st->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)
            || 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.
 *
 * Leaf function.
 *
 * @return 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 *sysinfo)
{
   if (!sysinfo)
      return;

   free((void*)sysinfo->library_name);
   free((void*)sysinfo->library_version);
   free((void*)sysinfo->valid_extensions);
   memset(sysinfo, 0, sizeof(*sysinfo));
}

#define _PSUPP_BUF(buf, len, var, name, desc) snprintf(buf + len, sizeof(buf) - len, "  %-15s - %s: %s", name, desc, var ? "yes\n" : "no\n")

static void retroarch_print_features(void)
{
   size_t _len;
   char buf[4096];

   frontend_driver_attach_console();

   _len  = strlcpy(buf, "Features:\n", sizeof(buf));
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_LIBRETRODB,      "LibretroDB",      "LibretroDB support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_COMMAND,         "Command",         "Command interface support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_NETWORK_COMMAND, "Network Command", "Network Command interface support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL,             "SDL",             "SDL input/audio/video drivers");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL2,            "SDL2",            "SDL2 input/audio/video drivers");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_X11,             "X11",             "X11 input/video drivers");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_UDEV,            "UDEV",            "UDEV/EVDEV input driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_WAYLAND,         "Wayland",         "Wayland input/video drivers");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_THREAD,          "Threads",         "Threading support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_VULKAN,          "Vulkan",          "Video driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_METAL,           "Metal",           "Video driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_OPENGL,          "OpenGL",          "Video driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_OPENGLES,        "OpenGLES",        "Video driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_XVIDEO,          "XVideo",          "Video driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_EGL,             "EGL",             "Video context driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_KMS,             "KMS",             "Video context driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_VG,              "OpenVG",          "Video context driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_COREAUDIO,       "CoreAudio",       "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_COREAUDIO3,      "CoreAudioV3",     "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_ALSA,            "ALSA",            "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_OSS,             "OSS",             "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_JACK,            "Jack",            "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_RSOUND,          "RSound",          "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_ROAR,            "RoarAudio",       "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_PULSE,           "PulseAudio",      "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_DSOUND,          "DirectSound",     "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_WASAPI,          "WASAPI",          "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_XAUDIO,          "XAudio2",         "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_AL,              "OpenAL",          "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_SL,              "OpenSL",          "Audio driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_7ZIP,            "7zip",            "7zip extraction support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_ZLIB,            "zlib",            "zip extraction support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_DYLIB,           "External",        "External filter and plugin support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_CG,              "Cg",              "Fragment/vertex shader driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_GLSL,            "GLSL",            "Fragment/vertex shader driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_HLSL,            "HLSL",            "Fragment/vertex shader driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL_IMAGE,       "SDL_image",       "SDL_image image loading");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_RPNG,            "rpng",            "PNG image loading/encoding");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_RJPEG,           "rjpeg",           "JPEG image loading");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_DYNAMIC,         "Dynamic",         "Dynamic run-time loading of libretro library");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_FFMPEG,          "FFmpeg",          "On-the-fly recording of gameplay with libavcodec");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_FREETYPE,        "FreeType",        "TTF font rendering driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_CORETEXT,        "CoreText",        "TTF font rendering driver");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_NETPLAY,         "Netplay",         "Peer-to-peer netplay");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_LIBUSB,          "Libusb",          "Libusb support");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_COCOA,           "Cocoa",           "Cocoa UI companion support (for OSX and/or iOS)");
   _len += _PSUPP_BUF(buf, _len, SUPPORTS_QT,              "Qt",              "Qt UI companion support");
   _PSUPP_BUF(buf, _len, SUPPORTS_V4L2,            "Video4Linux2",    "Camera driver");

   fputs(buf, stdout);
}

static void retroarch_print_version(void)
{
   char str[255];
   str[0] = '\0';

   frontend_driver_attach_console();

   fprintf(stdout, "%s - %s\n",
         msg_hash_to_str(MSG_PROGRAM),
         msg_hash_to_str(MSG_LIBRETRO_FRONTEND));

   fprintf(stdout, "Version: %s", PACKAGE_VERSION);
#ifdef HAVE_GIT_VERSION
   fprintf(stdout, " (Git %s)", retroarch_git_version);
#endif
   fprintf(stdout, " " __DATE__ "\n");

   retroarch_get_capabilities(RARCH_CAPABILITIES_COMPILER, str, 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)
{
   char buf[2048];
   buf[0] = '\0';

   frontend_driver_attach_console();
   fputs("\n", stdout);
   puts("===================================================================");
   retroarch_print_version();
   puts("===================================================================");
   fputs("\n", stdout);

   fprintf(stdout, "Usage: %s [OPTIONS]... [FILE]\n\n", arg0);

   strlcat(buf,
         "  -h, --help                     "
         "Show this help message.\n"
         "  -v, --verbose                  "
         "Verbose logging.\n"
         "      --log-file=FILE            "
         "Log messages to FILE.\n"
         "  -V, --version                  "
         "Show version.\n"
         "      --features                 "
         "Print 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

#ifdef HAVE_CONFIGFILE
   strlcat(buf, "  -c, --config=FILE              "
         "Path for config file.\n", sizeof(buf));
#ifdef _WIN32
   strlcat(buf, "                                 "
         "  Defaults to retroarch.cfg in same directory as retroarch.exe.\n"
         "                                 "
         "  If a default config is not found, the program will attempt to create one.\n"
         , sizeof(buf));
#else
   strlcat(buf,
         "                                 "
         "  By default looks for config in\n"
         "                                 "
         "  $XDG_CONFIG_HOME/retroarch/retroarch.cfg,\n"
         "                                 "
         "  $HOME/.config/retroarch/retroarch.cfg, and\n"
         "                                 "
         "  $HOME/.retroarch.cfg.\n"
         "                                 "
         "  If a default config is not found, the program will attempt to create one\n"
         "                                 "
         "  based on the skeleton config (" GLOBAL_CONFIG_DIR "/retroarch.cfg).\n"
         , sizeof(buf));
#endif
   strlcat(buf, "      --appendconfig=FILE        "
         "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));
#endif

   fputs(buf, stdout);
   buf[0] = '\0';

#ifdef HAVE_DYNAMIC
   strlcat(buf,
         "  -L, --libretro=FILE            "
         "Path to libretro implementation. Overrides any config setting.\n"
         "                                 "
         "  FILE may be one of the following:\n"
         "                                 "
         "  1. The full path to a core shared object library: path/to/<core_name>_libretro.<lib_ext>\n"
         "                                 "
         "  2. A core shared object library 'file name' (*): <core_name>_libretro.<lib_ext>\n"
         , sizeof(buf));
   strlcat(buf,
         "                                 "
         "  3. A core 'short name' (*): <core_name>_libretro OR <core_name>\n"
         "                                 "
         "  (*) If 'file name' or 'short name' do not correspond to an existing full file path,\n"
         "                                 "
         "  the configured frontend 'cores' directory will be searched for a match.\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"
         , sizeof(buf));
   strlcat(buf,
         "                                 "
         "  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));

#ifdef HAVE_LIBRETRODB
   strlcat(buf,
         "      --scan=PATH|FILE           "
         "Import content from path.\n"
         , sizeof(buf));
#endif

   strlcat(buf,
         "  -f, --fullscreen               "
         "Start the program in fullscreen regardless of config setting.\n"
         "      --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));

   fputs(buf, stdout);
   buf[0] = '\0';

   printf(      "  -N, --nodevice=PORT            "
         "Disconnects controller device connected to PORT (1 to %d).\n", MAX_USERS);
   printf(      "  -A, --dualanalog=PORT          "
         "Connect a DualAnalog controller to PORT (1 to %d).\n", MAX_USERS);
   printf(      "  -d, --device=PORT:ID           "
         "Connect a generic device into PORT of the device (1 to %d).\n", MAX_USERS);
   strlcat(buf,
         "                                 "
         "  Format is PORT:ID, where ID is a number corresponding to the particular device.\n"
         "  -M, --sram-mode=MODE           "
         "SRAM handling mode. MODE can be:\n"
         "                                 "
         "  'noload-nosave', '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"
         "  -C, --connect=HOST             "
         "Connect to netplay server as user 2.\n"
         "      --port=PORT                "
         "Port used to netplay. Default is 55435.\n"
         "      --mitm-session=ID           "
         "MITM (relay) session ID to join.\n"
         "      --nick=NICK                "
         "Picks a username (for use with netplay). Not mandatory.\n"
         "      --check-frames=NUMBER      "
         "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"
         "                                 "
         "  Available commands are listed if command is invalid.\n"
         , sizeof(buf));
#endif
#endif

#ifdef HAVE_BSV_MOVIE
   strlcat(buf,
         "  -P, --play-replay=FILE         "
         "Playback a replay file.\n"
         "  -R, --record-replay=FILE       "
         "Start recording a replay file from the beginning.\n"
         "      --eof-exit                 "
         "Exit upon reaching the end of the replay file.\n"
         , sizeof(buf));
#endif

   strlcat(buf,
         "  -r, --record=FILE              "
         "Path to record video file. Using mkv extension is recommended.\n"
         "      --recordconfig             "
         "Path to settings used during recording.\n"
         "      --size=WIDTHxHEIGHT        "
         "Overrides output video size when recording.\n"
         , sizeof(buf));

   fputs(buf, stdout);
   buf[0] = '\0';

   strlcat(buf,
         "  -D, --detach                   "
         "Detach program from the running console. Not relevant for all platforms.\n"
         "      --max-frames=NUMBER        "
         "Runs for the specified number of frames, then exits.\n"
         , sizeof(buf));



#ifdef HAVE_PATCH
   strlcat(buf,
         "  -U, --ups=FILE                 "
         "Specifies path for UPS patch that will be applied to content.\n"
         "      --bps=FILE                 "
         "Specifies path for BPS patch that will be applied to content.\n"
         "      --ips=FILE                 "
         "Specifies path for IPS patch that will be applied to content.\n"
         , sizeof(buf));
#ifdef HAVE_XDELTA
   strlcat(buf,
         "      --xdelta=FILE              "
         "Specifies path for Xdelta patch that will be applied to content.\n"
         , sizeof(buf));
#endif /* HAVE_XDELTA */
   strlcat(buf,
         "      --no-patch                 "
         "Disables all forms of content patching.\n"
         , sizeof(buf));
#endif /* HAVE_PATCH */

#ifdef HAVE_SCREENSHOTS
   strlcat(buf,
         "      --max-frames-ss            "
         "Takes a screenshot at the end of max-frames.\n"
         "      --max-frames-ss-path=FILE  "
         "Path to save the screenshot to at the end of max-frames.\n"
         , sizeof(buf));
#endif

#ifdef HAVE_ACCESSIBILITY
   strlcat(buf,
         "      --accessibility            "
         "Enables accessibilty for blind users using text-to-speech.\n"
         , sizeof(buf));
#endif

   strlcat(buf,
         "      --load-menu-on-error       "
         "Open menu instead of quitting if specified core or content fails to load.\n"
         "  -e, --entryslot=NUMBER         "
         "Slot from which to load an entry state.\n"
         "  -s, --save=PATH                "
         "Path for save files (*.srm). (DEPRECATED, use --appendconfig and savefile_directory)\n"
         "  -S, --savestate=PATH           "
         "Path for the save state files (*.state). (DEPRECATED, use --appendconfig and savestate_directory)\n"
         , sizeof(buf));

   fputs(buf, stdout);
}

#ifdef HAVE_DYNAMIC
static void retroarch_parse_input_libretro_path(const char *path, size_t path_len)
{
   settings_t *settings   = config_get_ptr();
   int path_stats         = 0;
   const char *path_ext   = NULL;
   core_info_t *core_info = NULL;
   const char *core_path  = NULL;
   bool core_path_matched = false;
   char tmp_path[PATH_MAX_LENGTH];

   if (string_is_empty(path))
      goto end;

   /* Check if path refers to a built-in core */
   if (string_ends_with_size(path, "builtin",
            path_len, STRLEN_CONST("builtin")))
   {
      RARCH_LOG("--libretro argument \"%s\" is a built-in core. Ignoring.\n",
            path);
      return;
   }

   path_stats = path_stat(path);

   /* Check if path is a directory */
   if ((path_stats & RETRO_VFS_STAT_IS_DIRECTORY) != 0)
   {
      path_clear(RARCH_PATH_CORE);

      configuration_set_string(settings,
            settings->paths.directory_libretro, path);

      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",
            path);
      return;
   }

   /* Check if path is a valid file */
   if ((path_stats & RETRO_VFS_STAT_IS_VALID) != 0)
   {
      core_path = path;
      goto end;
   }

   /* If path refers to a core file that does not exist,
    * check for its presence in the user-defined cores
    * directory */
   path_ext = path_get_extension(path);

   if (!string_is_empty(path_ext))
   {
      char core_ext[16];
      if (    string_is_empty(settings->paths.directory_libretro)
          || !frontend_driver_get_core_extension(core_ext,
               sizeof(core_ext))
          || !string_is_equal(path_ext, core_ext))
         goto end;

      fill_pathname_join_special(tmp_path, settings->paths.directory_libretro,
            path, sizeof(tmp_path));

      if (string_is_empty(tmp_path))
         goto end;

      path_stats = path_stat(tmp_path);

      if (   (path_stats & RETRO_VFS_STAT_IS_VALID)     != 0
          && (path_stats & RETRO_VFS_STAT_IS_DIRECTORY) == 0)
      {
         core_path         = tmp_path;
         core_path_matched = true;
         goto end;
      }
   }
   else
   {
      size_t _len;
      /* If path has no extension and contains no path
       * delimiters, check if it is a core 'name', matching
       * an existing file in the cores directory */
      if (find_last_slash(path))
         goto end;

      /* First check for built-in cores */
      if (string_is_equal(path, "ffmpeg"))
      {
         runloop_set_current_core_type(CORE_TYPE_FFMPEG, true);
         return;
      }
      else if (string_is_equal(path, "mpv"))
      {
         runloop_set_current_core_type(CORE_TYPE_MPV, true);
         return;
      }
      else if (string_is_equal(path, "imageviewer"))
      {
         runloop_set_current_core_type(CORE_TYPE_IMAGEVIEWER, true);
         return;
      }
      if (string_is_equal(path, "netretropad"))
      {
         runloop_set_current_core_type(CORE_TYPE_NETRETROPAD, true);
         return;
      }
      else if (string_is_equal(path, "videoprocessor"))
      {
         runloop_set_current_core_type(CORE_TYPE_VIDEO_PROCESSOR, true);
         return;
      }

      command_event(CMD_EVENT_CORE_INFO_INIT, NULL);

      _len = strlcpy(tmp_path, path, sizeof(tmp_path));

      if (!string_ends_with_size(tmp_path, "_libretro",
            _len, STRLEN_CONST("_libretro")))
         strlcpy(tmp_path       + _len,
               "_libretro",
               sizeof(tmp_path) - _len);

      if (  !core_info_find(tmp_path, &core_info)
          || string_is_empty(core_info->path))
         goto end;

      core_path         = core_info->path;
      core_path_matched = true;
   }

end:
   if (!string_is_empty(core_path))
   {
      path_set(RARCH_PATH_CORE, core_path);
      retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);

      /* We requested an explicit core, so use PLAIN core type. */
      runloop_set_current_core_type(CORE_TYPE_PLAIN, false);

      if (core_path_matched)
         RARCH_LOG("--libretro argument \"%s\" matches core file \"%s\".\n",
               path, core_path);
   }
   else
      RARCH_WARN("--libretro argument \"%s\" is not a file, core name"
            " or directory. Ignoring.\n",
            path ? path : "");
}
#endif

#if defined(HAVE_LIBRETRODB) && defined(HAVE_MENU)
void handle_dbscan_finished(retro_task_t *task,
      void *task_data, void *user_data, const char *err);
#endif

/**
 * 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;
   recording_state_t *recording_st = recording_state_get_ptr();
   video_driver_state_t *video_st  = video_state_get_ptr();
   runloop_state_t     *runloop_st = runloop_state_get_ptr();
   settings_t          *settings   = config_get_ptr();
#ifdef HAVE_ACCESSIBILITY
   access_state_t *access_st       = access_state_get_ptr();
#endif
#ifdef HAVE_LIBRETRODB
   retro_task_callback_t cb_task_dbscan
                                   = NULL;
#endif

   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
      { "play-replay",        1, NULL, 'P' },
      { "record-replay",      1, NULL, 'R' },
#endif
      { "sram-mode",          1, NULL, 'M' },
#ifdef HAVE_NETWORKING
      { "host",               0, NULL, 'H' },
      { "connect",            1, NULL, 'C' },
      { "mitm-session",       1, NULL, 'T' },
      { "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 },
#ifdef HAVE_XDELTA
      { "xdelta",             1, NULL, RA_OPT_XDELTA },
#endif /* HAVE_XDELTA */
      { "no-patch",           0, NULL, RA_OPT_NO_PATCH },
#endif /* HAVE_PATCH */
      { "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, 'V' /* 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 },
      { "entryslot",          1, NULL, 'e' },
#ifdef HAVE_LIBRETRODB
      { "scan",               1, NULL, RA_OPT_DATABASE_SCAN },
#endif
      { NULL, 0, NULL, 0 }
   };

   if (first_run)
   {
      size_t _len = 0;
      /* Copy the args into a buffer so launch arguments can be reused */
      for (i = 0; i < (unsigned)argc; i++)
      {
         _len += strlcpy(p_rarch->launch_arguments        + _len,
               argv[i], sizeof(p_rarch->launch_arguments) - _len);
         _len += strlcpy(p_rarch->launch_arguments        + _len,
               " ",     sizeof(p_rarch->launch_arguments) - _len);
      }
      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 (!(runloop_st->flags & RUNLOOP_FLAG_HAS_SET_CORE))
      runloop_set_current_core_type(CORE_TYPE_DUMMY, false);

   path_clear(RARCH_PATH_SUBSYSTEM);

   retroarch_override_setting_free_state();

   p_rarch->flags                 &= ~RARCH_FLAGS_HAS_SET_USERNAME;
#ifdef HAVE_PATCH
   p_rarch->flags                 &= ~(  RARCH_FLAGS_UPS_PREF | RARCH_FLAGS_IPS_PREF
                                       | RARCH_FLAGS_BPS_PREF | RARCH_FLAGS_XDELTA_PREF);
   *runloop_st->name.ups           = '\0';
   *runloop_st->name.bps           = '\0';
   *runloop_st->name.ips           = '\0';
   *runloop_st->name.xdelta        = '\0';
#endif
#ifdef HAVE_CONFIGFILE
   runloop_st->flags              &= ~RUNLOOP_FLAG_OVERRIDES_ACTIVE;
#endif
   global->cli_load_menu_on_error  = false;

   /* Make sure we can call retroarch_parse_input several times ... */
   optind                          = 0;
   optstring                       = "hs:fvVS:A:U:DN:d:e:"
      BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG CONFIG_FILE_ARG;

#if 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;

         /* Graceful failure with empty "-" parameter instead of allowing
          * to continue to segmentation fault by trying to load content */
         if (c == 0)
         {
            verbosity_enable();
            fprintf(stderr, "%s\n", msg_hash_to_str(MSG_ERROR_PARSING_ARGUMENTS));
            fprintf(stderr, "Try '%s --help' for more information\n", argv[0]);
            exit(EXIT_FAILURE);
         }

         switch (c)
         {
            case 'h':
               retroarch_print_help(argv[0]);
               exit(0);

            case 'V':
            case RA_OPT_VERSION:
               retroarch_print_version();
               exit(0);

            case RA_OPT_FEATURES:
               retroarch_print_features();
               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(runloop_st->name.savefile, optarg,
                     sizeof(runloop_st->name.savefile));
               retroarch_override_setting_set(
                     RARCH_OVERRIDE_SETTING_SAVE_PATH, NULL);
               break;

            case 'S':
               strlcpy(runloop_st->name.savestate, optarg,
                     sizeof(runloop_st->name.savestate));
               strlcpy(runloop_st->name.replay, optarg,
                     sizeof(runloop_st->name.replay));
               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(settings,
                     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;

            case RA_OPT_MENU:
               explicit_menu = true;
               break;
            case RA_OPT_DATABASE_SCAN:
#ifdef HAVE_LIBRETRODB
               verbosity_enable();
               retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_DATABASE_SCAN, NULL);
#endif
               break;

            /* Must handle '?' otherwise you get an infinite loop */
            case '?':
               frontend_driver_attach_console();
#ifdef _WIN32
               fprintf(stderr, "\n%s: unrecognized option '%s'\n", argv[0], argv[optind]);
#endif
               fprintf(stderr, "Try '%s --help' for more information\n", argv[0]);
               exit(EXIT_FAILURE);
               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. */
   runloop_st->current_core.flags &= ~(RETRO_CORE_FLAG_HAS_SET_INPUT_DESCRIPTORS
                                     | RETRO_CORE_FLAG_HAS_SET_SUBSYSTEMS);

   /* Load the config file now that we know what it is */
#ifdef HAVE_CONFIGFILE
   if (!(p_rarch->flags & RARCH_FLAGS_BLOCK_CONFIG_READ))
#endif
   {
      /* Workaround for libdecor 0.2.0 setting unwanted locale */
#if defined(HAVE_WAYLAND) && defined(HAVE_DYNAMIC)
      setlocale(LC_NUMERIC,"C");
#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(global_get_ptr());
   }

   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(
            settings->bools.log_to_file,
            settings->bools.log_to_file_timestamp,
            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(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(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':
               video_st->flags |= VIDEO_FLAG_FORCE_FULLSCREEN;
               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(1, "retroarch_parse_input()");
                  }
                  new_port = port - 1;
                  input_config_set_device(new_port, RETRO_DEVICE_NONE);
                  retroarch_override_setting_set(
                        RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &new_port);
               }
               break;

            case 'r':
               strlcpy(recording_st->path, optarg,
                     sizeof(recording_st->path));
               if (recording_st->enable)
                  recording_st->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))
               {
                  video_st->flags |= VIDEO_FLAG_CLI_SHADER_DISABLE;
                  break;
               }

               /* rebase on shader directory */
               if (path_is_absolute(optarg))
                  strlcpy(video_st->cli_shader_path, optarg,
                        sizeof(video_st->cli_shader_path));
               else
                  fill_pathname_join_special(video_st->cli_shader_path,
                        settings->paths.directory_video_shader,
                        optarg, sizeof(video_st->cli_shader_path));
#endif
               break;

#ifdef HAVE_DYNAMIC
            case 'L':
               retroarch_parse_input_libretro_path(optarg, strlen(optarg));
               break;
#endif
            case 'P':
#ifdef HAVE_BSV_MOVIE
               {
                  input_driver_state_t *input_st = input_state_get_ptr();
                  strlcpy(input_st->bsv_movie_state.movie_start_path, optarg,
                        sizeof(input_st->bsv_movie_state.movie_start_path));
                  input_st->bsv_movie_state.flags |=
                      BSV_FLAG_MOVIE_START_PLAYBACK;
                  input_st->bsv_movie_state.flags &=
                     ~BSV_FLAG_MOVIE_START_RECORDING;
               }
#endif
               break;
            case 'R':
#ifdef HAVE_BSV_MOVIE
               {
                  input_driver_state_t *input_st = input_state_get_ptr();
                  strlcpy(input_st->bsv_movie_state.movie_start_path, optarg,
                        sizeof(input_st->bsv_movie_state.movie_start_path));
                  input_st->bsv_movie_state.flags &=
                     ~BSV_FLAG_MOVIE_START_PLAYBACK;
                  input_st->bsv_movie_state.flags |=
                     BSV_FLAG_MOVIE_START_RECORDING;
               }
#endif
               break;

            case 'M':
               if (string_is_equal(optarg, "noload-nosave"))
                  runloop_st->flags |= RUNLOOP_FLAG_IS_SRAM_LOAD_DISABLED
                                     | RUNLOOP_FLAG_IS_SRAM_SAVE_DISABLED;
               else if (string_is_equal(optarg, "noload-save"))
                  runloop_st->flags |= RUNLOOP_FLAG_IS_SRAM_LOAD_DISABLED;
               else if (string_is_equal(optarg, "load-nosave"))
                  runloop_st->flags |= RUNLOOP_FLAG_IS_SRAM_SAVE_DISABLED;
               else if (string_is_not_equal(optarg, "load-save"))
               {
                  RARCH_ERR("Invalid argument in --sram-mode.\n");
                  retroarch_print_help(argv[0]);
                  retroarch_fail(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':
               retroarch_override_setting_set(
                     RARCH_OVERRIDE_SETTING_NETPLAY_MODE, NULL);
               netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
               p_rarch->connect_host = strdup(optarg);
               break;

            case 'T':
               p_rarch->connect_mitm_id = strdup(optarg);
               break;

            case RA_OPT_CHECK_FRAMES:
               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:
               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(1, "network_cmd_send()");
#endif
               break;
#endif

#endif

            case RA_OPT_BPS:
#ifdef HAVE_PATCH
               strlcpy(runloop_st->name.bps, optarg,
                     sizeof(runloop_st->name.bps));
               p_rarch->flags |= RARCH_FLAGS_BPS_PREF;
               retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_BPS_PREF, NULL);
#endif
               break;

            case 'U':
#ifdef HAVE_PATCH
               strlcpy(runloop_st->name.ups, optarg,
                     sizeof(runloop_st->name.ups));
               p_rarch->flags |= RARCH_FLAGS_UPS_PREF;
               retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_UPS_PREF, NULL);
#endif
               break;

            case RA_OPT_IPS:
#ifdef HAVE_PATCH
               strlcpy(runloop_st->name.ips, optarg,
                     sizeof(runloop_st->name.ips));
               p_rarch->flags |= RARCH_FLAGS_IPS_PREF;
               retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_IPS_PREF, NULL);
#endif
               break;
             case RA_OPT_XDELTA:
#if defined(HAVE_PATCH) && defined(HAVE_XDELTA)
                 strlcpy(runloop_st->name.xdelta, optarg,
                     sizeof(runloop_st->name.xdelta));
               p_rarch->flags |= RARCH_FLAGS_XDELTA_PREF;
               retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_XDELTA_PREF, NULL);
#endif
                 break;
            case RA_OPT_NO_PATCH:
#ifdef HAVE_PATCH
               runloop_st->flags |= RUNLOOP_FLAG_PATCH_BLOCKED;
#endif
               break;

            case 'D':
               frontend_driver_detach_console();
               break;

            case RA_OPT_MENU:
               explicit_menu = true;
               break;

            case RA_OPT_NICK:
               p_rarch->flags |= RARCH_FLAGS_HAS_SET_USERNAME;

               configuration_set_string(settings,
                     settings->paths.username, optarg);
               break;

            case RA_OPT_SIZE:
               if (sscanf(optarg, "%ux%u",
                        &recording_st->width,
                        &recording_st->height) != 2)
               {
                  RARCH_ERR("Wrong format for --size.\n");
                  retroarch_print_help(argv[0]);
                  retroarch_fail(1, "retroarch_parse_input()");
               }
               break;

            case RA_OPT_RECORDCONFIG:
               strlcpy(recording_st->config, optarg,
                     sizeof(recording_st->config));
               break;

            case RA_OPT_MAX_FRAMES:
               runloop_st->max_frames  = (unsigned)strtoul(optarg, NULL, 10);
               break;

            case RA_OPT_MAX_FRAMES_SCREENSHOT:
#ifdef HAVE_SCREENSHOTS
               runloop_st->flags |= RUNLOOP_FLAG_MAX_FRAMES_SCREENSHOT;
#endif
               break;

            case RA_OPT_MAX_FRAMES_SCREENSHOT_PATH:
#ifdef HAVE_SCREENSHOTS
               strlcpy(runloop_st->max_frames_screenshot_path,
                     optarg,
                     sizeof(runloop_st->max_frames_screenshot_path));
#endif
               break;

            case RA_OPT_SUBSYSTEM:
               path_set(RARCH_PATH_SUBSYSTEM, optarg);
               break;

            case RA_OPT_EOF_EXIT:
#ifdef HAVE_BSV_MOVIE
               {
                  input_driver_state_t *input_st   = input_state_get_ptr();
                  input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_EOF_EXIT;
               }
#endif
               break;

            case 'h':
            case 'V':
            case RA_OPT_VERSION:
            case RA_OPT_FEATURES:
#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(1, "retroarch_parse_input()");
            case RA_OPT_ACCESSIBILITY:
#ifdef HAVE_ACCESSIBILITY
               access_st->enabled = true;
#endif
               break;
            case RA_OPT_LOAD_MENU_ON_ERROR:
               global->cli_load_menu_on_error = true;
               break;
            case 'e':
               {
                  unsigned entry_state_slot = (unsigned)strtoul(optarg, NULL, 0);

                  if (entry_state_slot)
                     runloop_st->entry_state_slot = entry_state_slot;
                  else
                     RARCH_WARN("--entryslot argument \"%s\" is not a valid "
                        "entry state slot index. Ignoring.\n", optarg);
               }
               break;
            case RA_OPT_DATABASE_SCAN:
#ifdef HAVE_LIBRETRODB
               {
                  settings_t *settings           = config_get_ptr();
                  bool show_hidden_files         = settings->bools.show_hidden_files;
                  const char *directory_playlist = settings->paths.directory_playlist;
                  const char *path_content_db    = settings->paths.path_content_database;
                  int reinit_flags               = DRIVERS_CMD_ALL &
                        ~(DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK | DRIVER_MICROPHONE_MASK | DRIVER_INPUT_MASK | DRIVER_MIDI_MASK);

                  drivers_init(settings, reinit_flags, 0, false);
                  retroarch_init_task_queue();

#ifdef HAVE_MENU
                  if (explicit_menu)
                     cb_task_dbscan = handle_dbscan_finished;
#endif

                  task_push_dbscan(
                        directory_playlist,
                        path_content_db,
                        optarg, path_is_directory(optarg),
                        show_hidden_files,
                        cb_task_dbscan);

                  if (!explicit_menu)
                  {
                     task_queue_wait(NULL, NULL);
                     driver_uninit(DRIVERS_CMD_ALL, 0);
                     exit(0);
                  }
               }
#endif
               break;
            default:
               RARCH_ERR("%s\n", msg_hash_to_str(MSG_ERROR_PARSING_ARGUMENTS));
               retroarch_fail(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(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. */
         runloop_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. */
      runloop_set_current_core_type(CORE_TYPE_PLAIN, false);

      if (subsystem_path_is_empty)
         path_set(RARCH_PATH_NAMES, (const char*)argv[optind]);
      else
         runloop_path_set_special(argv + optind, argc - optind);

      /* Register that content has been set via the
       * command line interface */
      cli_content_set = true;
   }
   else if (runloop_st->entry_state_slot)
   {
      runloop_st->entry_state_slot = 0;
      RARCH_WARN("Trying to load entry state without content. Ignoring.\n");
   }
   #ifdef HAVE_BSV_MOVIE
   if (runloop_st->entry_state_slot)
   {
     input_driver_state_t *input_st = input_state_get_ptr();
     if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK)
     {
        runloop_st->entry_state_slot = 0;
        RARCH_WARN("Trying to load entry state while replay playback is active. Ignoring entry state.\n");
     }
   }
   #endif


   /* Check whether a core has been set via the
    * command line interface */
   cli_core_set = (runloop_st->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(runloop_st->name.savefile))
      dir_set(RARCH_DIR_SAVEFILE, runloop_st->name.savefile);

   if (retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_STATE_PATH, NULL) &&
         path_is_directory(runloop_st->name.savestate))
      dir_set(RARCH_DIR_SAVESTATE, runloop_st->name.savestate);

   return verbosity_enabled;
}

/**
 * retroarch_validate_cpu_features:
 *
 * 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(void)
{
   uint64_t cpu = cpu_features_get();
   (void)cpu;

#ifdef __MMX__
   if (!(cpu & RETRO_SIMD_MMX))
      FAIL_CPU("MMX");
#endif
#ifdef __SSE__
   if (!(cpu & RETRO_SIMD_SSE))
      FAIL_CPU("SSE");
#endif
#ifdef __SSE2__
   if (!(cpu & RETRO_SIMD_SSE2))
      FAIL_CPU("SSE2");
#endif
#ifdef __AVX__
   if (!(cpu & RETRO_SIMD_AVX))
      FAIL_CPU("AVX");
#endif
}

/**
 * retroarch_main_init:
 * @argc                 : Count of (commandline) arguments.
 * @argv                 : (Commandline) arguments.
 *
 * Initializes the program.
 *
 * @return 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;
   runloop_state_t *runloop_st   = runloop_state_get_ptr();
   input_driver_state_t
      *input_st                  = input_state_get_ptr();
   video_driver_state_t*video_st = video_state_get_ptr();
   settings_t *settings          = config_get_ptr();
   recording_state_t
      *recording_st              = recording_state_get_ptr();
   global_t            *global   = global_get_ptr();
#ifdef HAVE_ACCESSIBILITY
   access_state_t *access_st     = access_state_get_ptr();
   bool accessibility_enable     = false;
   unsigned accessibility_narrator_speech_speed = 0;
#endif
#ifdef HAVE_MENU
   struct menu_state *menu_st    = menu_state_get_ptr();
#endif

   input_st->osk_idx             = OSK_LOWERCASE_LATIN;
   video_st->flags              |= VIDEO_FLAG_ACTIVE;
   audio_state_get_ptr()->flags |= AUDIO_FLAG_ACTIVE;

   if (setjmp(global->error_sjlj_context) > 0)
   {
      RARCH_ERR("%s: \"%s\"\n",
            msg_hash_to_str(MSG_FATAL_ERROR_RECEIVED_IN),
            global_get_ptr()->error_string);
      goto error;
   }

   global->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,
         global_get_ptr(), argc, argv);

#ifdef __APPLE__
   /* This doesn't have to be apple specific but it's currently the only
    * platform that doesn't call dir_check_defaults(). This does exactly the
    * same thing as dir_check_defaults() except that it makes the directories
    * pointed at by the config file rather than the defaults, which is why it
    * comes right after parsing the config. osx users seem to be more likely to
    * want to do change the default directories.
    */
   dir_check_config();
#endif

#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,
            access_st->enabled))
      navigation_say(
            accessibility_enable,
            accessibility_narrator_speech_speed,
            (char*)msg_hash_to_str(MSG_ACCESSIBILITY_STARTUP),
            10);
#endif

   if (verbosity_enabled)
   {
      {
         char str_output[256];
         const char *cpu_model  = frontend_driver_get_cpu_model_name();
         size_t _len = strlcpy(str_output,
               "=== Build =======================================\n",
               sizeof(str_output));

         if (!string_is_empty(cpu_model))
         {
            /* TODO/FIXME - localize */
            _len += strlcpy(str_output + _len,
                  FILE_PATH_LOG_INFO " CPU Model Name: ",
                  sizeof(str_output)   - _len);
            _len              += strlcpy(str_output + _len, cpu_model,
                                 sizeof(str_output) - _len);
            str_output[  _len] = '\n';
            str_output[++_len] = '\0';
         }

         RARCH_LOG_OUTPUT("%s", str_output);
      }

      {
         char str_output[256];
         char str[128];
         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 " Version: " PACKAGE_VERSION "\n"
               FILE_PATH_LOG_INFO " Git: %s" "\n"
               FILE_PATH_LOG_INFO " Built: " __DATE__ "\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 " Version: " PACKAGE_VERSION "\n"
               FILE_PATH_LOG_INFO " Built: " __DATE__ "\n"
               FILE_PATH_LOG_INFO " =================================================\n",
               msg_hash_to_str(MSG_CAPABILITIES),
               str);
#endif
         RARCH_LOG_OUTPUT("%s", 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();
   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);
                  runloop_set_current_core_type(CORE_TYPE_MPV, false);
#elif defined(HAVE_FFMPEG)
                  retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_LIBRETRO, NULL);
                  runloop_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);
                  runloop_set_current_core_type(CORE_TYPE_IMAGEVIEWER, false);
               }
               break;
#endif
            default:
               break;
         }
      }
   }

   /* Pre-initialize all drivers
    * Attempts to find a default driver for
    * all driver types.
    */
   if (!(audio_driver_find_driver(settings,
         "audio driver", verbosity_enabled)))
      retroarch_fail(1, "audio_driver_find()");
   if (!video_driver_find_driver(settings,
         "video driver", verbosity_enabled))
      retroarch_fail(1, "video_driver_find_driver()");
   if (!input_driver_find_driver(settings,
         "input driver", verbosity_enabled))
      retroarch_fail(1, "input_driver_find_driver()");

   if (!camera_driver_find_driver("camera driver", verbosity_enabled))
      retroarch_fail(1, "find_camera_driver()");

#ifdef HAVE_BLUETOOTH
   bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_WIFI
   wifi_driver_ctl(RARCH_WIFI_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_CLOUDSYNC
   cloud_sync_find_driver(settings,
         "cloud sync driver", verbosity_enabled);
#endif
   location_driver_find_driver(settings,
         &location_driver_st,
         "location driver", verbosity_enabled);
#ifdef HAVE_MENU
   {
      if (!(menu_st->driver_ctx = menu_driver_find_driver(settings,
                  "menu driver", verbosity_enabled)))
         retroarch_fail(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 (runloop_st->flags & RUNLOOP_FLAG_HAS_SET_CORE)
   {
      runloop_st->flags &= ~RUNLOOP_FLAG_HAS_SET_CORE;
      if (!command_event(CMD_EVENT_CORE_INIT,
               &runloop_st->explicit_current_core_type))
         init_failed = true;
   }
   else if (!command_event(CMD_EVENT_CORE_INIT,
            &runloop_st->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
          ||  (menu_st->flags & MENU_ST_FLAG_ALIVE)
#endif
         )
#endif
      {
         /* Before initialising the dummy core, ensure
          * that we:
          * - Unload any active input remaps
          * - Disable any active config overrides */
         if (     (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CORE_ACTIVE)
               || (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE)
               || (runloop_st->flags & RUNLOOP_FLAG_REMAPS_GAME_ACTIVE)
               || !string_is_empty(runloop_st->name.remapfile)
            )
         {
            input_remapping_deinit(false);
            input_remapping_set_defaults(true);
         }
         else
            input_remapping_restore_global_config(true, false);

#ifdef HAVE_CONFIGFILE
         /* Reload the original config */
         if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE)
            config_unload_override();
#endif

#ifdef HAVE_DYNAMIC
         /* Ensure that currently loaded core is properly
          * deinitialised */
         if (runloop_st->current_core_type != CORE_TYPE_DUMMY)
            command_event(CMD_EVENT_CORE_DEINIT, NULL);
#endif
         /* Attempt initializing dummy core */
         runloop_st->current_core_type = CORE_TYPE_DUMMY;
         if (!command_event(CMD_EVENT_CORE_INIT, &runloop_st->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
         input_st->bsv_movie_state_handle
#else
         NULL
#endif
         );
#endif
   drivers_init(settings, DRIVERS_CMD_ALL, 0, verbosity_enabled);
#ifdef HAVE_COMMAND
   input_driver_deinit_command(input_st);
   input_driver_init_command(input_st, settings);
#endif
#ifdef HAVE_NETWORKGAMEPAD
   if (input_st->remote)
      input_remote_free(input_st->remote,
            settings->uints.input_max_users);
   input_st->remote    = NULL;
   if (settings->bools.network_remote_enable)
      input_st->remote = input_driver_init_remote(
            settings,
            settings->uints.input_max_users);
#endif
   input_mapper_reset(&input_st->mapper);
#ifdef HAVE_REWIND
   command_event(CMD_EVENT_REWIND_INIT, NULL);
#endif
   command_event(CMD_EVENT_CONTROLLER_INIT, NULL);
   if (!string_is_empty(recording_st->path))
      command_event(CMD_EVENT_RECORD_INIT, NULL);

   command_event(CMD_EVENT_SET_PER_GAME_RESOLUTION, NULL);

   global->error_on_init            = false;
   runloop_st->flags               |= RUNLOOP_FLAG_IS_INITED;

#ifdef HAVE_DISCORD
   {
      discord_state_t *discord_st = discord_state_get_ptr();

      if (command_event(CMD_EVENT_DISCORD_INIT, NULL))
         discord_st->inited = true;
   }
#endif

#ifdef HAVE_PRESENCE
   {
      presence_userdata_t userdata;
      userdata.status = PRESENCE_MENU;
      command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
   }
#endif

#if defined(HAVE_AUDIOMIXER)
   audio_driver_load_system_sounds();
#endif

#ifdef HAVE_RUNAHEAD
#ifdef HAVE_NETWORKING
   if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
#endif
      preempt_init(runloop_st);
#endif

   return true;

error:
   command_event(CMD_EVENT_CORE_DEINIT, NULL);
   runloop_state_get_ptr()->flags            &= ~RUNLOOP_FLAG_IS_INITED;

   return false;
}

void retroarch_init_task_queue(void)
{
#ifdef HAVE_THREADS
   settings_t *settings        = config_get_ptr();
   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 retroarch_ctl(enum rarch_ctl_state state, void *data)
{
   struct rarch_state     *p_rarch = &rarch_st;
   runloop_state_t     *runloop_st = runloop_state_get_ptr();

   switch(state)
   {
#ifdef HAVE_BSV_MOVIE
      case RARCH_CTL_BSV_MOVIE_IS_INITED:
         return (input_state_get_ptr()->bsv_movie_state_handle != NULL);
#endif
#ifdef HAVE_PATCH
      case RARCH_CTL_UNSET_BPS_PREF:
         p_rarch->flags &= ~RARCH_FLAGS_BPS_PREF;
         break;
      case RARCH_CTL_UNSET_UPS_PREF:
         p_rarch->flags &= ~RARCH_FLAGS_UPS_PREF;
         break;
      case RARCH_CTL_UNSET_IPS_PREF:
         p_rarch->flags &= ~RARCH_FLAGS_IPS_PREF;
         break;
#ifdef HAVE_XDELTA
      case RARCH_CTL_UNSET_XDELTA_PREF:
         p_rarch->flags &= ~RARCH_FLAGS_XDELTA_PREF;
         break;
#endif /* HAVE_XDELTA */
#endif /* HAVE_PATCH */
      case RARCH_CTL_IS_DUMMY_CORE:
         return runloop_st->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_st->flags & RUNLOOP_FLAG_CORE_RUNNING)
               && (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE);
      case RARCH_CTL_IS_SECOND_CORE_LOADED:
         return
                   (runloop_st->flags & RUNLOOP_FLAG_CORE_RUNNING)
               &&  (runloop_st->secondary_lib_handle != NULL);
#endif
      case RARCH_CTL_MAIN_DEINIT:
         {
            input_driver_state_t *input_st = input_state_get_ptr();
            if (!(runloop_st->flags & RUNLOOP_FLAG_IS_INITED))
               return false;
            command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
#ifdef HAVE_COMMAND
            input_driver_deinit_command(input_st);
#endif
#ifdef HAVE_NETWORKGAMEPAD
            if (input_st->remote)
               input_remote_free(input_st->remote,
                     config_get_ptr()->uints.input_max_users);
            input_st->remote = NULL;
#endif
            input_mapper_reset(&input_st->mapper);

#ifdef HAVE_THREADS
            if (runloop_st->flags & RUNLOOP_FLAG_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
            movie_stop(input_st);
#endif
            command_event(CMD_EVENT_CORE_DEINIT, NULL);

            content_deinit();

            runloop_path_deinit_subsystem();
            path_deinit_savefile();

            runloop_st->flags &= ~RUNLOOP_FLAG_IS_INITED;

#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->flags |= RARCH_FLAGS_BLOCK_CONFIG_READ;
         break;
      case RARCH_CTL_UNSET_BLOCK_CONFIG_READ:
         p_rarch->flags &= ~RARCH_FLAGS_BLOCK_CONFIG_READ;
         break;
#endif
      case RARCH_CTL_CORE_OPTIONS_LIST_GET:
         {
            core_option_manager_t **coreopts = (core_option_manager_t**)data;
            if (!coreopts || !runloop_st->core_options)
               return false;
            *coreopts = runloop_st->core_options;
         }
         break;
      case RARCH_CTL_CORE_OPTION_UPDATE_DISPLAY:
         if (   runloop_st->core_options
             && runloop_st->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_st->core_options->updated;
            bool display_updated = runloop_st->core_options_callback.update_display();

            runloop_st->core_options->updated = values_updated;
            return display_updated;
         }
         return false;
#ifdef HAVE_CONFIGFILE
      case RARCH_CTL_SET_REMAPS_CORE_ACTIVE:
         /* Only one type of remap can be active
          * at any one time */
         runloop_st->flags &= ~(RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE
                              | RUNLOOP_FLAG_REMAPS_GAME_ACTIVE);
         runloop_st->flags |=   RUNLOOP_FLAG_REMAPS_CORE_ACTIVE;
         break;
      case RARCH_CTL_SET_REMAPS_GAME_ACTIVE:
         runloop_st->flags &= ~(RUNLOOP_FLAG_REMAPS_CORE_ACTIVE
                              | RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE);
         runloop_st->flags |=   RUNLOOP_FLAG_REMAPS_GAME_ACTIVE;
         break;
      case RARCH_CTL_SET_REMAPS_CONTENT_DIR_ACTIVE:
         runloop_st->flags &= ~(RUNLOOP_FLAG_REMAPS_CORE_ACTIVE
                              | RUNLOOP_FLAG_REMAPS_GAME_ACTIVE);
         runloop_st->flags |=   RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE;
         break;
#endif
      case RARCH_CTL_GET_PERFCNT:
         {
            bool **perfcnt = (bool**)data;
            if (!perfcnt)
               return false;
            *perfcnt = &runloop_st->perfcnt_enable;
         }
         break;
      case RARCH_CTL_SET_PERFCNT_ENABLE:
         runloop_st->perfcnt_enable = true;
         break;
      case RARCH_CTL_UNSET_PERFCNT_ENABLE:
         runloop_st->perfcnt_enable = false;
         break;
      case RARCH_CTL_IS_PERFCNT_ENABLE:
         return runloop_st->perfcnt_enable;
      case RARCH_CTL_SET_WINDOWED_SCALE:
         {
            unsigned *idx = (unsigned*)data;
            if (!idx)
               return false;
            runloop_st->pending_windowed_scale = *idx;
         }
         break;
      case RARCH_CTL_STATE_FREE:
         {
            input_driver_state_t *input_st = input_state_get_ptr();
            runloop_st->perfcnt_enable     = false;
#ifdef HAVE_CONFIGFILE
            runloop_st->flags             &= ~RUNLOOP_FLAG_OVERRIDES_ACTIVE;
#endif
            runloop_st->flags             &= ~(RUNLOOP_FLAG_AUTOSAVE
                                           |   RUNLOOP_FLAG_SLOWMOTION
                                           |   RUNLOOP_FLAG_IDLE
                                           |   RUNLOOP_FLAG_PAUSED
                                              );
            runloop_state_free(runloop_st);

            memset(&input_st->analog_requested, 0,
                  sizeof(input_st->analog_requested));
         }
         break;
      case RARCH_CTL_SET_SHUTDOWN:
         runloop_st->flags |= RUNLOOP_FLAG_SHUTDOWN_INITIATED;
         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_st->core_options)
               return false;
            core_option_manager_adjust_val(runloop_st->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_st->core_options)
               return false;
            core_option_manager_adjust_val(runloop_st->core_options,
                  *idx, 1, true);
         }
         break;
      case RARCH_CTL_NONE:
      default:
         return false;
   }

   return true;
}

bool retroarch_override_setting_is_set(
      enum rarch_override_setting enum_idx, void *data)
{
   struct rarch_state *p_rarch = &rarch_st;
#ifdef HAVE_NETWORKING
   net_driver_state_t *net_st  = networking_state_get_ptr();
#endif

   switch (enum_idx)
   {
      case RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE:
         {
            unsigned *val = (unsigned*)data;
            if (val)
            {
               unsigned                bit = *val;
               runloop_state_t *runloop_st = runloop_state_get_ptr();
               return BIT256_GET(runloop_st->has_set_libretro_device, bit);
            }
         }
         break;
      case RARCH_OVERRIDE_SETTING_VERBOSITY:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_VERBOSITY) > 0);
      case RARCH_OVERRIDE_SETTING_LIBRETRO:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_LIBRETRO) > 0);
      case RARCH_OVERRIDE_SETTING_LIBRETRO_DIRECTORY:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_LIBRETRO_DIRECTORY) > 0);
      case RARCH_OVERRIDE_SETTING_SAVE_PATH:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_SAVE_PATH) > 0);
      case RARCH_OVERRIDE_SETTING_STATE_PATH:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_STATE_PATH) > 0);
#ifdef HAVE_NETWORKING
      case RARCH_OVERRIDE_SETTING_NETPLAY_MODE:
         return ((net_st->flags & NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_MODE) > 0);
      case RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS:
         return ((net_st->flags & NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_IP_ADDRESS) > 0);
      case RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT:
         return ((net_st->flags & NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_IP_PORT) > 0);
      case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
         return ((net_st->flags & NET_DRIVER_ST_FLAG_HAS_SET_NETPLAY_CHECK_FRAMES) > 0);
#endif
#ifdef HAVE_PATCH
      case RARCH_OVERRIDE_SETTING_UPS_PREF:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_UPS_PREF) > 0);
      case RARCH_OVERRIDE_SETTING_BPS_PREF:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_BPS_PREF) > 0);
      case RARCH_OVERRIDE_SETTING_IPS_PREF:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_IPS_PREF) > 0);
#ifdef HAVE_XDELTA
      case RARCH_OVERRIDE_SETTING_XDELTA_PREF:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_XDELTA_PREF) > 0);
#endif /* HAVE_XDELTA */
#endif /* HAVE_PATCH */
      case RARCH_OVERRIDE_SETTING_LOG_TO_FILE:
         return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_LOG_TO_FILE) > 0);
      case RARCH_OVERRIDE_SETTING_DATABASE_SCAN:
         return ((p_rarch->flags & RARCH_FLAGS_CLI_DATABASE_SCAN) > 0);
      case RARCH_OVERRIDE_SETTING_NONE:
      default:
         break;
   }

   return false;
}

int retroarch_get_capabilities(enum rarch_capabilities type,
      char *str_out, size_t str_len)
{
   switch (type)
   {
      case RARCH_CAPABILITIES_CPU:
         {
            uint64_t cpu = cpu_features_get();
            snprintf(str_out, str_len,
               "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
               cpu & RETRO_SIMD_MMX ? "MMX " : "",
               cpu & RETRO_SIMD_MMXEXT ? "MMXEXT " : "",
               cpu & RETRO_SIMD_SSE ? "SSE " : "",
               cpu & RETRO_SIMD_SSE2 ? "SSE2 " : "",
               cpu & RETRO_SIMD_SSE3 ? "SSE3 " : "",
               cpu & RETRO_SIMD_SSSE3 ? "SSSE3 " : "",
               cpu & RETRO_SIMD_SSE4 ? "SSE4 " : "",
               cpu & RETRO_SIMD_SSE42 ? "SSE42 " : "",
               cpu & RETRO_SIMD_AES ? "AES " : "",
               cpu & RETRO_SIMD_AVX ? "AVX " : "",
               cpu & RETRO_SIMD_AVX2 ? "AVX2 " : "",
               cpu & RETRO_SIMD_NEON ? "NEON " : "",
               cpu & RETRO_SIMD_VFPV3 ? "VFPV3 " : "",
               cpu & RETRO_SIMD_VFPV4 ? "VFPV4 " : "",
               cpu & RETRO_SIMD_VMX ? "VMX " : "",
               cpu & RETRO_SIMD_VMX128 ? "VMX128 " : "",
               cpu & RETRO_SIMD_VFPU ? "VFPU " : "",
               cpu & RETRO_SIMD_PS ? "PS " : "",
               cpu & RETRO_SIMD_ASIMD ? "ASIMD " : "");
            break;
         }
         break;
      case RARCH_CAPABILITIES_COMPILER:
#if defined(_MSC_VER)
         snprintf(str_out, str_len, "%s: MSVC (%d) %u-bit",
               msg_hash_to_str(MSG_COMPILER),
               _MSC_VER, (unsigned)
               (CHAR_BIT * sizeof(size_t)));
#elif defined(__SNC__)
         snprintf(str_out, str_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(str_out, str_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(str_out, str_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(str_out, str_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(str_out, str_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_fail(int error_code, const char *error)
{
   global_t *global                = global_get_ptr();
   /* We cannot longjmp unless we're in retroarch_main_init().
    * If not, something went very wrong, and we should
    * just exit right away. */
   strlcpy(global->error_string,
         error, sizeof(global->error_string));
   longjmp(global->error_sjlj_context, error_code);
}

/* Called on close content, checks if we need to also exit retroarch */
void check_quit_on_close(void)
{
#ifdef HAVE_MENU
   settings_t *settings   = config_get_ptr();
   global_t   *global     = global_get_ptr();
   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
}

/*
 * Also saves configuration files to disk,
 * and (optionally) autosave state.
 */
bool retroarch_main_quit(void)
{
   runloop_state_t *runloop_st   = runloop_state_get_ptr();
   video_driver_state_t*video_st = video_state_get_ptr();
   settings_t *settings          = config_get_ptr();
   bool config_save_on_exit      = settings->bools.config_save_on_exit;
#ifdef HAVE_ACCESSIBILITY
   access_state_t *access_st     = access_state_get_ptr();
#endif
   struct retro_system_av_info *av_info = &video_st->av_info;

   /* Restore video driver before saving */
   video_driver_restore_cached(settings);

#if !defined(HAVE_DYNAMIC)
   {
      /* Salamander sets RUNLOOP_FLAG_SHUTDOWN_INITIATED prior, so we need to handle it seperately */
      /* config_save_file_salamander() must be called independent of config_save_on_exit */
      config_save_file_salamander();
      if (config_save_on_exit)
         command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
   }
#endif

#ifdef HAVE_PRESENCE
   {
      presence_userdata_t userdata;
      userdata.status = PRESENCE_SHUTDOWN;
      command_event(CMD_EVENT_PRESENCE_UPDATE, &userdata);
   }
#endif
#ifdef HAVE_DISCORD
   {
      discord_state_t *discord_st = discord_state_get_ptr();
      if (discord_st->ready)
      {
         Discord_ClearPresence();
#ifdef DISCORD_DISABLE_IO_THREAD
         Discord_UpdateConnection();
#endif
         Discord_Shutdown();
         discord_st->ready       = false;
      }
      discord_st->inited         = false;
   }
#endif

   /* Restore original refresh rate, if it has been changed
    * automatically in SET_SYSTEM_AV_INFO */
   if (video_st->video_refresh_rate_original)
      video_display_server_restore_refresh_rate();

   if (!(runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED))
   {
      if (settings->bools.savestate_auto_save &&
          runloop_st->current_core_type != CORE_TYPE_DUMMY)
         command_event_save_auto_state();

      /* 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();

      if (     (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CORE_ACTIVE)
            || (runloop_st->flags & RUNLOOP_FLAG_REMAPS_CONTENT_DIR_ACTIVE)
            || (runloop_st->flags & RUNLOOP_FLAG_REMAPS_GAME_ACTIVE)
            || !string_is_empty(runloop_st->name.remapfile)
         )
      {
         input_remapping_deinit(settings->bools.remap_save_on_exit);
         input_remapping_set_defaults(true);
      }
      else
         input_remapping_restore_global_config(true, false);

#ifdef HAVE_CONFIGFILE
      if (runloop_st->flags & RUNLOOP_FLAG_OVERRIDES_ACTIVE)
      {
         /* Reload the original config */
         config_unload_override();
      }
#endif

      /* Save configs before quitting
       * as for UWP depending on `OnSuspending` is not important as we can call it directly here
       * specifically we need to get width,height which requires UI thread and it will not be available on exit
       */
#if defined(HAVE_DYNAMIC)
      if (config_save_on_exit)
         command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL);
#endif

#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
      runloop_st->runtime_shader_preset_path[0] = '\0';
#endif
   }

   runloop_st->flags |= RUNLOOP_FLAG_SHUTDOWN_INITIATED;
#ifdef HAVE_MENU
   retroarch_menu_running_finished(true);
#endif

#ifdef HAVE_TRANSLATE
   translation_release(false);
#endif

#ifdef HAVE_ACCESSIBILITY
#ifdef HAVE_THREADS
   if (access_st->image_lock)
   {
      slock_free(access_st->image_lock);
      access_st->image_lock = NULL;
   }
#endif
#endif

   return true;
}

enum retro_language retroarch_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[] =
   {
      {"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},
      {"id", RETRO_LANGUAGE_INDONESIAN},
      {"sv", RETRO_LANGUAGE_SWEDISH},
      {"uk", RETRO_LANGUAGE_UKRAINIAN},
      {"cs", RETRO_LANGUAGE_CZECH},
      {"ca_ES@valencia", RETRO_LANGUAGE_CATALAN_VALENCIA},
      {"en_CA", RETRO_LANGUAGE_BRITISH_ENGLISH}, /* Canada must be indexed before Catalan's "ca". */
      {"ca", RETRO_LANGUAGE_CATALAN},
      {"en_GB", RETRO_LANGUAGE_BRITISH_ENGLISH},
      {"en", RETRO_LANGUAGE_ENGLISH},
      {"hu", RETRO_LANGUAGE_HUNGARIAN},
      {"be", RETRO_LANGUAGE_BELARUSIAN},
   };

   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 retroarch_favorites_init(void)
{
   settings_t *settings                = config_get_ptr();
   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;

   retroarch_favorites_deinit();

   if (!playlist_config.capacity)
      return;

   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 retroarch_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;
}

#ifdef HAVE_ACCESSIBILITY
bool navigation_say(
      bool accessibility_enable,
      unsigned accessibility_narrator_speech_speed,
      const char* speak_text, int priority)
{
   access_state_t *access_st   = access_state_get_ptr();
   if (is_accessibility_enabled(
            accessibility_enable,
            access_st->enabled))
   {
      const char *voice    = get_user_language_iso639_1(false);
      bool native_narrator = accessibility_speak_priority(accessibility_narrator_speech_speed,
         speak_text, priority, voice);

      if (!native_narrator)
      {
         /*
          * 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;
}

bool accessibility_speak_priority(
      unsigned accessibility_narrator_speech_speed,
      const char *speak_text,
      int priority,
      const char *voice)
{
   frontend_ctx_driver_t *frontend =
      frontend_state_get_ptr()->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, voice);

   RARCH_LOG("Platform not supported for accessibility.\n");

   return false;
}
#endif
