mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-23 07:59:42 +00:00
1710 lines
52 KiB
C
1710 lines
52 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <encodings/utf.h>
|
|
#include <string/stdstring.h>
|
|
#include <streams/file_stream.h>
|
|
#include <time/rtime.h>
|
|
|
|
#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)
|
|
{
|
|
runloop_state_t *runloop_st = runloop_state_get_ptr();
|
|
|
|
if (runloop_st->input_state_list)
|
|
{
|
|
int i;
|
|
/* 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];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 *sys_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 (sys_info)
|
|
{
|
|
ssize_t port;
|
|
for (port = 0; port < MAX_USERS; port++)
|
|
{
|
|
if (port < sys_info->ports.size)
|
|
{
|
|
unsigned device = (port < num_active_users)
|
|
? runloop_st->port_map[port]
|
|
: RETRO_DEVICE_NONE;
|
|
runloop_st->secondary_core.retro_set_controller_port_device(
|
|
(unsigned)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) ? true : false;
|
|
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;
|
|
}
|