diff --git a/config.def.h b/config.def.h index 829ea1834562..98430898f160 100644 --- a/config.def.h +++ b/config.def.h @@ -589,6 +589,12 @@ static const float slowmotion_ratio = 3.0; /* Maximum fast forward ratio. */ static const float fastforward_ratio = 0.0; +/* Run core logic one or more frames ahead then load the state back to reduce perceived input lag. */ +static const unsigned run_ahead_frames = 1; + +/* When using the Run Ahead feature, use a secondary instance of the core. */ +static const bool run_ahead_secondary_instance = true; + /* Enable stdin/network command interface. */ static const bool network_cmd_enable = false; static const uint16_t network_cmd_port = 55355; diff --git a/configuration.c b/configuration.c index 020302be65e3..2e259ebb126b 100644 --- a/configuration.c +++ b/configuration.c @@ -1220,6 +1220,8 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings, SETTING_BOOL("ui_menubar_enable", &settings->bools.ui_menubar_enable, true, true, false); SETTING_BOOL("suspend_screensaver_enable", &settings->bools.ui_suspend_screensaver_enable, true, true, false); SETTING_BOOL("rewind_enable", &settings->bools.rewind_enable, true, rewind_enable, false); + SETTING_BOOL("run_ahead_enabled", &settings->bools.run_ahead_enabled, true, false, false); + SETTING_BOOL("run_ahead_secondary_instance", &settings->bools.run_ahead_secondary_instance, true, false, false); SETTING_BOOL("audio_sync", &settings->bools.audio_sync, true, audio_sync, false); SETTING_BOOL("video_shader_enable", &settings->bools.video_shader_enable, true, shader_enable, false); SETTING_BOOL("video_shader_watch_files", &settings->bools.video_shader_watch_files, true, video_shader_watch_files, false); @@ -1513,6 +1515,8 @@ static struct config_uint_setting *populate_settings_uint(settings_t *settings, SETTING_UINT("video_msg_bgcolor_green", &settings->uints.video_msg_bgcolor_green, true, message_bgcolor_green, false); SETTING_UINT("video_msg_bgcolor_blue", &settings->uints.video_msg_bgcolor_blue, true, message_bgcolor_blue, false); + SETTING_UINT("run_ahead_frames", &settings->uints.run_ahead_frames, true, 1, false); + *size = count; return tmp; diff --git a/configuration.h b/configuration.h index 2f3e01eeb10c..02b646cb0a80 100644 --- a/configuration.h +++ b/configuration.h @@ -223,6 +223,8 @@ typedef struct settings bool playlist_entry_remove; bool playlist_entry_rename; bool rewind_enable; + bool run_ahead_enabled; + bool run_ahead_secondary_instance; bool pause_nonactive; bool block_sram_overwrite; bool savestate_auto_index; @@ -380,6 +382,8 @@ typedef struct settings unsigned input_remap_ids[MAX_USERS][RARCH_CUSTOM_BIND_LIST_END]; unsigned led_map[MAX_LEDS]; + + unsigned run_ahead_frames; } uints; struct diff --git a/core.h b/core.h index 35878206b4c4..06cdbac69824 100644 --- a/core.h +++ b/core.h @@ -170,6 +170,9 @@ bool core_set_poll_type(unsigned *type); /* Runs the core for one frame. */ bool core_run(void); +/* Runs the core for one frame, but does not trigger any input polling */ +bool core_run_no_input_polling(void); + bool core_init(void); bool core_deinit(void *data); diff --git a/core_impl.c b/core_impl.c index 62285e66826b..245f1bc31fe0 100644 --- a/core_impl.c +++ b/core_impl.c @@ -44,6 +44,9 @@ #include "gfx/video_driver.h" #include "audio/audio_driver.h" +#include "runahead/copy_load_info.h" +#include "runahead/secondary_core.h" + struct retro_callbacks retro_ctx; struct retro_core_t current_core; @@ -262,6 +265,9 @@ bool core_set_controller_port_device(retro_ctx_controller_info_t *pad) { if (!pad) return false; + + remember_controller_port_device(pad->port, pad->device); + current_core.retro_set_controller_port_device(pad->port, pad->device); return true; } @@ -277,6 +283,8 @@ bool core_get_memory(retro_ctx_memory_info_t *info) bool core_load_game(retro_ctx_load_content_info_t *load_info) { + set_load_content_info(load_info); + bool contentless = false; bool is_inited = false; @@ -424,6 +432,12 @@ bool core_run(void) return true; } +bool core_run_no_input_polling(void) +{ + current_core.retro_run(); + return true; +} + bool core_load(unsigned poll_type_behavior) { current_core.poll_type = poll_type_behavior; diff --git a/dynamic.c b/dynamic.c index 3e89144f811f..86c7996cb482 100644 --- a/dynamic.c +++ b/dynamic.c @@ -66,9 +66,11 @@ #include "msg_hash.h" #include "verbosity.h" +#include "runahead/secondary_core.h" + #ifdef HAVE_DYNAMIC #define SYMBOL(x) do { \ - function_t func = dylib_proc(lib_handle, #x); \ + function_t func = dylib_proc(lib_handle_local, #x); \ memcpy(¤t_core->x, &func, sizeof(func)); \ if (current_core->x == NULL) { RARCH_ERR("Failed to load symbol: \"%s\"\n", #x); retroarch_fail(1, "init_libretro_sym()"); } \ } while (0) @@ -383,14 +385,32 @@ bool libretro_get_system_info(const char *path, * Setup libretro callback symbols. Returns true on success, * or false if symbols could not be loaded. **/ -static bool load_symbols(enum rarch_core_type type, struct retro_core_t *current_core) +bool init_libretro_sym_custom(enum rarch_core_type type, struct retro_core_t *current_core, const char *lib_path, dylib_t *lib_handle_p) { + /* the library handle for use with the SYMBOL macro */ + dylib_t lib_handle_local; switch (type) { case CORE_TYPE_PLAIN: #ifdef HAVE_DYNAMIC - if (!load_dynamic_core()) - return false; + + if (lib_path == NULL || lib_handle_p == NULL) + { + if (!load_dynamic_core()) + return false; + lib_handle_local = lib_handle; + } + else + { + /* for a secondary core, we already have a primary library loaded, so we can skip some checks and just load the library */ + retro_assert(lib_path != NULL && lib_handle_p != NULL); + lib_handle_local = dylib_load(lib_path); + if (lib_handle_local == NULL) + { + return false; + } + *lib_handle_p = lib_handle_local; + } #endif SYMBOL(retro_init); @@ -615,6 +635,11 @@ static bool load_symbols(enum rarch_core_type type, struct retro_core_t *current return true; } +static bool load_symbols(enum rarch_core_type type, struct retro_core_t *current_core) +{ + return init_libretro_sym_custom(type, current_core, NULL, NULL); +} + /** * init_libretro_sym: * @type : Type of core to be loaded. @@ -634,6 +659,8 @@ bool init_libretro_sym(enum rarch_core_type type, struct retro_core_t *current_c if (!load_symbols(type, current_core)) return false; + /* remember last core type created, so creating a secondary core will know what core type to use */ + set_last_core_type(type); return true; } diff --git a/dynamic.h b/dynamic.h index 2432704e489c..e930a2edc800 100644 --- a/dynamic.h +++ b/dynamic.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "core_type.h" @@ -132,6 +133,8 @@ bool libretro_get_shared_context(void); bool init_libretro_sym(enum rarch_core_type type, struct retro_core_t *core); +bool init_libretro_sym_custom(enum rarch_core_type type, struct retro_core_t *current_core, const char *lib_path, dylib_t *lib_handle_p); + /** * uninit_libretro_sym: * diff --git a/griffin/griffin.c b/griffin/griffin.c index 7b321d959ea3..40b0f89ac729 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -56,6 +56,9 @@ COMPATIBILITY #include "../libretro-common/compat/compat_fnmatch.c" #include "../libretro-common/compat/fopen_utf8.c" +#if defined(HAVE_DYNAMIC) && HAVE_DYNAMIC +#include "../libretro-common/compat/unlink_utf8.c" +#endif #include "../libretro-common/memmap/memalign.c" /*============================================================ @@ -1252,6 +1255,13 @@ MENU #include "../libretro-common/net/net_http_parse.c" #endif +#include "../runahead/mem_util.c" +#include "../runahead/secondary_core.c" +#include "../runahead/run_ahead.c" +#include "../runahead/copy_load_info.c" +#include "../runahead/dirty_input.c" +#include "../runahead/mylist.c" + /*============================================================ DEPENDENCIES ============================================================ */ diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index e914210d2727..4152488f14c2 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -989,6 +989,12 @@ MSG_HASH(MENU_ENUM_LABEL_SHUTDOWN, "shutdown") MSG_HASH(MENU_ENUM_LABEL_SLOWMOTION_RATIO, "slowmotion_ratio") +MSG_HASH(MENU_ENUM_LABEL_RUN_AHEAD_ENABLED, + "run_ahead_enabled") +MSG_HASH(MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE, + "run_ahead_secondary_instance") +MSG_HASH(MENU_ENUM_LABEL_RUN_AHEAD_FRAMES, + "run_ahead_frames") MSG_HASH(MENU_ENUM_LABEL_SORT_SAVEFILES_ENABLE, "sort_savefiles_enable") MSG_HASH(MENU_ENUM_LABEL_SORT_SAVESTATES_ENABLE, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index caca939fe1ee..97584df271de 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1445,6 +1445,12 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_SHUTDOWN, "Shutdown") MSG_HASH(MENU_ENUM_LABEL_VALUE_SLOWMOTION_RATIO, "Slow-Motion Ratio") +MSG_HASH(MENU_ENUM_LABEL_VALUE_RUN_AHEAD_ENABLED, + "Run-Ahead to Reduce Latency") +MSG_HASH(MENU_ENUM_LABEL_VALUE_RUN_AHEAD_FRAMES, + "Number of Frames to Run Ahead") +MSG_HASH(MENU_ENUM_LABEL_VALUE_RUN_AHEAD_SECONDARY_INSTANCE, + "Runahead Use Second Instance") MSG_HASH(MENU_ENUM_LABEL_VALUE_SORT_SAVEFILES_ENABLE, "Sort Saves In Folders") MSG_HASH(MENU_ENUM_LABEL_VALUE_SORT_SAVESTATES_ENABLE, @@ -2720,6 +2726,18 @@ MSG_HASH( MENU_ENUM_SUBLABEL_SLOWMOTION_RATIO, "When in slow motion, content will slow down by the factor specified/set." ) +MSG_HASH( + MENU_ENUM_SUBLABEL_RUN_AHEAD_ENABLED, + "Run core logic one or more frames ahead then load the state back to reduce perceived input lag." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_RUN_AHEAD_FRAMES, + "The number of frames to run ahead. Causes gameplay issues such as jitter if you exceed the number of lag frames internal to the game." + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_RUN_AHEAD_SECONDARY_INSTANCE, + "Use a second instance of the RetroArch core to run ahead. Prevents audio problems due to loading state." + ) MSG_HASH( MENU_ENUM_SUBLABEL_REWIND_ENABLE, "Enable rewinding. This will take a performance hit when playing." diff --git a/libretro-common/compat/unlink_utf8.c b/libretro-common/compat/unlink_utf8.c new file mode 100644 index 000000000000..ce3bcad01e6d --- /dev/null +++ b/libretro-common/compat/unlink_utf8.c @@ -0,0 +1,30 @@ +#include +#include +#include "boolean.h" +#include + +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX) +#ifndef LEGACY_WIN32 +#define LEGACY_WIN32 +#endif +#endif + +#ifdef _WIN32 + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include + +bool unlink_utf8(const char * filename) +{ +#if defined(LEGACY_WIN32) + bool result = DeleteFileA(filename_w); +#else + wchar_t * filename_w = utf8_to_utf16_string_alloc(filename); + bool result = DeleteFileW(filename_w); + free(filename_w); +#endif + return result; +} + +#endif diff --git a/libretro-common/include/compat/unlink_utf8.h b/libretro-common/include/compat/unlink_utf8.h new file mode 100644 index 000000000000..59a3d4c9f90c --- /dev/null +++ b/libretro-common/include/compat/unlink_utf8.h @@ -0,0 +1,23 @@ +#ifndef __UNLINK_UTF8_H +#define __UNLINK_UTF8_H + +#include "boolean.h" + +#ifdef _WIN32 + +#if __cplusplus +extern "C" +{ +#endif + +bool unlink_utf8(const char * filename); + +#if __cplusplus +} +#endif + +#else +#include +#define unlink_utf8 unlink +#endif +#endif diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 23d511aabd7a..319ee1f87577 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -172,6 +172,9 @@ default_sublabel_macro(action_bind_sublabel_savestate_auto_index, MENU_ default_sublabel_macro(action_bind_sublabel_block_sram_overwrite, MENU_ENUM_SUBLABEL_BLOCK_SRAM_OVERWRITE) default_sublabel_macro(action_bind_sublabel_fastforward_ratio, MENU_ENUM_SUBLABEL_FASTFORWARD_RATIO) default_sublabel_macro(action_bind_sublabel_slowmotion_ratio, MENU_ENUM_SUBLABEL_SLOWMOTION_RATIO) +default_sublabel_macro(action_bind_sublabel_run_ahead_enabled, MENU_ENUM_SUBLABEL_RUN_AHEAD_ENABLED) +default_sublabel_macro(action_bind_sublabel_run_ahead_secondary_instance, MENU_ENUM_SUBLABEL_RUN_AHEAD_SECONDARY_INSTANCE) +default_sublabel_macro(action_bind_sublabel_run_ahead_frames, MENU_ENUM_SUBLABEL_RUN_AHEAD_FRAMES) default_sublabel_macro(action_bind_sublabel_rewind, MENU_ENUM_SUBLABEL_REWIND_ENABLE) default_sublabel_macro(action_bind_sublabel_rewind_granularity, MENU_ENUM_SUBLABEL_REWIND_GRANULARITY) default_sublabel_macro(action_bind_sublabel_libretro_log_level, MENU_ENUM_SUBLABEL_LIBRETRO_LOG_LEVEL) @@ -1109,6 +1112,15 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_SLOWMOTION_RATIO: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_slowmotion_ratio); break; + case MENU_ENUM_LABEL_RUN_AHEAD_ENABLED: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_run_ahead_enabled); + break; + case MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_run_ahead_secondary_instance); + break; + case MENU_ENUM_LABEL_RUN_AHEAD_FRAMES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_run_ahead_frames); + break; case MENU_ENUM_LABEL_FASTFORWARD_RATIO: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_fastforward_ratio); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index bdc3c8df3ffb..768ea75d2940 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4963,6 +4963,15 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_SLOWMOTION_RATIO, PARSE_ONLY_FLOAT, false); + menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_RUN_AHEAD_ENABLED, + PARSE_ONLY_BOOL, false); + menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_RUN_AHEAD_FRAMES, + PARSE_ONLY_UINT, false); + menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE, + PARSE_ONLY_BOOL, false); if (settings->bools.menu_show_advanced_settings) menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_MENU_THROTTLE_FRAMERATE, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 6f17dcfeaefa..55cda7d72da2 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -4834,6 +4834,51 @@ static bool setting_append_list( general_read_handler); menu_settings_list_current_add_range(list, list_info, 1, 10, 0.1, true, true); + CONFIG_BOOL( + list, list_info, + &settings->bools.run_ahead_enabled, + MENU_ENUM_LABEL_RUN_AHEAD_ENABLED, + MENU_ENUM_LABEL_VALUE_RUN_AHEAD_ENABLED, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE + ); + + CONFIG_UINT( + list, list_info, + &settings->uints.run_ahead_frames, + MENU_ENUM_LABEL_RUN_AHEAD_FRAMES, + MENU_ENUM_LABEL_VALUE_RUN_AHEAD_FRAMES, + 1, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + menu_settings_list_current_add_range(list, list_info, 1, 6, 1, true, true); + + CONFIG_BOOL( + list, list_info, + &settings->bools.run_ahead_secondary_instance, + MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE, + MENU_ENUM_LABEL_VALUE_RUN_AHEAD_SECONDARY_INSTANCE, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE + ); + CONFIG_BOOL( list, list_info, &settings->bools.menu_throttle_framerate, diff --git a/msg_hash.h b/msg_hash.h index 181814deb1ec..59c330a01ae2 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1262,6 +1262,9 @@ enum msg_hash_enums MENU_LABEL(THUMBNAILS_DIRECTORY), MENU_LABEL(SLOWMOTION_RATIO), + MENU_LABEL(RUN_AHEAD_ENABLED), + MENU_LABEL(RUN_AHEAD_SECONDARY_INSTANCE), + MENU_LABEL(RUN_AHEAD_FRAMES), MENU_LABEL(TURBO), /* Privacy settings */ diff --git a/retroarch.c b/retroarch.c index ed8ef60679e4..ca6187a52cb5 100644 --- a/retroarch.c +++ b/retroarch.c @@ -118,6 +118,8 @@ #include "command.h" +#include "runahead/run_ahead.h" + #define _PSUPP(var, name, desc) printf(" %s:\n\t\t%s: %s\n", name, desc, _##var##_supp ? "yes" : "no") #define FAIL_CPU(simd_type) do { \ @@ -3248,7 +3250,15 @@ int runloop_iterate(unsigned *sleep_ms) if ((settings->uints.video_frame_delay > 0) && !input_nonblock_state) retro_sleep(settings->uints.video_frame_delay); - core_run(); + /* Run Ahead Feature replaces the call to core_run in this loop */ + if (settings->bools.run_ahead_enabled && settings->uints.run_ahead_frames > 0) + { + run_ahead(settings->uints.run_ahead_frames, settings->bools.run_ahead_secondary_instance); + } + else + { + core_run(); + } #ifdef HAVE_CHEEVOS if (runloop_check_cheevos()) diff --git a/runahead/copy_load_info.c b/runahead/copy_load_info.c new file mode 100644 index 000000000000..4d0f394d604e --- /dev/null +++ b/runahead/copy_load_info.c @@ -0,0 +1,177 @@ +#include "libretro.h" +#include "mem_util.h" +#include "core.h" +#include +#include +#include "lists/string_list.h" + +retro_ctx_load_content_info_t *load_content_info; +enum rarch_core_type last_core_type; + +static void free_retro_game_info(struct retro_game_info *dest) +{ + if (dest == NULL) return; + FREE(dest->path); + FREE(dest->data); + FREE(dest->meta); +} + +static struct retro_game_info* clone_retro_game_info(const struct retro_game_info *src) +{ + struct retro_game_info *dest; + if (src == NULL) return NULL; + dest = (struct retro_game_info*)malloc_zero(sizeof(struct retro_game_info)); + dest->path = strcpy_alloc(src->path); + dest->data = memcpy_alloc(src->data, src->size); + dest->size = src->size; + dest->meta = strcpy_alloc(src->meta); + return dest; +} + +static void free_string_list(struct string_list *dest) +{ + int i; + if (dest == NULL) return; + for (i = 0; i < dest->size; i++) + { + FREE(dest->elems[i].data); + } + FREE(dest->elems); +} + +static struct string_list* clone_string_list(const struct string_list *src) +{ + int i; + struct string_list *dest; + if (src == NULL) return NULL; + dest = (struct string_list*)malloc_zero(sizeof(struct string_list)); + dest->size = src->size; + dest->cap = src->cap; + dest->elems = (struct string_list_elem*)malloc_zero(sizeof(struct string_list_elem) * dest->size); + for (i = 0; i < src->size; i++) + { + dest->elems[i].data = strcpy_alloc(src->elems[i].data); + dest->elems[i].attr = src->elems[i].attr; + } + return dest; +} + +#if 0 +/* for cloning the Special field, however, attempting to use this feature crashes retroarch */ +static void free_retro_subsystem_memory_info(struct retro_subsystem_memory_info *dest) +{ + if (dest == NULL) return; + FREE(dest->extension); +} + +static void clone_retro_subsystem_memory_info(struct retro_subsystem_memory_info* dest, const struct retro_subsystem_memory_info *src) +{ + dest->extension = strcpy_alloc(src->extension); + dest->type = src->type; +} + +static void free_retro_subsystem_rom_info(struct retro_subsystem_rom_info *dest) +{ + int i; + if (dest == NULL) return; + FREE(dest->desc); + FREE(dest->valid_extensions); + for (i = 0; i < dest->num_memory; i++) + { + free_retro_subsystem_memory_info((struct retro_subsystem_memory_info*)&dest->memory[i]); + } + FREE(dest->memory); +} + +static void clone_retro_subsystem_rom_info(struct retro_subsystem_rom_info *dest, const struct retro_subsystem_rom_info *src) +{ + int i; + retro_subsystem_memory_info *memory; + dest->need_fullpath = src->need_fullpath; + dest->block_extract = src->block_extract; + dest->required = src->required; + dest->num_memory = src->num_memory; + dest->desc = strcpy_alloc(src->desc); + dest->valid_extensions = strcpy_alloc(src->valid_extensions); + memory = (struct retro_subsystem_memory_info*)malloc_zero(dest->num_memory * sizeof(struct retro_subsystem_memory_info)); + dest->memory = memory; + for (i = 0; i < dest->num_memory; i++) + { + clone_retro_subsystem_memory_info(&memory[i], &src->memory[i]); + } +} + +static void free_retro_subsystem_info(struct retro_subsystem_info *dest) +{ + int i; + if (dest == NULL) return; + FREE(dest->desc); + FREE(dest->ident); + for (i = 0; i < dest->num_roms; i++) + { + free_retro_subsystem_rom_info((struct retro_subsystem_rom_info*)&dest->roms[i]); + } + FREE(dest->roms); +} + +static retro_subsystem_info* clone_retro_subsystem_info(struct const retro_subsystem_info *src) +{ + int i; + retro_subsystem_info *dest; + retro_subsystem_rom_info *roms; + if (src == NULL) return NULL; + dest = (struct retro_subsystem_info*)malloc_zero(sizeof(struct retro_subsystem_info)); + dest->desc = strcpy_alloc(src->desc); + dest->ident = strcpy_alloc(src->ident); + dest->num_roms = src->num_roms; + dest->id = src->id; + + roms = (struct retro_subsystem_rom_info*)malloc_zero(src->num_roms * sizeof(struct retro_subsystem_rom_info)); + dest->roms = roms; + for (i = 0; i < src->num_roms; i++) + { + clone_retro_subsystem_rom_info(&roms[i], &src->roms[i]); + } + return dest; +} +#endif + +static void free_retro_ctx_load_content_info(struct retro_ctx_load_content_info *dest) +{ + if (dest == NULL) return; + free_retro_game_info(dest->info); + free_string_list((struct string_list*)dest->content); + FREE(dest->info); + FREE(dest->content); +#if 0 + free_retro_subsystem_info((retro_subsystem_info*)dest->special); + FREE(dest->special); +#endif +} + +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; + if (src == NULL || src->special != NULL) return NULL; /* refuse to deal with the Special field */ + + dest = (struct retro_ctx_load_content_info*)malloc_zero(sizeof(struct retro_ctx_load_content_info)); + dest->info = clone_retro_game_info(src->info); + dest->content = clone_string_list(src->content); + dest->special = NULL; +#if 0 + dest->special = clone_retro_subsystem_info(src->special); +#endif + return dest; +} + +void set_load_content_info(const retro_ctx_load_content_info_t *ctx) +{ + free_retro_ctx_load_content_info(load_content_info); + free(load_content_info); + load_content_info = clone_retro_ctx_load_content_info(ctx); +} + +void set_last_core_type(enum rarch_core_type type) +{ + last_core_type = type; +} diff --git a/runahead/copy_load_info.h b/runahead/copy_load_info.h new file mode 100644 index 000000000000..9223ff4748a4 --- /dev/null +++ b/runahead/copy_load_info.h @@ -0,0 +1,16 @@ +#ifndef __COPY_LOAD_INFO_H__ +#define __COPY_LOAD_INFO_H__ + +#include "retro_common_api.h" +#include "libretro.h" +#include "core.h" +#include "boolean.h" + +RETRO_BEGIN_DECLS + +void set_load_content_info(const retro_ctx_load_content_info_t *ctx); +void set_last_core_type(enum rarch_core_type type); + +RETRO_END_DECLS + +#endif diff --git a/runahead/dirty_input.c b/runahead/dirty_input.c new file mode 100644 index 000000000000..eab75dec9c80 --- /dev/null +++ b/runahead/dirty_input.c @@ -0,0 +1,166 @@ +#include "core.h" +#include "boolean.h" +#include "mylist.h" +#include "dynamic.h" +#include "mem_util.h" + +bool input_is_dirty; +static MyList *inputStateList; + +typedef struct InputListElement_t +{ + unsigned port; + unsigned device; + unsigned index; + int16_t state[36]; +} InputListElement; + +typedef struct retro_core_t _retro_core_t; +extern _retro_core_t current_core; +extern struct retro_callbacks retro_ctx; + +typedef bool(*LoadStateFunction)(const void*, size_t); + +static function_t retro_reset_callback_original = NULL; +static LoadStateFunction retro_unserialize_callback_original = NULL; +static retro_input_state_t input_state_callback_original; + +static void reset_hook(void); +static bool unserialze_hook(const void *buf, size_t size); + +static void* InputListElementConstructor(void) +{ + void *ptr; + const int size = sizeof(InputListElement); + ptr = malloc_zero(size); + return ptr; +} + +static void input_state_destory(void) +{ + mylist_destroy(&inputStateList); +} + +static void input_state_setlast(unsigned port, unsigned device, unsigned index, unsigned id, int16_t value) +{ + int i; + InputListElement *element; + + if (inputStateList == NULL) + { + mylist_create(&inputStateList, 16, InputListElementConstructor, free); + } + + /* find list item */ + for (i = 0; i < inputStateList->size; i++) + { + element = (InputListElement*)inputStateList->data[i]; + if (element->port == port && element->device == device && element->index == index) + { + element->state[id] = value; + return; + } + } + element = (InputListElement*)mylist_add_element(inputStateList); + element->port = port; + element->device = device; + element->index = index; + element->state[id] = value; +} + +static int16_t input_state_getlast(unsigned port, unsigned device, unsigned index, unsigned id) +{ + int i; + InputListElement *element; + if (inputStateList == NULL) + { + return 0; + } + /* find list item */ + for (i = 0; i < inputStateList->size; i++) + { + element = (InputListElement*)inputStateList->data[i]; + if (element->port == port && element->device == device && element->index == index) + { + return element->state[id]; + } + } + return 0; +} + +static int16_t input_state_with_logging(unsigned port, unsigned device, unsigned index, unsigned id) +{ + if (input_state_callback_original != NULL) + { + int16_t result = input_state_callback_original(port, device, index, id); + int16_t lastInput = input_state_getlast(port, device, index, id); + if (result != lastInput) + { + input_is_dirty = true; + } + input_state_setlast(port, device, index, id, result); + return result; + } + return 0; +} + +static void reset_hook(void) +{ + input_is_dirty = true; + if (retro_reset_callback_original) + { + retro_reset_callback_original(); + } +} + +static bool unserialze_hook(const void *buf, size_t size) +{ + input_is_dirty = true; + if (retro_unserialize_callback_original) + { + return retro_unserialize_callback_original(buf, size); + } + return false; +} + +void add_input_state_hook(void) +{ + if (input_state_callback_original == NULL) + { + input_state_callback_original = retro_ctx.state_cb; + retro_ctx.state_cb = input_state_with_logging; + current_core.retro_set_input_state(retro_ctx.state_cb); + } + if (retro_reset_callback_original == NULL) + { + retro_reset_callback_original = current_core.retro_reset; + current_core.retro_reset = reset_hook; + } + if (retro_unserialize_callback_original == NULL) + { + retro_unserialize_callback_original = current_core.retro_unserialize; + current_core.retro_unserialize = unserialze_hook; + } +} + +void remove_input_state_hook(void) +{ + if (input_state_callback_original != NULL) + { + retro_ctx.state_cb = input_state_callback_original; + current_core.retro_set_input_state(retro_ctx.state_cb); + input_state_callback_original = NULL; + input_state_destory(); + } + if (retro_reset_callback_original != NULL) + { + current_core.retro_reset = retro_reset_callback_original; + retro_reset_callback_original = NULL; + } + if (retro_unserialize_callback_original != NULL) + { + current_core.retro_unserialize = retro_unserialize_callback_original; + retro_unserialize_callback_original = NULL; + } +} + diff --git a/runahead/dirty_input.h b/runahead/dirty_input.h new file mode 100644 index 000000000000..12623d2b020d --- /dev/null +++ b/runahead/dirty_input.h @@ -0,0 +1,15 @@ +#ifndef __DIRTY_INPUT_H___ +#define __DIRTY_INPUT_H___ + +#include "retro_common_api.h" +#include "boolean.h" + +RETRO_BEGIN_DECLS + +extern bool input_is_dirty; +void add_input_state_hook(void); +void remove_input_state_hook(void); + +RETRO_END_DECLS + +#endif diff --git a/runahead/mem_util.c b/runahead/mem_util.c new file mode 100644 index 000000000000..5d130e742502 --- /dev/null +++ b/runahead/mem_util.c @@ -0,0 +1,95 @@ +#include "mem_util.h" + +void *malloc_zero(size_t size) +{ + void *ptr; + ptr = malloc(size); + memset(ptr, 0, size); + return ptr; +} + +void free_str(char **str_p) +{ + free_ptr((void**)str_p); +} + +void free_ptr(void **data_p) +{ + if (data_p == NULL) + { + return; + } + if (*data_p == NULL) + { + return; + } + free(*data_p); + *data_p = NULL; +} + +void *memcpy_alloc(const void *src, size_t size) +{ + void *result; + result = malloc(size); + memcpy(result, src, size); + return result; +} + +char *strcpy_alloc(const char *sourceStr) +{ + size_t len; + char *result; + if (sourceStr == NULL) + { + len = 0; + } + else + { + len = strlen(sourceStr); + } + if (len == 0) + { + return NULL; + } + result = (char*)malloc(len + 1); + strcpy(result, sourceStr); + return result; +} + +char *strcpy_alloc_force(const char *sourceStr) +{ + char *result; + result = strcpy_alloc(sourceStr); + if (result == NULL) + { + result = (char*)malloc_zero(1); + } + return result; +} + +void strcat_alloc(char ** destStr_p, const char *appendStr) +{ + size_t len1, len2, newLen; + char *destStr; + + destStr = *destStr_p; + + if (destStr == NULL) + { + destStr = strcpy_alloc_force(appendStr); + *destStr_p = destStr; + return; + } + + if (appendStr == NULL) + { + return; + } + + len1 = strlen(destStr); + len2 = strlen(appendStr); + newLen = len1 + len2 + 1; + destStr = (char*)realloc(destStr, newLen); + *destStr_p = destStr; + strcpy(destStr + len1, appendStr); +} diff --git a/runahead/mem_util.h b/runahead/mem_util.h new file mode 100644 index 000000000000..9c97e0022200 --- /dev/null +++ b/runahead/mem_util.h @@ -0,0 +1,26 @@ +#ifndef __MEM_UTIL__ +#define __MEM_UTIL__ + +#include "retro_common_api.h" +#include "boolean.h" + +#include +#include +#include + +#define FREE(xxxx) if ((xxxx) != NULL) { free((void*)(xxxx)); } (xxxx) = NULL + +RETRO_BEGIN_DECLS + +void *malloc_zero(size_t size); +void free_str(char **str_p); +void free_ptr(void **data_p); +char *strcpy_alloc(const char *sourceStr); +char *strcpy_alloc_force(const char *sourceStr); +void strcat_alloc(char ** destStr_p, const char *appendStr); +void *memcpy_alloc(const void *src, size_t size); + +RETRO_END_DECLS + +#endif + diff --git a/runahead/mylist.c b/runahead/mylist.c new file mode 100644 index 000000000000..e5e1d851094c --- /dev/null +++ b/runahead/mylist.c @@ -0,0 +1,151 @@ +#include "mylist.h" +#include +#include +#include "mem_util.h" + +void mylist_resize(MyList *list, int newSize, bool runConstructor) +{ + int newCapacity; + int oldSize; + int i; + void *element; + if (newSize < 0) newSize = 0; + if (list == NULL) return; + newCapacity = newSize; + oldSize = list->size; + if (newSize == oldSize) return; + if (newSize > list->capacity) + { + if (newCapacity < list->capacity * 2) + { + newCapacity = list->capacity * 2; + } + /* try to realloc */ + list->data = (void**)realloc((void*)list->data, newCapacity * sizeof(void*)); + for (i = list->capacity; i < newCapacity; i++) + { + list->data[i] = NULL; + } + list->capacity = newCapacity; + } + if (newSize <= list->size) + { + for (i = newSize; i < list->size; i++) + { + element = list->data[i]; + if (element != NULL) + { + list->Destructor(element); + list->data[i] = NULL; + } + } + } + else + { + for (i = list->size; i < newSize; i++) + { + if (runConstructor) + { + list->data[i] = list->Constructor(); + } + else + { + list->data[i] = NULL; + } + } + } + list->size = newSize; +} + +void *mylist_add_element(MyList *list) +{ + int oldSize; + if (list == NULL) return NULL; + oldSize = list->size; + mylist_resize(list, oldSize + 1, true); + return list->data[oldSize]; +} + +void mylist_create(MyList **list_p, int initialCapacity, constructor_t constructor, destructor_t destructor) +{ + MyList *list; + if (list_p == NULL) return; + if (initialCapacity < 0) initialCapacity = 0; + list = *list_p; + if (list != NULL) + { + mylist_destroy(list_p); + } + list = (MyList*)malloc(sizeof(MyList)); + *list_p = list; + list->size = 0; + list->Constructor = constructor; + list->Destructor = destructor; + if (initialCapacity > 0) + { + list->data = (void**)malloc_zero(initialCapacity * sizeof(void*)); + list->capacity = initialCapacity; + } + else + { + list->data = NULL; + list->capacity = 0; + } +} + +void mylist_destroy(MyList **list_p) +{ + if (list_p == NULL) return; + MyList *list; + list = *list_p; + if (list != NULL) + { + mylist_resize(list, 0, false); + free(list->data); + free(list); + *list_p = NULL; + } +} + +void mylist_assign(MyList *list, int index, void *value) +{ + void *oldElement; + if (index < 0 || index >= list->size) + { + return; + } + oldElement = list->data[index]; + list->Destructor(oldElement); + list->data[index] = value; +} + +void mylist_remove_at(MyList *list, int index) +{ + int i; + + if (index < 0 || index >= list->size) + { + return; + } + mylist_assign(list, index, NULL); + for (i = index + 1; i < list->size; i++) + { + list->data[i - 1] = list->data[i]; + } + list->size--; + list->data[list->size] = NULL; +} + +void mylist_pop_front(MyList *list) +{ + mylist_remove_at(list, 0); +} + +void mylist_push_back(MyList *list, void *value) +{ + int oldSize; + if (list == NULL) return; + oldSize = list->size; + mylist_resize(list, oldSize + 1, false); + list->data[oldSize] = value; +} diff --git a/runahead/mylist.h b/runahead/mylist.h new file mode 100644 index 000000000000..08438378f1fc --- /dev/null +++ b/runahead/mylist.h @@ -0,0 +1,32 @@ +#ifndef __MYLIST_H__ +#define __MYLIST_H__ + +#include "retro_common_api.h" +#include "boolean.h" + +RETRO_BEGIN_DECLS + +typedef void* (*constructor_t)(void); +typedef void(*destructor_t)(void*); + +typedef struct MyList_t +{ + void **data; + int capacity; + int size; + constructor_t Constructor; + destructor_t Destructor; +} MyList; + +void *mylist_add_element(MyList *list); +void mylist_resize(MyList *list, int newSize, bool runConstructor); +void mylist_create(MyList **list_p, int initialCapacity, constructor_t constructor, destructor_t destructor); +void mylist_destroy(MyList **list_p); +void mylist_assign(MyList *list, int index, void *value); +void mylist_remove_at(MyList *list, int index); +void mylist_pop_front(MyList *list); + +RETRO_END_DECLS + +#endif + diff --git a/runahead/run_ahead.c b/runahead/run_ahead.c new file mode 100644 index 000000000000..1e5e6739741d --- /dev/null +++ b/runahead/run_ahead.c @@ -0,0 +1,425 @@ +#include "core.h" +#include "dynamic.h" +#include "audio/audio_driver.h" +#include "gfx/video_driver.h" +#include "boolean.h" +#include +#include +#include +#include "dirty_input.h" +#include "mylist.h" +#include "secondary_core.h" + +static void *runahead_save_state_alloc(void); +static void runahead_save_state_free(void *state); +static void runahead_save_state_list_init(size_t saveStateSize); +static void runahead_save_state_list_destroy(void); +static void runahead_save_state_list_rotate(void); + +static void add_hooks(void); +static void remove_hooks(void); +static void deinit_hook(void); +static void unload_hook(void); + +static void runahead_clear_variables(void); +static void runahead_check_for_gui(void); +static void runahead_error(void); +static bool runahead_create(void); +static bool runahead_save_state(void); +static bool runahead_load_state(void); +static bool runahead_load_state_secondary(void); +static bool runahead_run_secondary(void); +static void runahead_suspend_audio(void); +static void runahead_resume_audio(void); +static void runahead_suspend_video(void); +static void runahead_resume_video(void); +void run_ahead(int runAheadCount, bool useSecondary); +void runahead_destroy(void); + +static void runahead_destroy(void); +static bool runahead_create(void); + +static size_t runahead_save_state_size = -1; + +/* Save State List for Run Ahead */ +static MyList *runahead_save_state_list; + +static void *runahead_save_state_alloc(void) +{ + retro_ctx_serialize_info_t *savestate; + savestate = (retro_ctx_serialize_info_t*)malloc(sizeof(retro_ctx_serialize_info_t)); + savestate->size = runahead_save_state_size; + if (runahead_save_state_size > 0 && runahead_save_state_size != -1) + { + savestate->data = malloc(runahead_save_state_size); + savestate->data_const = savestate->data; + } + else + { + savestate->data = NULL; + savestate->data_const = NULL; + savestate->size = 0; + } + return savestate; +} + +static void runahead_save_state_free(void *state) +{ + retro_ctx_serialize_info_t *savestate; + savestate = (retro_ctx_serialize_info_t*)state; + if (savestate == NULL) return; + free(savestate->data); + free(savestate); +} + +static void runahead_save_state_list_init(size_t saveStateSize) +{ + runahead_save_state_size = saveStateSize; + mylist_create(&runahead_save_state_list, 16, runahead_save_state_alloc, runahead_save_state_free); +} + +static void runahead_save_state_list_destroy(void) +{ + mylist_destroy(&runahead_save_state_list); +} + +static void runahead_save_state_list_rotate(void) +{ + int i; + void *element; + void *firstElement; + firstElement = runahead_save_state_list->data[0]; + for (i = 1; i < runahead_save_state_list->size; i++) + { + runahead_save_state_list->data[i - 1] = runahead_save_state_list->data[i - 1]; + } + runahead_save_state_list->data[runahead_save_state_list->size - 1] = firstElement; +} + +/* Hooks - Hooks to cleanup, and add dirty input hooks */ + +static function_t originalRetroDeinit = NULL; +static function_t originalRetroUnload = NULL; + +typedef struct retro_core_t _retro_core_t; +extern _retro_core_t current_core; +extern struct retro_callbacks retro_ctx; + +static void deinit_hook(void); +static void unload_hook(void); + +static void add_hooks(void) +{ + if (originalRetroDeinit == NULL) + { + originalRetroDeinit = current_core.retro_deinit; + current_core.retro_deinit = deinit_hook; + } + if (originalRetroUnload == NULL) + { + originalRetroUnload = current_core.retro_unload_game; + current_core.retro_unload_game = unload_hook; + } + add_input_state_hook(); +} + +static void remove_hooks(void) +{ + if (originalRetroDeinit != NULL) + { + current_core.retro_deinit = originalRetroDeinit; + originalRetroDeinit = NULL; + } + if (originalRetroUnload != NULL) + { + current_core.retro_unload_game = originalRetroUnload; + originalRetroUnload = NULL; + } + remove_input_state_hook(); +} + +static void deinit_hook(void) +{ + remove_hooks(); + runahead_destroy(); + secondary_core_destroy(); + if (current_core.retro_deinit) + { + current_core.retro_deinit(); + } +} + +static void unload_hook(void) +{ + remove_hooks(); + runahead_destroy(); + secondary_core_destroy(); + if (current_core.retro_unload_game) + { + current_core.retro_unload_game(); + } +} + +/* Runahead Code */ + +static bool runahead_video_driver_is_active = true; +static bool runahead_available = true; +static bool runahead_secondary_core_available = true; +static bool runahead_force_input_dirty = true; +static uint64_t runahead_last_frame_count = 0; + +static void runahead_clear_variables(void) +{ + runahead_save_state_size = -1; + runahead_video_driver_is_active = true; + runahead_available = true; + runahead_secondary_core_available = true; + runahead_force_input_dirty = true; + runahead_last_frame_count = 0; +} + +static void runahead_check_for_gui(void) +{ + /* Hack: If we were in the GUI, force a resync. */ + bool is_dirty; + bool is_alive, is_focused; + uint64_t frame_count; + video_driver_get_status(&frame_count, &is_alive, &is_focused); + if (frame_count != runahead_last_frame_count + 1) + { + runahead_force_input_dirty = true; + } + runahead_last_frame_count = frame_count; +} + +void run_ahead(int runAheadCount, bool useSecondary) +{ + int frameNumber; + bool okay; + bool lastFrame; + bool suspendedFrame; + + if (runAheadCount <= 0 || !runahead_available) + { + core_run(); + runahead_force_input_dirty = true; + return; + } + + if (runahead_save_state_size == -1) + { + if (!runahead_create()) + { + /*runloop_msg_queue_push("RunAhead has been disabled because the core does not support savestates", 1, 180, true);*/ + core_run(); + runahead_force_input_dirty = true; + return; + } + } + + runahead_check_for_gui(); + + if (!useSecondary || !HAVE_DYNAMIC || !runahead_secondary_core_available) + { + /* TODO: multiple savestates for higher performance when not using secondary core */ + for (frameNumber = 0; frameNumber <= runAheadCount; frameNumber++) + { + lastFrame = frameNumber == runAheadCount; + suspendedFrame = !lastFrame; + if (suspendedFrame) + { + runahead_suspend_audio(); + runahead_suspend_video(); + } + if (frameNumber == 0) + { + core_run(); + } + else + { + core_run_no_input_polling(); + } + if (suspendedFrame) + { + runahead_resume_video(); + runahead_resume_audio(); + } + if (frameNumber == 0) + { + if (!runahead_save_state()) + { + /*runloop_msg_queue_push("RunAhead has been disabled due to save state failure", 1, 180, true);*/ + return; + } + } + if (lastFrame) + { + if (!runahead_load_state()) + { + /*runloop_msg_queue_push("RunAhead has been disabled due to load state failure", 1, 180, true);*/ + return; + } + } + } + } + else + { +#if HAVE_DYNAMIC + /* run main core with video suspended */ + runahead_suspend_video(); + core_run(); + runahead_resume_video(); + + if (input_is_dirty || runahead_force_input_dirty) + { + input_is_dirty = false; + if (!runahead_save_state()) + { + return; + } + if (!runahead_load_state_secondary()) + { + /*runloop_msg_queue_push("Could not create a secondary core. RunAhead will only use the main core now.", 1, 180, true);*/ + return; + } + for (int frameCount = 0; frameCount < runAheadCount - 1; frameCount++) + { + runahead_suspend_video(); + runahead_suspend_audio(); + okay = runahead_run_secondary(); + runahead_resume_audio(); + runahead_resume_video(); + if (!okay) + { + /*runloop_msg_queue_push("Could not create a secondary core. RunAhead will only use the main core now.", 1, 180, true);*/ + return; + } + } + } + runahead_suspend_audio(); + okay = runahead_run_secondary(); + runahead_resume_audio(); + if (!okay) + { + /*runloop_msg_queue_push("Could not create a secondary core. RunAhead will only use the main core now.", 1, 180, true);*/ + return; + } +#endif + } + runahead_force_input_dirty = false; +} + +static void runahead_error(void) +{ + runahead_available = false; + runahead_save_state_list_destroy(); + remove_hooks(); + runahead_save_state_size = 0; +} + +static bool runahead_create(void) +{ + /* get savestate size and allocate buffer */ + retro_ctx_size_info_t info; + core_serialize_size(&info); + + runahead_save_state_list_init(info.size); + runahead_video_driver_is_active = video_driver_is_active(); + + if (runahead_save_state_size == 0 || runahead_save_state_size == -1) + { + runahead_error(); + return false; + } + add_hooks(); + runahead_force_input_dirty = true; + mylist_resize(runahead_save_state_list, 1, true); + return true; +} + +static bool runahead_save_state(void) +{ + bool okay; + retro_ctx_serialize_info_t *serialize_info; + serialize_info = (retro_ctx_serialize_info_t*)runahead_save_state_list->data[0]; + okay = core_serialize(serialize_info); + if (!okay) + { + runahead_error(); + } + return okay; +} + +static bool runahead_load_state(void) +{ + bool okay; + bool lastDirty; + retro_ctx_serialize_info_t *serialize_info; + serialize_info = (retro_ctx_serialize_info_t*)runahead_save_state_list->data[0]; + lastDirty = input_is_dirty; + okay = core_unserialize(serialize_info); + input_is_dirty = lastDirty; + if (!okay) + { + runahead_error(); + } + return okay; +} + +static bool runahead_load_state_secondary(void) +{ + bool okay; + retro_ctx_serialize_info_t *serialize_info; + serialize_info = (retro_ctx_serialize_info_t*)runahead_save_state_list->data[0]; + okay = secondary_core_deserialize(serialize_info->data_const, serialize_info->size); + if (!okay) + { + runahead_secondary_core_available = false; + } + return okay; +} + +static bool runahead_run_secondary(void) +{ + bool okay; + okay = secondary_core_run_no_input_polling(); + if (!okay) + { + runahead_secondary_core_available = false; + } + return okay; +} + +static void runahead_suspend_audio(void) +{ + audio_driver_suspend(); +} + +static void runahead_resume_audio(void) +{ + audio_driver_resume(); +} + +static void runahead_suspend_video(void) +{ + video_driver_unset_active(); +} + +static void runahead_resume_video(void) +{ + if (runahead_video_driver_is_active) + { + video_driver_set_active(); + } + else + { + video_driver_unset_active(); + } +} + +void runahead_destroy(void) +{ + runahead_save_state_list_destroy(); + remove_hooks(); + runahead_clear_variables(); +} diff --git a/runahead/secondary_core.c b/runahead/secondary_core.c new file mode 100644 index 000000000000..58cc6ff47af6 --- /dev/null +++ b/runahead/secondary_core.c @@ -0,0 +1,487 @@ +#if defined(HAVE_DYNAMIC) && HAVE_DYNAMIC + +#include +#include +#include + +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX) +#ifndef LEGACY_WIN32 +#define LEGACY_WIN32 +#endif +#endif + +#include "mem_util.h" +#include "boolean.h" +#include "encodings/utf.h" +#include "compat/fopen_utf8.h" +#include "compat/unlink_utf8.h" +#include "dynamic/dylib.h" +#include "dynamic.h" +#include "core.h" +#include "file/file_path.h" +#include "paths.h" +#include "content.h" + +static int port_map[16]; + +typedef struct retro_core_t _retro_core_t; +typedef struct retro_callbacks retro_callbacks_t; + +static char *secondary_library_path; +static dylib_t secondary_module; +static _retro_core_t secondary_core; +static struct retro_callbacks secondary_callbacks; + +extern retro_ctx_load_content_info_t *load_content_info; +extern enum rarch_core_type last_core_type; +extern struct retro_callbacks retro_ctx; + +static char* get_temp_directory_alloc(void); +static char* copy_core_to_temp_file(void); +static void* read_file_data_alloc(const char *fileName, int *size); +static bool write_file_data(const char *fileName, const void *data, int dataSize); +static bool write_file_with_random_name(char **tempDllPath, const char *retroarchTempPath, const void* data, int dataSize); +static void* InputListElementConstructor(void); +static void secondary_core_clear(void); +static bool secondary_core_create(void); +bool secondary_core_run_no_input_polling(void); +bool secondary_core_deserialize(const void *buffer, int size); +void secondary_core_destroy(void); +void set_last_core_type(enum rarch_core_type type); +void remember_controller_port_device(long port, long device); +void clear_controller_port_map(void); + +static void free_file(FILE **file_p); + +char* get_temp_directory_alloc(void) +{ +#ifdef _WIN32 +#ifdef LEGACY_WIN32 + DWORD pathLength; + char *path; + + pathLength = GetTempPath(0, NULL) + 1; + path = (char*)malloc(pathLength * sizeof(char)); + path[pathLength - 1] = 0; + GetTempPath(pathLength, path); + return path; +#else + DWORD pathLength; + wchar_t *wideStr; + char *path; + + pathLength = GetTempPathW(0, NULL) + 1; + wideStr = (wchar_t*)malloc(pathLength * sizeof(wchar_t)); + wideStr[pathLength - 1] = 0; + GetTempPathW(pathLength, wideStr); + + path = utf16_to_utf8_string_alloc(wideStr); + free(wideStr); + return path; +#endif +#else + char *path; + path = strcpy_alloc_force(getenv("TMPDIR"); + return path; +#endif +} + +char* copy_core_to_temp_file(void) +{ + bool okay; + const char *corePath = NULL; /* ptr to static buffer, do not need to free this */ + const char *coreBaseName = NULL; /* ptr to static buffer, do not need to free this */ + char *tempDirectory = NULL; + char *retroarchTempPath = NULL; + char *tempDllPath = NULL; + void *dllFileData = NULL; + int dllFileSize = 0; + + corePath = path_get(RARCH_PATH_CORE); + coreBaseName = path_basename(corePath); + + if (strlen(coreBaseName) == 0) + { + goto failed; + } + + tempDirectory = get_temp_directory_alloc(); + if (tempDirectory == NULL) + { + goto failed; + } + + strcat_alloc(&retroarchTempPath, tempDirectory); + strcat_alloc(&retroarchTempPath, path_default_slash()); + strcat_alloc(&retroarchTempPath, "retroarch_temp"); + strcat_alloc(&retroarchTempPath, path_default_slash()); + + okay = path_mkdir(retroarchTempPath); + if (!okay) + { + goto failed; + } + + dllFileData = read_file_data_alloc(corePath, &dllFileSize); + if (dllFileData == NULL) + { + goto failed; + } + strcat_alloc(&tempDllPath, retroarchTempPath); + strcat_alloc(&tempDllPath, coreBaseName); + okay = write_file_data(tempDllPath, dllFileData, dllFileSize); + if (!okay) + { + /* try other file names */ + okay = write_file_with_random_name(&tempDllPath, retroarchTempPath, dllFileData, dllFileSize); + if (!okay) + { + goto failed; + } + } +success: + free_str(&tempDirectory); + free_str(&retroarchTempPath); + free_ptr(&dllFileData); + return tempDllPath; + +failed: + free_str(&tempDirectory); + free_str(&retroarchTempPath); + free_str(&tempDllPath); + free_ptr(&dllFileData); + return NULL; +} + +void* read_file_data_alloc(const char *fileName, int *size) +{ + void *data = NULL; + FILE *f = NULL; + int fileSize = 0; + size_t bytesRead = 0; +#ifdef _WIN32 + int64_t fileSizeLong = 0; +#else + off64_t fileSizeLong = 0; +#endif + f = (FILE*)fopen_utf8(fileName, "rb"); + if (f == NULL) + { + goto failed; + } + fseek(f, 0, SEEK_END); +#ifdef _WIN32 + fileSizeLong = _ftelli64(f); +#else + fileSizeLong = ftello64(f); +#endif + fseek(f, 0, SEEK_SET); + /* 256MB file size limit for DLL files */ + if (fileSizeLong < 0 || fileSizeLong > 256 * 1024 * 1024) + { + goto failed; + } + fileSize = (int)fileSizeLong; + data = malloc(fileSize); + if (data == NULL) + { + goto failed; + } + bytesRead = fread(data, 1, fileSize, f); + if ((int)bytesRead != (int)fileSize) + { + goto failed; + } +success: + free_file(&f); + if (size != NULL) *size = fileSize; + return data; +failed: + free_ptr(&data); + free_file(&f); + if (size != NULL) *size = 0; + return NULL; +} + +bool write_file_data(const char *fileName, const void *data, int dataSize) +{ + bool okay = false; + FILE *f = NULL; + size_t bytesWritten = 0; + + f = (FILE*)fopen_utf8(fileName, "wb"); + if (f == NULL) goto failed; + bytesWritten = fwrite(data, 1, dataSize, f); + if (bytesWritten != dataSize) + { + goto failed; + } +success: + free_file(&f); + return true; +failed: + free_file(&f); + return false; +} + +bool write_file_with_random_name(char **tempDllPath, const char *retroarchTempPath, const void* data, int dataSize) +{ + int extLen; + char *ext = NULL; + bool okay = false; + const int maxAttempts = 30; + const char *prefix = "tmp"; + char numberBuf[32]; + time_t timeValue = time(NULL); + unsigned int numberValue = (unsigned int)timeValue; + int number = 0; + int i; + + ext = strcpy_alloc_force(path_get_extension(*tempDllPath)); + extLen = strlen(ext); + if (extLen > 0) + { + strcat_alloc(&ext, "."); + memmove(ext + 1, ext, extLen); + ext[0] = '.'; + extLen++; + } + + + /* try up to 30 'random' filenames before giving up */ + for (i = 0; i < 30; i++) + { + numberValue = numberValue * 214013 + 2531011; + number = (numberValue >> 14) % 100000; + sprintf(numberBuf, "%05d", number); + free_str(tempDllPath); + strcat_alloc(tempDllPath, retroarchTempPath); + strcat_alloc(tempDllPath, prefix); + strcat_alloc(tempDllPath, numberBuf); + strcat_alloc(tempDllPath, ext); + okay = write_file_data(*tempDllPath, data, dataSize); + if (okay) + { + break; + } + } +success: + free_str(&ext); + return true; +failed: + free_str(&ext); + return false; +} + +void secondary_core_clear(void) +{ + secondary_library_path = NULL; + secondary_module = NULL; + memset(&secondary_core, 0, sizeof(struct retro_core_t)); +} + +bool secondary_core_create(void) +{ + long port, device; + bool contentless, is_inited; + + if (last_core_type != CORE_TYPE_PLAIN || load_content_info == NULL || load_content_info->special != NULL) + { + return false; + } + + free_str(&secondary_library_path); + secondary_library_path = copy_core_to_temp_file(); + if (secondary_library_path == NULL) + { + return false; + } + /* Load Core */ + if (init_libretro_sym_custom(CORE_TYPE_PLAIN, &secondary_core, secondary_library_path, &secondary_module)) + { + secondary_core.symbols_inited = true; + + core_set_default_callbacks(&secondary_callbacks); + secondary_core.retro_set_video_refresh(secondary_callbacks.frame_cb); + secondary_core.retro_set_audio_sample(secondary_callbacks.sample_cb); + secondary_core.retro_set_audio_sample_batch(secondary_callbacks.sample_batch_cb); + secondary_core.retro_set_input_state(secondary_callbacks.state_cb); + secondary_core.retro_set_input_poll(secondary_callbacks.poll_cb); + secondary_core.retro_set_environment(rarch_environment_cb); + + secondary_core.retro_init(); + + content_get_status(&contentless, &is_inited); + secondary_core.inited = is_inited; + + /* Load Content */ + if (load_content_info == NULL || load_content_info->special != NULL) + { + /* disabled due to crashes */ + return false; +#if 0 + secondary_core.game_loaded = secondary_core.retro_load_game_special(loadContentInfo.special->id, loadContentInfo.info, loadContentInfo.content->size); + if (!secondary_core.game_loaded) + { + secondary_core_destroy(); + return false; + } +#endif + } + else if (load_content_info->content->size > 0 && load_content_info->content->elems[0].data != NULL) + { + secondary_core.game_loaded = secondary_core.retro_load_game(load_content_info->info); + if (!secondary_core.game_loaded) + { + secondary_core_destroy(); + return false; + } + } + else if (contentless) + { + secondary_core.game_loaded = secondary_core.retro_load_game(NULL); + if (!secondary_core.game_loaded) + { + secondary_core_destroy(); + return false; + } + } + else + { + secondary_core.game_loaded = false; + } + if (!secondary_core.inited) + { + secondary_core_destroy(); + return false; + } + + for (port = 0; port < 16; port++) + { + device = port_map[port]; + if (device >= 0) + { + secondary_core.retro_set_controller_port_device(port, device); + } + } + clear_controller_port_map(); + } + else + { + return false; + } + return true; +} + +bool secondary_core_run_no_input_polling(void) +{ + bool okay; + if (secondary_module == NULL) + { + okay = secondary_core_create(); + if (!okay) + { + return false; + } + } + secondary_core.retro_run(); + return true; +} + +bool secondary_core_deserialize(const void *buffer, int size) +{ + bool okay; + if (secondary_module == NULL) + { + okay = secondary_core_create(); + if (!okay) + { + secondary_core_destroy(); + return false; + } + } + return secondary_core.retro_unserialize(buffer, size); +} + +void secondary_core_destroy(void) +{ + if (secondary_module != NULL) + { + /* unload game from core */ + if (secondary_core.retro_unload_game != NULL) + { + secondary_core.retro_unload_game(); + } + /* deinit */ + if (secondary_core.retro_deinit != NULL) + { + secondary_core.retro_deinit(); + } + memset(&secondary_core, 0, sizeof(struct retro_core_t)); + + dylib_close(secondary_module); + secondary_module = NULL; + unlink_utf8(secondary_library_path); + free_str(&secondary_library_path); + } +} + +void remember_controller_port_device(long port, long device) +{ + if (port >= 0 && port < 16) + { + port_map[port] = device; + } + if (secondary_module != NULL && secondary_core.retro_set_controller_port_device != NULL) + { + secondary_core.retro_set_controller_port_device(port, device); + } +} + +void clear_controller_port_map(void) +{ + int port; + for (port = 0; port < 16; port++) + { + port_map[port] = -1; + } +} + +static void free_file(FILE **file_p) +{ + bool result; + if (file_p == NULL) + { + return; + } + if (*file_p == NULL) + { + return; + } + result = fclose(*file_p) != 0; + *file_p = NULL; + return; +} + + +#else +#include "boolean.h" +#include "core.h" +bool secondary_core_run_no_input_polling(void) +{ + return false; +} +bool secondary_core_deserialize(const void *buffer, int size) +{ + return false; +} +void secondary_core_destroy(void) +{ + /* do nothing */ +} +void remember_controller_port_device(long port, long device) +{ + /* do nothing */ +} +#endif + diff --git a/runahead/secondary_core.h b/runahead/secondary_core.h new file mode 100644 index 000000000000..5393c1f6e048 --- /dev/null +++ b/runahead/secondary_core.h @@ -0,0 +1,19 @@ +#ifndef __SECONDARY_CORE_H__ +#define __SECONDARY_CORE_H__ + +#include "core_type.h" +#include "retro_common_api.h" +#include "boolean.h" + +RETRO_BEGIN_DECLS + +bool secondary_core_run_no_input_polling(); +bool secondary_core_deserialize(const void *buffer, int size); +void secondary_core_destroy(); +void set_last_core_type(enum rarch_core_type type); +void remember_controller_port_device(long port, long device); +void clear_controller_port_map(); + +RETRO_END_DECLS + +#endif