/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2023 - Daniel De Matteis
* Copyright (C) 2018-2023 - Dan Weiss
* Copyright (C) 2022-2023 - Neil Fore
*
* 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 .
*/
#include
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include "content.h"
#include "core.h"
#include "dynamic.h"
#include "driver.h"
#include "audio/audio_driver.h"
#include "gfx/video_driver.h"
#include "paths.h"
#include "runloop.h"
#include "verbosity.h"
static int16_t input_state_get_last(unsigned port,
unsigned device, unsigned index, unsigned id)
{
int i;
runloop_state_t *runloop_st = runloop_state_get_ptr();
if (!runloop_st->input_state_list)
return 0;
/* find list item */
for (i = 0; i < runloop_st->input_state_list->size; i++)
{
input_list_element *element =
(input_list_element*)runloop_st->input_state_list->data[i];
if ( (element->port == port) &&
(element->device == device) &&
(element->index == index))
{
if (id < element->state_size)
return element->state[id];
return 0;
}
}
return 0;
}
static void free_retro_ctx_load_content_info(struct
retro_ctx_load_content_info *dest)
{
if (!dest)
return;
string_list_free((struct string_list*)dest->content);
if (dest->info)
free(dest->info);
dest->info = NULL;
dest->content = NULL;
}
static struct retro_game_info* clone_retro_game_info(const
struct retro_game_info *src)
{
struct retro_game_info *dest = (struct retro_game_info*)malloc(
sizeof(struct retro_game_info));
if (!dest)
return NULL;
/* content_file_init() guarantees that all
* elements of the source retro_game_info
* struct will persist for the lifetime of
* the core. This means we do not have to
* copy any data; pointer assignment is
* sufficient */
dest->path = src->path;
dest->data = src->data;
dest->size = src->size;
dest->meta = src->meta;
return dest;
}
static struct retro_ctx_load_content_info
*clone_retro_ctx_load_content_info(
const struct retro_ctx_load_content_info *src)
{
struct retro_ctx_load_content_info *dest = NULL;
if (!src || src->special)
return NULL; /* refuse to deal with the Special field */
dest = (struct retro_ctx_load_content_info*)
malloc(sizeof(*dest));
if (!dest)
return NULL;
dest->info = NULL;
dest->content = NULL;
dest->special = NULL;
if (src->info)
dest->info = clone_retro_game_info(src->info);
if (src->content)
dest->content = string_list_clone(src->content);
return dest;
}
void runahead_set_load_content_info(void *data,
const retro_ctx_load_content_info_t *ctx)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
free_retro_ctx_load_content_info(runloop_st->load_content_info);
free(runloop_st->load_content_info);
runloop_st->load_content_info = clone_retro_ctx_load_content_info(ctx);
}
/* RUNAHEAD - SECONDARY CORE */
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
static void strcat_alloc(char **dst, const char *s)
{
size_t len1;
char *src = *dst;
if (!src)
{
if (s)
{
size_t len = strlen(s);
if (len != 0)
{
char *_dst= (char*)malloc(len + 1);
strcpy_literal(_dst, s);
src = _dst;
}
else
src = NULL;
}
else
src = (char*)calloc(1,1);
*dst = src;
return;
}
if (!s)
return;
len1 = strlen(src);
if (!(src = (char*)realloc(src, len1 + strlen(s) + 1)))
return;
*dst = src;
strcpy_literal(src + len1, s);
}
void runahead_secondary_core_destroy(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
if (!runloop_st->secondary_lib_handle)
return;
/* unload game from core */
if (runloop_st->secondary_core.retro_unload_game)
runloop_st->secondary_core.retro_unload_game();
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
/* deinit */
if (runloop_st->secondary_core.retro_deinit)
runloop_st->secondary_core.retro_deinit();
memset(&runloop_st->secondary_core, 0, sizeof(struct retro_core_t));
dylib_close(runloop_st->secondary_lib_handle);
runloop_st->secondary_lib_handle = NULL;
filestream_delete(runloop_st->secondary_library_path);
if (runloop_st->secondary_library_path)
free(runloop_st->secondary_library_path);
runloop_st->secondary_library_path = NULL;
}
static char *get_tmpdir_alloc(const char *override_dir)
{
const char *src = NULL;
char *path = NULL;
#ifdef _WIN32
#ifdef LEGACY_WIN32
DWORD plen = GetTempPath(0, NULL) + 1;
if (!(path = (char*)malloc(plen * sizeof(char))))
return NULL;
path[plen - 1] = 0;
GetTempPath(plen, path);
#else
DWORD plen = GetTempPathW(0, NULL) + 1;
wchar_t *wide_str = (wchar_t*)malloc(plen * sizeof(wchar_t));
if (!wide_str)
return NULL;
wide_str[plen - 1] = 0;
GetTempPathW(plen, wide_str);
path = utf16_to_utf8_string_alloc(wide_str);
free(wide_str);
#endif
#else
#if defined ANDROID
src = override_dir;
#else
{
char *tmpdir = getenv("TMPDIR");
if (tmpdir)
src = tmpdir;
else
src = "/tmp";
}
#endif
if (src)
{
size_t len = strlen(src);
if (len != 0)
{
char *dst = (char*)malloc(len + 1);
strcpy_literal(dst, src);
path = dst;
}
}
else
path = (char*)calloc(1,1);
#endif
return path;
}
static bool write_file_with_random_name(char **temp_dll_path,
const char *tmp_path, const void* data, ssize_t dataSize)
{
int i;
size_t ext_len;
char number_buf[32];
bool okay = false;
const char *prefix = "tmp";
char *ext = NULL;
time_t time_value = time(NULL);
unsigned _number_value = (unsigned)time_value;
const char *src = path_get_extension(*temp_dll_path);
if (src)
{
size_t len = strlen(src);
if (len != 0)
{
char *dst = (char*)malloc(len + 1);
strcpy_literal(dst, src);
ext = dst;
}
}
else
ext = (char*)calloc(1,1);
ext_len = strlen(ext);
if (ext_len > 0)
{
strcat_alloc(&ext, ".");
memmove(ext + 1, ext, ext_len);
ext[0] = '.';
ext_len++;
}
/* Try up to 30 'random' filenames before giving up */
for (i = 0; i < 30; i++)
{
int number_value = _number_value * 214013 + 2531011;
int number = (number_value >> 14) % 100000;
snprintf(number_buf, sizeof(number_buf), "%05d", number);
if (*temp_dll_path)
free(*temp_dll_path);
*temp_dll_path = NULL;
strcat_alloc(temp_dll_path, tmp_path);
strcat_alloc(temp_dll_path, PATH_DEFAULT_SLASH());
strcat_alloc(temp_dll_path, prefix);
strcat_alloc(temp_dll_path, number_buf);
strcat_alloc(temp_dll_path, ext);
if (filestream_write_file(*temp_dll_path, data, dataSize))
{
okay = true;
break;
}
}
if (ext)
free(ext);
ext = NULL;
return okay;
}
static char *copy_core_to_temp_file(
const char *core_path,
const char *dir_libretro)
{
char tmp_path[PATH_MAX_LENGTH];
bool failed = false;
char *tmpdir = NULL;
char *tmp_dll_path = NULL;
void *dll_file_data = NULL;
int64_t dll_file_size = 0;
const char *core_base_name = path_basename_nocompression(core_path);
if (strlen(core_base_name) == 0)
return NULL;
if (!(tmpdir = get_tmpdir_alloc(dir_libretro)))
return NULL;
fill_pathname_join_special(tmp_path,
tmpdir, "retroarch_temp",
sizeof(tmp_path));
if (!path_mkdir(tmp_path))
{
failed = true;
goto end;
}
if (!filestream_read_file(core_path, &dll_file_data, &dll_file_size))
{
failed = true;
goto end;
}
strcat_alloc(&tmp_dll_path, tmp_path);
strcat_alloc(&tmp_dll_path, PATH_DEFAULT_SLASH());
strcat_alloc(&tmp_dll_path, core_base_name);
if (!filestream_write_file(tmp_dll_path, dll_file_data, dll_file_size))
{
/* try other file names */
if (!write_file_with_random_name(&tmp_dll_path,
tmp_path, dll_file_data, dll_file_size))
failed = true;
}
end:
if (tmpdir)
free(tmpdir);
if (dll_file_data)
free(dll_file_data);
tmpdir = NULL;
dll_file_data = NULL;
if (!failed)
return tmp_dll_path;
if (tmp_dll_path)
free(tmp_dll_path);
tmp_dll_path = NULL;
return NULL;
}
static bool runloop_environment_secondary_core_hook(
unsigned cmd, void *data)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
bool result = runloop_environment_cb(cmd, data);
if (runloop_st->flags & RUNLOOP_FLAG_HAS_VARIABLE_UPDATE)
{
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
{
bool *bool_p = (bool*)data;
*bool_p = true;
runloop_st->flags &= ~RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
return true;
}
else if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE)
runloop_st->flags &= ~RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
}
return result;
}
void runahead_clear_controller_port_map(void *data)
{
int port;
runloop_state_t *runloop_st = (runloop_state_t*)data;
for (port = 0; port < MAX_USERS; port++)
runloop_st->port_map[port] = -1;
}
static bool secondary_core_create(runloop_state_t *runloop_st,
settings_t *settings)
{
const enum rarch_core_type
last_core_type = runloop_st->last_core_type;
rarch_system_info_t *info = &runloop_st->system;
unsigned num_active_users = settings->uints.input_max_users;
uint8_t flags = content_get_flags();
if ( (last_core_type != CORE_TYPE_PLAIN)
|| (!runloop_st->load_content_info)
|| ( runloop_st->load_content_info->special))
return false;
if (runloop_st->secondary_library_path)
free(runloop_st->secondary_library_path);
runloop_st->secondary_library_path = NULL;
runloop_st->secondary_library_path = copy_core_to_temp_file(
path_get(RARCH_PATH_CORE),
settings->paths.directory_libretro);
if (!runloop_st->secondary_library_path)
return false;
/* Load Core */
if (!runloop_init_libretro_symbols(runloop_st,
CORE_TYPE_PLAIN, &runloop_st->secondary_core,
runloop_st->secondary_library_path,
&runloop_st->secondary_lib_handle))
return false;
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_SYMBOLS_INITED;
runloop_st->secondary_core.retro_set_environment(
runloop_environment_secondary_core_hook);
runloop_st->flags |= RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
runloop_st->secondary_core.retro_init();
if (flags & CONTENT_ST_FLAG_IS_INITED)
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_INITED;
else
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_INITED;
/* Load Content */
/* disabled due to crashes */
if ( (!runloop_st->load_content_info)
|| (runloop_st->load_content_info->special))
return false;
if ( ( runloop_st->load_content_info->content->size > 0)
&& runloop_st->load_content_info->content->elems[0].data)
{
if (!runloop_st->secondary_core.retro_load_game(
runloop_st->load_content_info->info))
{
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
goto error;
}
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_GAME_LOADED;
}
else if (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)
{
if (!runloop_st->secondary_core.retro_load_game(NULL))
{
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
goto error;
}
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_GAME_LOADED;
}
else
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
if (!(runloop_st->secondary_core.flags & RETRO_CORE_FLAG_INITED))
goto error;
core_set_default_callbacks(&runloop_st->secondary_callbacks);
runloop_st->secondary_core.retro_set_video_refresh(
runloop_st->secondary_callbacks.frame_cb);
runloop_st->secondary_core.retro_set_audio_sample(
runloop_st->secondary_callbacks.sample_cb);
runloop_st->secondary_core.retro_set_audio_sample_batch(
runloop_st->secondary_callbacks.sample_batch_cb);
runloop_st->secondary_core.retro_set_input_state(
runloop_st->secondary_callbacks.state_cb);
runloop_st->secondary_core.retro_set_input_poll(
runloop_st->secondary_callbacks.poll_cb);
if (info)
{
int port;
for (port = 0; port < MAX_USERS; port++)
{
if (port < (int)info->ports.size)
{
unsigned device = (port < (int)num_active_users) ?
runloop_st->port_map[port] : RETRO_DEVICE_NONE;
runloop_st->secondary_core.retro_set_controller_port_device(
port, device);
}
}
}
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
runahead_clear_controller_port_map(runloop_st);
#endif
return true;
error:
runahead_secondary_core_destroy(runloop_st);
return false;
}
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
bool secondary_core_ensure_exists(void *data, settings_t *settings)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
if (!runloop_st->secondary_lib_handle)
if (!secondary_core_create(runloop_st, settings))
return false;
return true;
}
#endif
#if defined(HAVE_DYNAMIC)
static bool secondary_core_deserialize(runloop_state_t *runloop_st,
settings_t *settings,
const void *data, size_t size)
{
bool ret = false;
if (secondary_core_ensure_exists(runloop_st, settings))
{
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
ret = runloop_st->secondary_core.retro_unserialize(data, size);
runloop_st->flags &= ~RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
}
else
runahead_secondary_core_destroy(runloop_st);
return ret;
}
#endif
static void secondary_core_input_poll_null(void) { }
static bool secondary_core_run_use_last_input(runloop_state_t *runloop_st)
{
retro_input_poll_t old_poll_function;
retro_input_state_t old_input_function;
if (!secondary_core_ensure_exists(runloop_st, config_get_ptr()))
{
runahead_secondary_core_destroy(runloop_st);
return false;
}
old_poll_function = runloop_st->secondary_callbacks.poll_cb;
old_input_function = runloop_st->secondary_callbacks.state_cb;
runloop_st->secondary_callbacks.poll_cb = secondary_core_input_poll_null;
runloop_st->secondary_callbacks.state_cb = input_state_get_last;
runloop_st->secondary_core.retro_set_input_poll(
runloop_st->secondary_callbacks.poll_cb);
runloop_st->secondary_core.retro_set_input_state(
runloop_st->secondary_callbacks.state_cb);
runloop_st->secondary_core.retro_run();
runloop_st->secondary_callbacks.poll_cb = old_poll_function;
runloop_st->secondary_callbacks.state_cb = old_input_function;
runloop_st->secondary_core.retro_set_input_poll(
runloop_st->secondary_callbacks.poll_cb);
runloop_st->secondary_core.retro_set_input_state(
runloop_st->secondary_callbacks.state_cb);
return true;
}
void runahead_remember_controller_port_device(void *data,
long port, long device)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
if (port >= 0 && port < MAX_USERS)
runloop_st->port_map[port] = (int)device;
if ( runloop_st->secondary_lib_handle
&& runloop_st->secondary_core.retro_set_controller_port_device)
runloop_st->secondary_core.retro_set_controller_port_device((unsigned)port, (unsigned)device);
}
#else
void runahead_secondary_core_destroy(void *data) { }
#endif
static void mylist_resize(my_list *list,
int new_size, bool run_constructor)
{
int i;
int new_capacity;
int old_size;
void *element = NULL;
if (new_size < 0)
new_size = 0;
new_capacity = new_size;
old_size = list->size;
if (new_size == old_size)
return;
if (new_size > list->capacity)
{
if (new_capacity < list->capacity * 2)
new_capacity = list->capacity * 2;
/* try to realloc */
list->data = (void**)realloc(
(void*)list->data, new_capacity * sizeof(void*));
for (i = list->capacity; i < new_capacity; i++)
list->data[i] = NULL;
list->capacity = new_capacity;
}
if (new_size <= list->size)
{
for (i = new_size; i < list->size; i++)
{
element = list->data[i];
if (element)
{
list->destructor(element);
list->data[i] = NULL;
}
}
}
else
{
for (i = list->size; i < new_size; i++)
{
list->data[i] = NULL;
if (run_constructor)
list->data[i] = list->constructor();
}
}
list->size = new_size;
}
static void *mylist_add_element(my_list *list)
{
int old_size = list->size;
if (list)
mylist_resize(list, old_size + 1, true);
return list->data[old_size];
}
static void mylist_destroy(my_list **list_p)
{
my_list *list = NULL;
if (!list_p)
return;
list = *list_p;
if (list)
{
mylist_resize(list, 0, false);
free(list->data);
free(list);
*list_p = NULL;
}
}
static void mylist_create(my_list **list_p, int initial_capacity,
constructor_t constructor, destructor_t destructor)
{
my_list *list = NULL;
if (!list_p)
return;
list = *list_p;
if (list)
mylist_destroy(list_p);
list = (my_list*)malloc(sizeof(my_list));
*list_p = list;
list->size = 0;
list->constructor = constructor;
list->destructor = destructor;
list->data = (void**)calloc(initial_capacity, sizeof(void*));
list->capacity = initial_capacity;
}
static void *input_list_element_constructor(void)
{
void *ptr = malloc(sizeof(input_list_element));
input_list_element *element = (input_list_element*)ptr;
element->port = 0;
element->device = 0;
element->index = 0;
element->state = (int16_t*)calloc(NAME_MAX_LENGTH,
sizeof(int16_t));
element->state_size = NAME_MAX_LENGTH;
return ptr;
}
static void input_list_element_realloc(
input_list_element *element,
unsigned int new_size)
{
if (new_size > element->state_size)
{
element->state = (int16_t*)realloc(element->state,
new_size * sizeof(int16_t));
memset(&element->state[element->state_size], 0,
(new_size - element->state_size) * sizeof(int16_t));
element->state_size = new_size;
}
}
static void input_list_element_expand(
input_list_element *element, unsigned int new_index)
{
unsigned int new_size = element->state_size;
if (new_size == 0)
new_size = 32;
while (new_index >= new_size)
new_size *= 2;
input_list_element_realloc(element, new_size);
}
static void input_list_element_destructor(void* element_ptr)
{
input_list_element *element = (input_list_element*)element_ptr;
if (!element)
return;
free(element->state);
free(element_ptr);
}
static void runahead_input_state_set_last(
runloop_state_t *runloop_st,
unsigned port, unsigned device,
unsigned index, unsigned id, int16_t value)
{
unsigned i;
input_list_element *element = NULL;
if (!runloop_st->input_state_list)
mylist_create(&runloop_st->input_state_list, 16,
input_list_element_constructor,
input_list_element_destructor);
/* Find list item */
for (i = 0; i < (unsigned)runloop_st->input_state_list->size; i++)
{
element = (input_list_element*)runloop_st->input_state_list->data[i];
if ( (element->port == port) &&
(element->device == device) &&
(element->index == index)
)
{
if (id >= element->state_size)
input_list_element_expand(element, id);
element->state[id] = value;
return;
}
}
element = NULL;
if (runloop_st->input_state_list)
element = (input_list_element*)
mylist_add_element(runloop_st->input_state_list);
if (element)
{
element->port = port;
element->device = device;
element->index = index;
if (id >= element->state_size)
input_list_element_expand(element, id);
element->state[id] = value;
}
}
static int16_t runahead_input_state_with_logging(unsigned port,
unsigned device, unsigned index, unsigned id)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
if (runloop_st->input_state_callback_original)
{
int16_t result =
runloop_st->input_state_callback_original(
port, device, index, id);
int16_t last_input =
input_state_get_last(port, device, index, id);
if (result != last_input)
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
/*arbitrary limit of up to 65536 elements in state array*/
if (id < 65536)
runahead_input_state_set_last(runloop_st, port, device, index, id, result);
return result;
}
return 0;
}
static void runahead_reset_hook(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (runloop_st->retro_reset_callback_original)
runloop_st->retro_reset_callback_original();
}
static bool runahead_unserialize_hook(const void *buf, size_t size)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (runloop_st->retro_unserialize_callback_original)
return runloop_st->retro_unserialize_callback_original(buf, size);
return false;
}
static void runahead_add_input_state_hook(runloop_state_t *runloop_st)
{
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
if (!runloop_st->input_state_callback_original)
{
runloop_st->input_state_callback_original = cbs->state_cb;
cbs->state_cb = runahead_input_state_with_logging;
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
}
if (!runloop_st->retro_reset_callback_original)
{
runloop_st->retro_reset_callback_original
= runloop_st->current_core.retro_reset;
runloop_st->current_core.retro_reset = runahead_reset_hook;
}
if (!runloop_st->retro_unserialize_callback_original)
{
runloop_st->retro_unserialize_callback_original = runloop_st->current_core.retro_unserialize;
runloop_st->current_core.retro_unserialize = runahead_unserialize_hook;
}
}
static void runahead_remove_input_state_hook(runloop_state_t *runloop_st)
{
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
if (runloop_st->input_state_callback_original)
{
cbs->state_cb =
runloop_st->input_state_callback_original;
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
runloop_st->input_state_callback_original = NULL;
mylist_destroy(&runloop_st->input_state_list);
}
if (runloop_st->retro_reset_callback_original)
{
runloop_st->current_core.retro_reset =
runloop_st->retro_reset_callback_original;
runloop_st->retro_reset_callback_original = NULL;
}
if (runloop_st->retro_unserialize_callback_original)
{
runloop_st->current_core.retro_unserialize =
runloop_st->retro_unserialize_callback_original;
runloop_st->retro_unserialize_callback_original = NULL;
}
}
static void *runahead_save_state_alloc(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
retro_ctx_serialize_info_t *savestate = (retro_ctx_serialize_info_t*)
malloc(sizeof(retro_ctx_serialize_info_t));
if (!savestate)
return NULL;
savestate->data = NULL;
savestate->data_const = NULL;
savestate->size = 0;
if ( (runloop_st->runahead_save_state_size > 0)
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN))
{
savestate->data = malloc(runloop_st->runahead_save_state_size);
savestate->data_const = savestate->data;
savestate->size = runloop_st->runahead_save_state_size;
}
return savestate;
}
static void runahead_save_state_free(void *data)
{
retro_ctx_serialize_info_t *savestate = (retro_ctx_serialize_info_t*)data;
if (!savestate)
return;
free(savestate->data);
free(savestate);
}
static void runahead_save_state_list_init(
runloop_state_t *runloop_st,
size_t save_state_size)
{
runloop_st->runahead_save_state_size = save_state_size;
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN;
mylist_create(&runloop_st->runahead_save_state_list, 16,
runahead_save_state_alloc, runahead_save_state_free);
}
/* Hooks - Hooks to cleanup, and add dirty input hooks */
static void runahead_remove_hooks(runloop_state_t *runloop_st)
{
if (runloop_st->original_retro_deinit)
{
runloop_st->current_core.retro_deinit =
runloop_st->original_retro_deinit;
runloop_st->original_retro_deinit = NULL;
}
if (runloop_st->original_retro_unload)
{
runloop_st->current_core.retro_unload_game =
runloop_st->original_retro_unload;
runloop_st->original_retro_unload = NULL;
}
runahead_remove_input_state_hook(runloop_st);
}
static void runahead_destroy(runloop_state_t *runloop_st)
{
mylist_destroy(&runloop_st->runahead_save_state_list);
runahead_remove_hooks(runloop_st);
runahead_clear_variables(runloop_st);
}
static void runahead_unload_hook(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runahead_remove_hooks(runloop_st);
runahead_destroy(runloop_st);
runahead_secondary_core_destroy(runloop_st);
if (runloop_st->current_core.retro_unload_game)
runloop_st->current_core.retro_unload_game();
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
}
static void runahead_deinit_hook(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runahead_remove_hooks(runloop_st);
runahead_destroy(runloop_st);
runahead_secondary_core_destroy(runloop_st);
if (runloop_st->current_core.retro_deinit)
runloop_st->current_core.retro_deinit();
}
static void runahead_add_hooks(runloop_state_t *runloop_st)
{
if (!runloop_st->original_retro_deinit)
{
runloop_st->original_retro_deinit =
runloop_st->current_core.retro_deinit;
runloop_st->current_core.retro_deinit = runahead_deinit_hook;
}
if (!runloop_st->original_retro_unload)
{
runloop_st->original_retro_unload = runloop_st->current_core.retro_unload_game;
runloop_st->current_core.retro_unload_game = runahead_unload_hook;
}
runahead_add_input_state_hook(runloop_st);
}
/* Runahead Code */
static void runahead_error(runloop_state_t *runloop_st)
{
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_AVAILABLE;
mylist_destroy(&runloop_st->runahead_save_state_list);
runahead_remove_hooks(runloop_st);
runloop_st->runahead_save_state_size = 0;
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN;
}
static bool runahead_create(runloop_state_t *runloop_st)
{
/* get savestate size and allocate buffer */
video_driver_state_t *video_st = video_state_get_ptr();
size_t info_size = core_serialize_size_special();
runahead_save_state_list_init(runloop_st, info_size);
if (video_st->flags & VIDEO_FLAG_ACTIVE)
video_st->flags |= VIDEO_FLAG_RUNAHEAD_IS_ACTIVE;
else
video_st->flags &= ~VIDEO_FLAG_RUNAHEAD_IS_ACTIVE;
if ( (runloop_st->runahead_save_state_size == 0)
|| !(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN))
{
runahead_error(runloop_st);
return false;
}
runahead_add_hooks(runloop_st);
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
if (runloop_st->runahead_save_state_list)
mylist_resize(runloop_st->runahead_save_state_list, 1, true);
return true;
}
static bool runahead_save_state(runloop_state_t *runloop_st)
{
retro_ctx_serialize_info_t *serialize_info;
if (!runloop_st->runahead_save_state_list)
return false;
serialize_info =
(retro_ctx_serialize_info_t*)runloop_st->runahead_save_state_list->data[0];
if (core_serialize_special(serialize_info))
return true;
runahead_error(runloop_st);
return false;
}
static bool runahead_load_state(runloop_state_t *runloop_st)
{
retro_ctx_serialize_info_t *serialize_info =
(retro_ctx_serialize_info_t*)
runloop_st->runahead_save_state_list->data[0];
bool last_dirty = runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY;
bool ret = core_unserialize_special(serialize_info);
if (last_dirty)
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
else
runloop_st->flags &= ~RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (!ret)
runahead_error(runloop_st);
return ret;
}
#if HAVE_DYNAMIC
static bool runahead_load_state_secondary(runloop_state_t *runloop_st, settings_t *settings)
{
retro_ctx_serialize_info_t *serialize_info =
(retro_ctx_serialize_info_t*)runloop_st->runahead_save_state_list->data[0];
if (!secondary_core_deserialize(runloop_st,
settings, serialize_info->data_const,
serialize_info->size))
{
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
runahead_error(runloop_st);
return false;
}
return true;
}
#endif
static void runahead_core_run_use_last_input(runloop_state_t *runloop_st)
{
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
retro_input_poll_t old_poll_function = cbs->poll_cb;
retro_input_state_t old_input_function = cbs->state_cb;
cbs->poll_cb = retro_input_poll_null;
cbs->state_cb = input_state_get_last;
runloop_st->current_core.retro_set_input_poll(cbs->poll_cb);
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
runloop_st->current_core.retro_run();
cbs->poll_cb = old_poll_function;
cbs->state_cb = old_input_function;
runloop_st->current_core.retro_set_input_poll(cbs->poll_cb);
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
}
void runahead_run(void *data,
int runahead_count,
bool runahead_hide_warnings,
bool use_secondary)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
int frame_number = 0;
bool last_frame = false;
bool suspended_frame = false;
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
const bool have_dynamic = true;
settings_t *settings = config_get_ptr();
#else
const bool have_dynamic = false;
#endif
video_driver_state_t
*video_st = video_state_get_ptr();
uint64_t frame_count = video_st->frame_count;
audio_driver_state_t
*audio_st = audio_state_get_ptr();
if ( runahead_count <= 0
|| !(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE))
goto force_input_dirty;
if (!(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN))
{
/* Disable runahead if current core reports
* that it has an insufficient savestate
* support level */
if (!core_info_current_supports_runahead())
{
runahead_error(runloop_st);
/* If core is incompatible with runahead,
* log a warning but do not spam OSD messages.
* Runahead menu entries are hidden when using
* incompatible cores, so there is no mechanism
* for users to respond to notifications. In
* addition, auto-disabling runahead is a feature,
* not a cause for 'concern'; OSD warnings should
* be reserved for when a core reports that it is
* runahead-compatible but subsequently fails in
* execution */
RARCH_WARN("[Run-Ahead]: %s\n", msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_RUNAHEAD));
goto force_input_dirty;
}
if (!runahead_create(runloop_st))
{
const char *runahead_failed_str =
msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_SAVESTATES);
if (!runahead_hide_warnings)
runloop_msg_queue_push(runahead_failed_str, 0, 2 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead]: %s\n", runahead_failed_str);
goto force_input_dirty;
}
}
/* Check for GUI */
/* Hack: If we were in the GUI, force a resync. */
if (frame_count != runloop_st->runahead_last_frame_count + 1)
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
runloop_st->runahead_last_frame_count = frame_count;
if ( !use_secondary
|| !have_dynamic
|| !(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE))
{
/* TODO: multiple savestates for higher performance
* when not using secondary core */
for (frame_number = 0; frame_number <= runahead_count; frame_number++)
{
last_frame = frame_number == runahead_count;
suspended_frame = !last_frame;
if (suspended_frame)
{
audio_st->flags |= AUDIO_FLAG_SUSPENDED;
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
}
if (frame_number == 0)
core_run();
else
runahead_core_run_use_last_input(runloop_st);
if (suspended_frame)
{
if (video_st->flags & VIDEO_FLAG_RUNAHEAD_IS_ACTIVE)
video_st->flags |= VIDEO_FLAG_ACTIVE;
else
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
audio_st->flags &= ~AUDIO_FLAG_SUSPENDED;
}
if (frame_number == 0)
{
if (!runahead_save_state(runloop_st))
{
const char *runahead_failed_str =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE);
runloop_msg_queue_push(runahead_failed_str, 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead]: %s\n", runahead_failed_str);
return;
}
}
if (last_frame)
{
if (!runahead_load_state(runloop_st))
{
const char *runahead_failed_str =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE);
runloop_msg_queue_push(runahead_failed_str, 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead]: %s\n", runahead_failed_str);
return;
}
}
}
}
else
{
#if HAVE_DYNAMIC
if (!secondary_core_ensure_exists(runloop_st, config_get_ptr()))
{
const char *runahead_failed_str =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE);
runahead_secondary_core_destroy(runloop_st);
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
runloop_msg_queue_push(runahead_failed_str, 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead]: %s\n", runahead_failed_str);
goto force_input_dirty;
}
/* run main core with video suspended */
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
core_run();
if (video_st->flags & VIDEO_FLAG_RUNAHEAD_IS_ACTIVE)
video_st->flags |= VIDEO_FLAG_ACTIVE;
else
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
if ( (runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY)
|| (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY))
{
runloop_st->flags &= ~RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (!runahead_save_state(runloop_st))
{
const char *runahead_failed_str =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE);
runloop_msg_queue_push(runahead_failed_str, 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead]: %s\n", runahead_failed_str);
return;
}
if (!runahead_load_state_secondary(runloop_st, settings))
{
const char *runahead_failed_str =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE);
runloop_msg_queue_push(runahead_failed_str, 0, 3 * 60, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead]: %s\n", runahead_failed_str);
return;
}
for (frame_number = 0; frame_number < runahead_count - 1; frame_number++)
{
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
audio_st->flags |= AUDIO_FLAG_SUSPENDED
| AUDIO_FLAG_HARD_DISABLE;
if (secondary_core_run_use_last_input(runloop_st))
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
else
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
audio_st->flags &= ~(AUDIO_FLAG_SUSPENDED
| AUDIO_FLAG_HARD_DISABLE);
if (video_st->flags & VIDEO_FLAG_RUNAHEAD_IS_ACTIVE)
video_st->flags |= VIDEO_FLAG_ACTIVE;
else
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
}
}
audio_st->flags |= AUDIO_FLAG_SUSPENDED
| AUDIO_FLAG_HARD_DISABLE;
if (secondary_core_run_use_last_input(runloop_st))
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
else
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
audio_st->flags &= ~(AUDIO_FLAG_SUSPENDED
| AUDIO_FLAG_HARD_DISABLE);
#endif
}
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
return;
force_input_dirty:
core_run();
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
}
/* Preemptive Frames */
static int16_t preempt_input_state(unsigned port,
unsigned device, unsigned index, unsigned id)
{
settings_t *settings = config_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
preempt_t *preempt = runloop_st->preempt_data;
unsigned device_class = device & RETRO_DEVICE_MASK;
unsigned *port_map = settings->uints.input_remap_port_map[port];
uint8_t p;
switch (device_class)
{
case RETRO_DEVICE_ANALOG:
/* Add requested inputs to mask */
while ((p = *(port_map++)) < MAX_USERS)
preempt->analog_mask[p] |= (1 << (id + index * 2));
break;
case RETRO_DEVICE_LIGHTGUN:
case RETRO_DEVICE_MOUSE:
case RETRO_DEVICE_POINTER:
/* Set pointing device for this port */
while ((p = *(port_map++)) < MAX_USERS)
preempt->ptr_dev[p] = device_class;
break;
default:
break;
}
return input_driver_state_wrapper(port, device, index, id);
}
static const char* preempt_allocate(runloop_state_t *runloop_st,
const uint8_t frames)
{
uint8_t i;
size_t info_size;
preempt_t *preempt = (preempt_t*)calloc(1, sizeof(preempt_t));
if (!(runloop_st->preempt_data = preempt))
return msg_hash_to_str(MSG_PREEMPT_FAILED_TO_ALLOCATE);
info_size = core_serialize_size_special();
if (!info_size)
return msg_hash_to_str(MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_SAVESTATES);
preempt->state_size = info_size;
preempt->frames = frames;
for (i = 0; i < frames; i++)
{
preempt->buffer[i] = malloc(preempt->state_size);
if (!preempt->buffer[i])
return msg_hash_to_str(MSG_PREEMPT_FAILED_TO_ALLOCATE);
}
return NULL;
}
/**
* preempt_deinit:
*
* Frees preempt object and unsets overrides.
**/
void preempt_deinit(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
preempt_t *preempt = runloop_st->preempt_data;
struct retro_core_t *current_core = &runloop_st->current_core;
size_t i;
if (!preempt)
return;
/* Free memory */
for (i = 0; i < preempt->frames; i++)
free(preempt->buffer[i]);
free(preempt);
runloop_st->preempt_data = NULL;
/* Undo overrides */
runloop_st->flags |= (RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE);
if (current_core->retro_set_input_poll)
current_core->retro_set_input_poll(runloop_st->input_poll_callback_original);
if (current_core->retro_set_input_state)
current_core->retro_set_input_state(runloop_st->retro_ctx.state_cb);
}
/**
* preempt_init:
*
* @return true on success, false on failure
*
* Allocates savestate buffer and sets overrides for preemptive frames.
**/
bool preempt_init(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
settings_t *settings = config_get_ptr();
const char *failed_str = NULL;
if ( runloop_st->preempt_data
|| !settings->bools.preemptive_frames_enable
|| !settings->uints.run_ahead_frames
|| !(runloop_st->current_core.flags & RETRO_CORE_FLAG_GAME_LOADED))
return false;
/* Check if supported - same requirements as runahead */
if (!core_info_current_supports_runahead())
{
failed_str = msg_hash_to_str(MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_PREEMPT);
goto error;
}
/* Set flags to block runahead and request 'same instance' states */
runloop_st->flags &= ~(RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE);
/* Run at least one frame before attempting
* retro_serialize_size or retro_serialize */
if (video_state_get_ptr()->frame_count == 0)
runloop_st->current_core.retro_run();
/* Allocate - same 'frames' setting as runahead */
if ((failed_str = preempt_allocate(runloop_st,
settings->uints.run_ahead_frames)))
goto error;
/* Only poll in preempt_run() */
runloop_st->current_core.retro_set_input_poll(retro_input_poll_null);
/* Track requested analog states and pointing device types */
runloop_st->current_core.retro_set_input_state(preempt_input_state);
return true;
error:
preempt_deinit(runloop_st);
if (!settings->bools.preemptive_frames_hide_warnings)
runloop_msg_queue_push(
failed_str, 0, 2 * 60, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Preemptive Frames]: %s\n", failed_str);
return false;
}
static INLINE bool preempt_analog_input_dirty(preempt_t *preempt,
retro_input_state_t state_cb, unsigned port, unsigned mapped_port)
{
int16_t state[20] = {0};
uint8_t base, i;
/* axes */
for (i = 0; i < 2; i++)
{
base = i * 2;
if (preempt->analog_mask[port] & (1 << (base )))
state[base ] = state_cb(mapped_port, RETRO_DEVICE_ANALOG, i, 0);
if (preempt->analog_mask[port] & (1 << (base + 1)))
state[base + 1] = state_cb(mapped_port, RETRO_DEVICE_ANALOG, i, 1);
}
/* buttons */
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
{
if (preempt->analog_mask[port] & (1 << (i + 4)))
state[i + 4] = state_cb(mapped_port, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_BUTTON, i);
}
if (memcmp(preempt->analog_state[port], state, sizeof(state)) == 0)
return false;
memcpy(preempt->analog_state[port], state, sizeof(state));
return true;
}
static INLINE bool preempt_ptr_input_dirty(preempt_t *preempt,
retro_input_state_t state_cb, unsigned device,
unsigned port, unsigned mapped_port)
{
int16_t state[4] = {0};
unsigned count_id = 0;
unsigned x_id = 0;
unsigned id, max_id;
switch (device)
{
case RETRO_DEVICE_MOUSE:
max_id = RETRO_DEVICE_ID_MOUSE_BUTTON_5;
break;
case RETRO_DEVICE_LIGHTGUN:
x_id = RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X;
max_id = RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT;
break;
case RETRO_DEVICE_POINTER:
max_id = RETRO_DEVICE_ID_POINTER_PRESSED;
count_id = RETRO_DEVICE_ID_POINTER_COUNT;
break;
default:
return false;
}
/* x, y */
state[0] = state_cb(mapped_port, device, 0, x_id );
state[1] = state_cb(mapped_port, device, 0, x_id + 1);
/* buttons */
for (id = 2; id <= max_id; id++)
state[2] |= state_cb(mapped_port, device, 0, id) ? 1 << id : 0;
/* ptr count */
if (count_id)
state[3] = state_cb(mapped_port, device, 0, count_id);
if (memcmp(preempt->ptrdev_state[port], state, sizeof(state)) == 0)
return false;
memcpy(preempt->ptrdev_state[port], state, sizeof(state));
return true;
}
static INLINE void preempt_input_poll(preempt_t *preempt,
runloop_state_t *runloop_st, settings_t *settings)
{
size_t p;
int16_t joypad_state;
retro_input_state_t state_cb = input_driver_state_wrapper;
unsigned max_users = settings->uints.input_max_users;
input_driver_poll();
/* Check for input state changes */
for (p = 0; p < max_users; p++)
{
unsigned mapped_port = settings->uints.input_remap_ports[p];
unsigned device = settings->uints.input_libretro_device[mapped_port]
& RETRO_DEVICE_MASK;
switch (device)
{
case RETRO_DEVICE_JOYPAD:
case RETRO_DEVICE_ANALOG:
/* Check full digital joypad */
joypad_state = state_cb(mapped_port, RETRO_DEVICE_JOYPAD,
0, RETRO_DEVICE_ID_JOYPAD_MASK);
if (joypad_state != preempt->joypad_state[p])
{
preempt->joypad_state[p] = joypad_state;
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
}
/* Check requested analogs */
if ( preempt->analog_mask[p]
&& preempt_analog_input_dirty(preempt, state_cb, (unsigned)p, mapped_port))
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
break;
case RETRO_DEVICE_MOUSE:
case RETRO_DEVICE_LIGHTGUN:
case RETRO_DEVICE_POINTER:
/* Check full device state */
if (preempt_ptr_input_dirty(
preempt, state_cb, preempt->ptr_dev[p], (unsigned)p, mapped_port))
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
break;
default:
break;
}
}
/* Clear requested inputs */
memset(preempt->analog_mask, 0, max_users * sizeof(uint32_t));
memset(preempt->ptr_dev, 0, max_users * sizeof(uint8_t));
}
/* macro for preempt_run */
#define PREEMPT_NEXT_PTR(x) ((x + 1) % preempt->frames)
/**
* preempt_run:
* @preempt : pointer to preemptive frames object
*
* Call in place of core_run() for preemptive frames.
**/
void preempt_run(preempt_t *preempt, void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
struct retro_core_t *current_core = &runloop_st->current_core;
const char *failed_str = NULL;
settings_t *settings = config_get_ptr();
audio_driver_state_t *audio_st = audio_state_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
/* Poll and check for dirty input */
preempt_input_poll(preempt, runloop_st, settings);
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
if ((runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY)
&& preempt->frame_count >= preempt->frames)
{
/* Suspend A/V and run preemptive frames */
audio_st->flags |= AUDIO_FLAG_SUSPENDED;
video_st->flags &= ~VIDEO_FLAG_ACTIVE;
if (!current_core->retro_unserialize(
preempt->buffer[preempt->start_ptr], preempt->state_size))
{
failed_str = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_LOAD_STATE);
goto error;
}
current_core->retro_run();
preempt->replay_ptr = PREEMPT_NEXT_PTR(preempt->start_ptr);
while (preempt->replay_ptr != preempt->start_ptr)
{
if (!current_core->retro_serialize(
preempt->buffer[preempt->replay_ptr], preempt->state_size))
{
failed_str = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_SAVE_STATE);
goto error;
}
current_core->retro_run();
preempt->replay_ptr = PREEMPT_NEXT_PTR(preempt->replay_ptr);
}
audio_st->flags &= ~AUDIO_FLAG_SUSPENDED;
video_st->flags |= VIDEO_FLAG_ACTIVE;
}
/* Save current state and set start_ptr to oldest state */
if (!current_core->retro_serialize(
preempt->buffer[preempt->start_ptr], preempt->state_size))
{
failed_str = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_SAVE_STATE);
goto error;
}
preempt->start_ptr = PREEMPT_NEXT_PTR(preempt->start_ptr);
runloop_st->flags &= ~(RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE
| RUNLOOP_FLAG_INPUT_IS_DIRTY);
/* Run normal frame */
current_core->retro_run();
preempt->frame_count++;
return;
error:
runloop_st->flags &= ~(RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE
| RUNLOOP_FLAG_INPUT_IS_DIRTY);
audio_st->flags &= ~AUDIO_FLAG_SUSPENDED;
video_st->flags |= VIDEO_FLAG_ACTIVE;
preempt_deinit(runloop_st);
if (!settings->bools.preemptive_frames_hide_warnings)
runloop_msg_queue_push(
failed_str, 0, 2 * 60, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_ERR("[Preemptive Frames]: %s\n", failed_str);
}
void runahead_clear_variables(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
video_driver_state_t *video_st = video_state_get_ptr();
runloop_st->runahead_save_state_size = 0;
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN;
video_st->flags |= VIDEO_FLAG_RUNAHEAD_IS_ACTIVE;
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
runloop_st->runahead_last_frame_count = 0;
}