From f14951935122465ff85d27d5324b5add39cf77e2 Mon Sep 17 00:00:00 2001 From: Joe Osborn Date: Tue, 24 Jan 2023 22:15:32 -0800 Subject: [PATCH] Allow for both -e and -R to start a BSV file recording at a state (#14898) * Allow for both -e and -R to start a BSV file recording at a state The key issue is that loading a state takes some time, and the BSV recording shouldn't start until that's done. The minimal patch for this would just be a change to runloop.c which moves movie initialization after entry state loading, throwing in a task_queue_wait(). This makes for some awkward repeated autoload OSD messages and doesn't solve the underlying issue. Most of this change puts BSV recording start/stop into tasks, like saving and loading are tasks; this was important to centralize BSV operations a bit more and is the first part of a refactoring towards more robust input recording. The necessary wait is introduced in the begin-recording callback. Co-authored-by: Joseph C. Osborn --- Makefile.common | 1 + command.c | 4 +- command.h | 39 ---- content.h | 2 + griffin/griffin.c | 1 + input/input_driver.c | 337 +----------------------------- input/input_driver.h | 51 ++++- retroarch.c | 19 +- runloop.c | 51 +++-- tasks/task_movie.c | 484 +++++++++++++++++++++++++++++++++++++++++++ tasks/task_save.c | 22 ++ 11 files changed, 612 insertions(+), 399 deletions(-) create mode 100644 tasks/task_movie.c diff --git a/Makefile.common b/Makefile.common index 547c932856..3597675751 100644 --- a/Makefile.common +++ b/Makefile.common @@ -257,6 +257,7 @@ endif OBJ += \ tasks/task_save.o \ + tasks/task_movie.o \ tasks/task_file_transfer.o \ tasks/task_image.o \ tasks/task_playlist_manager.o \ diff --git a/command.c b/command.c index b8b1cc324a..724fd64010 100644 --- a/command.c +++ b/command.c @@ -1193,9 +1193,7 @@ void command_event_init_cheats( bool allow_cheats = true; #endif #ifdef HAVE_BSV_MOVIE - bsv_movie_t * - bsv_movie_state_handle = (bsv_movie_t*)bsv_movie_data; - allow_cheats &= !(bsv_movie_state_handle != NULL); + allow_cheats &= !(bsv_movie_data != NULL); #endif if (!allow_cheats) diff --git a/command.h b/command.h index 5111fe4af3..42d9f2f3e1 100644 --- a/command.h +++ b/command.h @@ -319,45 +319,6 @@ command_t* command_uds_new(void); bool command_network_send(const char *cmd_); -#ifdef HAVE_BSV_MOVIE -enum bsv_flags -{ - BSV_FLAG_MOVIE_START_RECORDING = (1 << 0), - BSV_FLAG_MOVIE_START_PLAYBACK = (1 << 1), - BSV_FLAG_MOVIE_PLAYBACK = (1 << 2), - BSV_FLAG_MOVIE_EOF_EXIT = (1 << 3), - BSV_FLAG_MOVIE_END = (1 << 4) -}; - -struct bsv_state -{ - uint8_t flags; - /* Movie playback/recording support. */ - char movie_path[PATH_MAX_LENGTH]; - /* Immediate playback/recording. */ - char movie_start_path[PATH_MAX_LENGTH]; -}; - -struct bsv_movie -{ - intfstream_t *file; - uint8_t *state; - /* A ring buffer keeping track of positions - * in the file for each frame. */ - size_t *frame_pos; - size_t frame_mask; - size_t frame_ptr; - size_t min_file_pos; - size_t state_size; - - bool playback; - bool first_rewind; - bool did_rewind; -}; - -typedef struct bsv_movie bsv_movie_t; -#endif - #ifdef HAVE_CONFIGFILE bool command_event_save_config( const char *config_path, diff --git a/content.h b/content.h index e61037fee2..f596ad8fe0 100644 --- a/content.h +++ b/content.h @@ -74,6 +74,8 @@ bool content_deserialize_state(const void* serialized_data, size_t serialized_si /* Waits for any in-progress save state tasks to finish */ void content_wait_for_save_state_task(void); +/* Waits for any in-progress load state tasks to finish */ +void content_wait_for_load_state_task(void); /* Copy a save state. */ bool content_rename_state(const char *origin, const char *dest); diff --git a/griffin/griffin.c b/griffin/griffin.c index 253b09cd35..4f3d91c3c4 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1367,6 +1367,7 @@ DATA RUNLOOP #include "../tasks/task_patch.c" #endif #include "../tasks/task_save.c" +#include "../tasks/task_movie.c" #include "../tasks/task_image.c" #include "../tasks/task_file_transfer.c" #include "../tasks/task_playlist_manager.c" diff --git a/input/input_driver.c b/input/input_driver.c index b38831d789..bfdbb24a87 100644 --- a/input/input_driver.c +++ b/input/input_driver.c @@ -5146,230 +5146,12 @@ void input_driver_poll(void) } #ifdef HAVE_BSV_MOVIE -#define MAGIC_INDEX 0 -#define SERIALIZER_INDEX 1 -#define CRC_INDEX 2 -#define STATE_SIZE_INDEX 3 - -#define BSV_MAGIC 0x42535631 - -static bool bsv_movie_init_playback( - bsv_movie_t *handle, const char *path) +void bsv_movie_free(bsv_movie_t*); +void bsv_movie_deinit(input_driver_state_t *input_st) { - uint32_t state_size = 0; - uint32_t header[4] = {0}; - intfstream_t *file = intfstream_open_file(path, - RETRO_VFS_FILE_ACCESS_READ, - RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!file) - { - RARCH_ERR("Could not open BSV file for playback, path : \"%s\".\n", path); - return false; - } - - handle->file = file; - handle->playback = true; - - intfstream_read(handle->file, header, sizeof(uint32_t) * 4); - /* Compatibility with old implementation that - * used incorrect documentation. */ - if (swap_if_little32(header[MAGIC_INDEX]) != BSV_MAGIC - && swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_BSV1_FILE)); - return false; - } - - state_size = swap_if_big32(header[STATE_SIZE_INDEX]); - -#if 0 - RARCH_ERR("----- debug %u -----\n", header[0]); - RARCH_ERR("----- debug %u -----\n", header[1]); - RARCH_ERR("----- debug %u -----\n", header[2]); - RARCH_ERR("----- debug %u -----\n", header[3]); -#endif - - if (state_size) - { - retro_ctx_size_info_t info; - retro_ctx_serialize_info_t serial_info; - uint8_t *buf = (uint8_t*)malloc(state_size); - - if (!buf) - return false; - - handle->state = buf; - handle->state_size = state_size; - if (intfstream_read(handle->file, - handle->state, state_size) != state_size) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE)); - return false; - } - - core_serialize_size( &info); - - if (info.size == state_size) - { - serial_info.data_const = handle->state; - serial_info.size = state_size; - core_unserialize(&serial_info); - } - else - RARCH_WARN("%s\n", - msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION)); - } - - handle->min_file_pos = sizeof(header) + state_size; - - return true; -} - -static bool bsv_movie_init_record( - bsv_movie_t *handle, const char *path) -{ - retro_ctx_size_info_t info; - uint32_t state_size = 0; - uint32_t content_crc = 0; - uint32_t header[4] = {0}; - intfstream_t *file = intfstream_open_file(path, - RETRO_VFS_FILE_ACCESS_WRITE, - RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!file) - { - RARCH_ERR("Could not open BSV file for recording, path : \"%s\".\n", path); - return false; - } - - handle->file = file; - - content_crc = content_get_crc(); - - /* This value is supposed to show up as - * BSV1 in a HEX editor, big-endian. */ - header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC); - header[CRC_INDEX] = swap_if_big32(content_crc); - - core_serialize_size(&info); - - state_size = (unsigned)info.size; - - header[STATE_SIZE_INDEX] = swap_if_big32(state_size); - - intfstream_write(handle->file, header, 4 * sizeof(uint32_t)); - - handle->min_file_pos = sizeof(header) + state_size; - handle->state_size = state_size; - - if (state_size) - { - retro_ctx_serialize_info_t serial_info; - uint8_t *st = (uint8_t*)malloc(state_size); - - if (!st) - return false; - - handle->state = st; - - serial_info.data = handle->state; - serial_info.size = state_size; - - core_serialize(&serial_info); - - intfstream_write(handle->file, - handle->state, state_size); - } - - return true; -} - -static void bsv_movie_free(bsv_movie_t *handle) -{ - intfstream_close(handle->file); - free(handle->file); - - free(handle->state); - free(handle->frame_pos); - free(handle); -} - -static bsv_movie_t *bsv_movie_init_internal(const char *path, - enum rarch_movie_type type) -{ - size_t *frame_pos = NULL; - bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle)); - - if (!handle) - return NULL; - - if (type == RARCH_MOVIE_PLAYBACK) - { - if (!bsv_movie_init_playback(handle, path)) - goto error; - } - else if (!bsv_movie_init_record(handle, path)) - goto error; - - /* Just pick something really large - * ~1 million frames rewind should do the trick. */ - if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t)))) - goto error; - - handle->frame_pos = frame_pos; - - handle->frame_pos[0] = handle->min_file_pos; - handle->frame_mask = (1 << 20) - 1; - - return handle; - -error: - if (handle) - bsv_movie_free(handle); - return NULL; -} - -static bool runloop_check_movie_init(input_driver_state_t *input_st, - settings_t *settings) -{ - size_t _len; - char msg[16384], path[8192]; - bsv_movie_t *state = NULL; - int state_slot = settings->ints.state_slot; - msg[0] = '\0'; - - configuration_set_uint(settings, settings->uints.rewind_granularity, 1); - - _len = strlcpy(path, - input_st->bsv_movie_state.movie_path, sizeof(path)); - if (state_slot > 0) - snprintf(path + _len, sizeof(path) - _len, "%d", state_slot); - strlcat(path, ".bsv", sizeof(path)); - - snprintf(msg, sizeof(msg), "%s \"%s\".", - msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO), - path); - - if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_RECORD))) - { - runloop_msg_queue_push( - msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD), - 2, 180, true, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_ERR("%s\n", - msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD)); - return false; - } - - input_st->bsv_movie_state_handle = state; - - runloop_msg_queue_push(msg, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_LOG("%s \"%s\".\n", - msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO), - path); - - return true; + if (input_st->bsv_movie_state_handle) + bsv_movie_free(input_st->bsv_movie_state_handle); + input_st->bsv_movie_state_handle = NULL; } void bsv_movie_frame_rewind(void) @@ -5428,115 +5210,6 @@ void bsv_movie_frame_rewind(void) } } -bool bsv_movie_init(input_driver_state_t *input_st) -{ - bsv_movie_t *state = NULL; - if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK) - { - const char *starting_movie_str = NULL; - if (!(state = bsv_movie_init_internal( - input_st->bsv_movie_state.movie_start_path, - RARCH_MOVIE_PLAYBACK))) - { - RARCH_ERR("%s: \"%s\".\n", - msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE), - input_st->bsv_movie_state.movie_start_path); - return false; - } - - input_st->bsv_movie_state_handle = state; - input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_PLAYBACK; - starting_movie_str = - msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK); - - runloop_msg_queue_push(starting_movie_str, - 2, 180, false, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_LOG("%s.\n", starting_movie_str); - - return true; - } - else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_RECORDING) - { - char msg[8192]; - const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO); - - if (!(state = bsv_movie_init_internal( - input_st->bsv_movie_state.movie_start_path, - RARCH_MOVIE_RECORD))) - { - const char *movie_rec_fail_str = - msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD); - runloop_msg_queue_push(movie_rec_fail_str, - 1, 180, true, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_ERR("%s.\n", movie_rec_fail_str); - return false; - } - - input_st->bsv_movie_state_handle = state; - snprintf(msg, sizeof(msg), - "%s \"%s\".", movie_rec_str, - input_st->bsv_movie_state.movie_start_path); - - runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_LOG("%s \"%s\".\n", movie_rec_str, - input_st->bsv_movie_state.movie_start_path); - - return true; - } - - return false; -} - -void bsv_movie_deinit(input_driver_state_t *input_st) -{ - if (input_st->bsv_movie_state_handle) - bsv_movie_free(input_st->bsv_movie_state_handle); - input_st->bsv_movie_state_handle = NULL; -} - -bool bsv_movie_check(input_driver_state_t *input_st, - settings_t *settings) -{ - const char *movie_rec_stopped_str = NULL; - if (!input_st->bsv_movie_state_handle) - return runloop_check_movie_init(input_st, settings); - - if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK) - { - const char *movie_playback_end_str = NULL; - /* Checks if movie is being played back. */ - if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)) - return false; - movie_playback_end_str = msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED); - runloop_msg_queue_push( - movie_playback_end_str, 2, 180, false, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_LOG("%s\n", movie_playback_end_str); - - bsv_movie_deinit(input_st); - - input_st->bsv_movie_state.flags &= ~( - BSV_FLAG_MOVIE_END - | BSV_FLAG_MOVIE_PLAYBACK); - return true; - } - - /* Checks if movie is being recorded. */ - if (!input_st->bsv_movie_state_handle) - return false; - - movie_rec_stopped_str = msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED); - runloop_msg_queue_push(movie_rec_stopped_str, - 2, 180, true, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - RARCH_LOG("%s\n", movie_rec_stopped_str); - - bsv_movie_deinit(input_st); - - return true; -} #endif int16_t input_state_internal(unsigned port, unsigned device, diff --git a/input/input_driver.h b/input/input_driver.h index 4c8777dfb5..2ecfa191df 100644 --- a/input/input_driver.h +++ b/input/input_driver.h @@ -108,6 +108,45 @@ enum rarch_movie_type RARCH_MOVIE_RECORD }; +#ifdef HAVE_BSV_MOVIE +enum bsv_flags +{ + BSV_FLAG_MOVIE_START_RECORDING = (1 << 0), + BSV_FLAG_MOVIE_START_PLAYBACK = (1 << 1), + BSV_FLAG_MOVIE_PLAYBACK = (1 << 2), + BSV_FLAG_MOVIE_EOF_EXIT = (1 << 3), + BSV_FLAG_MOVIE_END = (1 << 4) +}; + +struct bsv_state +{ + uint8_t flags; + /* Movie playback/recording support. */ + char movie_path[PATH_MAX_LENGTH]; + /* Immediate playback/recording. */ + char movie_start_path[PATH_MAX_LENGTH]; +}; + +struct bsv_movie +{ + intfstream_t *file; + uint8_t *state; + /* A ring buffer keeping track of positions + * in the file for each frame. */ + size_t *frame_pos; + size_t frame_mask; + size_t frame_ptr; + size_t min_file_pos; + size_t state_size; + + bool playback; + bool first_rewind; + bool did_rewind; +}; + +typedef struct bsv_movie bsv_movie_t; +#endif + /** * line_complete callback (when carriage return is pressed) * @@ -949,13 +988,15 @@ void input_overlay_init(void); #ifdef HAVE_BSV_MOVIE void bsv_movie_frame_rewind(void); - -bool bsv_movie_init(input_driver_state_t *input_st); - void bsv_movie_deinit(input_driver_state_t *input_st); -bool bsv_movie_check(input_driver_state_t *input_st, - settings_t *settings); +bool movie_start_playback(input_driver_state_t *input_st, char *path); +bool movie_start_record(input_driver_state_t *input_st, char *path); +bool movie_stop_playback(); +bool movie_stop_record(input_driver_state_t *input_st); +bool movie_toggle_record(input_driver_state_t *input_st, settings_t *settings); +bool movie_stop(input_driver_state_t *input_st); + #endif /** diff --git a/retroarch.c b/retroarch.c index 4d4d98858e..3040b749f2 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2176,7 +2176,7 @@ bool command_event(enum event_command cmd, void *data) { #ifdef HAVE_BSV_MOVIE input_driver_state_t *input_st = input_state_get_ptr(); - bsv_movie_check(input_st, settings); + movie_toggle_record(input_st, settings); #endif } break; @@ -2447,7 +2447,10 @@ bool command_event(enum event_command cmd, void *data) * we absolutely cannot change game state. */ input_driver_state_t *input_st = input_state_get_ptr(); if (input_st->bsv_movie_state_handle) + { + RARCH_LOG("[Load] [Movie] Can't load state during movie playback or record\n"); return false; + } #endif #ifdef HAVE_CHEEVOS @@ -5905,6 +5908,17 @@ static bool retroarch_parse_input_and_config( runloop_st->entry_state_slot = 0; RARCH_WARN("Trying to load entry state without content. Ignoring.\n"); } + #ifdef HAVE_BSV_MOVIE + if (runloop_st->entry_state_slot) + { + input_driver_state_t *input_st = input_state_get_ptr(); + if(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK) { + runloop_st->entry_state_slot = 0; + RARCH_WARN("Trying to load entry state while BSV playback is active. Ignoring entry state.\n"); + } + } + #endif + /* Check whether a core has been set via the * command line interface */ @@ -6418,9 +6432,8 @@ bool retroarch_ctl(enum rarch_ctl_state state, void *data) cheat_manager_state_free(); #endif #ifdef HAVE_BSV_MOVIE - bsv_movie_deinit(input_st); + movie_stop(input_st); #endif - command_event(CMD_EVENT_CORE_DEINIT, NULL); content_deinit(); diff --git a/runloop.c b/runloop.c index 5b8ecf685e..a3682da2f9 100644 --- a/runloop.c +++ b/runloop.c @@ -4143,16 +4143,6 @@ static bool event_init_content( RARCH_LOG("[SRAM]: %s\n", msg_hash_to_str(MSG_SKIPPING_SRAM_LOAD)); -#ifdef HAVE_BSV_MOVIE - bsv_movie_deinit(input_st); - if (bsv_movie_init(input_st)) - { - /* Set granularity upon success */ - configuration_set_uint(settings, - settings->uints.rewind_granularity, 1); - } -#endif - /* Since the operations are asynchronous we can't guarantee users will not use auto_load_state to cheat on @@ -4162,17 +4152,44 @@ static bool event_init_content( */ #ifdef HAVE_CHEEVOS if (!cheevos_enable || !cheevos_hardcore_mode_enable) -#endif -#ifdef HAVE_BSV_MOVIE - if (!input_st->bsv_movie_state_handle) #endif { - if (runloop_st->entry_state_slot && !command_event_load_entry_state(settings)) - runloop_st->entry_state_slot = 0; - if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load) - command_event_load_auto_state(); +#ifdef HAVE_BSV_MOVIE + /* ignore entry state if we're doing bsv playback (we do want it + for bsv recording though) */ + if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK)) +#endif + { + if (runloop_st->entry_state_slot && !command_event_load_entry_state(settings)) + { + /* loading the state failed, reset entry slot */ + runloop_st->entry_state_slot = 0; + } + } +#ifdef HAVE_BSV_MOVIE + /* ignore autoload state if we're doing bsv playback or recording */ + if (!(input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_START_RECORDING | BSV_FLAG_MOVIE_START_PLAYBACK))) +#endif + { + if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load) + command_event_load_auto_state(); + } } +#ifdef HAVE_BSV_MOVIE + movie_stop(input_st); + if(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_RECORDING) + { + configuration_set_uint(settings, settings->uints.rewind_granularity, 1); + movie_start_record(input_st, input_st->bsv_movie_state.movie_start_path); + } + else if(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK) + { + configuration_set_uint(settings, settings->uints.rewind_granularity, 1); + movie_start_playback(input_st, input_st->bsv_movie_state.movie_start_path); + } +#endif + command_event(CMD_EVENT_NETPLAY_INIT, NULL); return true; diff --git a/tasks/task_movie.c b/tasks/task_movie.c new file mode 100644 index 0000000000..77144bfc44 --- /dev/null +++ b/tasks/task_movie.c @@ -0,0 +1,484 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2016-2019 - Brad Parker + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifdef HAVE_BSV_MOVIE +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include "../msg_hash.h" +#include "../verbosity.h" +#include "../core.h" +#include "../content.h" +#include "../runloop.h" +#include "tasks_internal.h" +#include "../input/input_driver.h" + +#define MAGIC_INDEX 0 +#define SERIALIZER_INDEX 1 +#define CRC_INDEX 2 +#define STATE_SIZE_INDEX 3 + +#define BSV_MAGIC 0x42535631 + +/* Private functions */ + +static bool bsv_movie_init_playback( + bsv_movie_t *handle, const char *path) +{ + uint32_t state_size = 0; + uint32_t header[4] = {0}; + intfstream_t *file = intfstream_open_file(path, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + RARCH_ERR("Could not open BSV file for playback, path : \"%s\".\n", path); + return false; + } + + handle->file = file; + handle->playback = true; + + intfstream_read(handle->file, header, sizeof(uint32_t) * 4); + /* Compatibility with old implementation that + * used incorrect documentation. */ + if (swap_if_little32(header[MAGIC_INDEX]) != BSV_MAGIC + && swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC) + { + RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_BSV1_FILE)); + return false; + } + + state_size = swap_if_big32(header[STATE_SIZE_INDEX]); + +#if 0 + RARCH_ERR("----- debug %u -----\n", header[0]); + RARCH_ERR("----- debug %u -----\n", header[1]); + RARCH_ERR("----- debug %u -----\n", header[2]); + RARCH_ERR("----- debug %u -----\n", header[3]); +#endif + + if (state_size) + { + retro_ctx_size_info_t info; + retro_ctx_serialize_info_t serial_info; + uint8_t *buf = (uint8_t*)malloc(state_size); + + if (!buf) + return false; + + handle->state = buf; + handle->state_size = state_size; + if (intfstream_read(handle->file, + handle->state, state_size) != state_size) + { + RARCH_ERR("%s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE)); + return false; + } + core_serialize_size( &info); + + if (info.size == state_size) + { + serial_info.data_const = handle->state; + serial_info.size = state_size; + core_unserialize(&serial_info); + } + else + RARCH_WARN("%s\n", + msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION)); + } + + handle->min_file_pos = sizeof(header) + state_size; + + return true; +} + +static bool bsv_movie_init_record( + bsv_movie_t *handle, const char *path) +{ + retro_ctx_size_info_t info; + uint32_t state_size = 0; + uint32_t content_crc = 0; + uint32_t header[4] = {0}; + intfstream_t *file = intfstream_open_file(path, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + RARCH_ERR("Could not open BSV file for recording, path : \"%s\".\n", path); + return false; + } + + handle->file = file; + + content_crc = content_get_crc(); + + /* This value is supposed to show up as + * BSV1 in a HEX editor, big-endian. */ + header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC); + header[CRC_INDEX] = swap_if_big32(content_crc); + core_serialize_size(&info); + + state_size = (unsigned)info.size; + + header[STATE_SIZE_INDEX] = swap_if_big32(state_size); + + intfstream_write(handle->file, header, 4 * sizeof(uint32_t)); + + handle->min_file_pos = sizeof(header) + state_size; + handle->state_size = state_size; + + if (state_size) + { + retro_ctx_serialize_info_t serial_info; + uint8_t *st = (uint8_t*)malloc(state_size); + + if (!st) + return false; + + handle->state = st; + + serial_info.data = handle->state; + serial_info.size = state_size; + + core_serialize(&serial_info); + + intfstream_write(handle->file, + handle->state, state_size); + } + + return true; +} + +void bsv_movie_free(bsv_movie_t *handle) +{ + intfstream_close(handle->file); + free(handle->file); + + free(handle->state); + free(handle->frame_pos); + free(handle); +} + +static bsv_movie_t *bsv_movie_init_internal(const char *path, enum rarch_movie_type type) +{ + size_t *frame_pos = NULL; + bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle)); + + if (!handle) + return NULL; + + if (type == RARCH_MOVIE_PLAYBACK) + { + if (!bsv_movie_init_playback(handle, path)) + goto error; + } + else if (!bsv_movie_init_record(handle, path)) + goto error; + + /* Just pick something really large + * ~1 million frames rewind should do the trick. */ + if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t)))) + goto error; + + handle->frame_pos = frame_pos; + + handle->frame_pos[0] = handle->min_file_pos; + handle->frame_mask = (1 << 20) - 1; + + return handle; + +error: + if (handle) + bsv_movie_free(handle); + return NULL; +} +bool bsv_movie_start_record(input_driver_state_t * input_st, char *path) +{ + bsv_movie_t *state = NULL; + const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO); + char msg[8192]; + + /* this should trigger a start recording task which on failure or + success prints a message and on success sets the + input_st->bsv_movie_state_handle. */ + if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_RECORD))) + { + const char *movie_rec_fail_str = + msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD); + runloop_msg_queue_push(movie_rec_fail_str, + 1, 180, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_ERR("%s.\n", movie_rec_fail_str); + return false; + } + + input_st->bsv_movie_state_handle = state; + snprintf(msg, sizeof(msg), + "%s \"%s\".", movie_rec_str, + path); + + runloop_msg_queue_push(msg, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_LOG("%s \"%s\".\n", movie_rec_str, path); + + return true; +} + +bool bsv_movie_start_playback(input_driver_state_t *input_st, char *path) { + bsv_movie_t *state = NULL; + const char *starting_movie_str = NULL; + + /* this should trigger a start playback task which on failure or + success prints a message and on success sets the + input_st->bsv_movie_state_handle. */ + + if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_PLAYBACK))) + { + RARCH_ERR("%s: \"%s\".\n", + msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE), + path); + return false; + } + + input_st->bsv_movie_state_handle = state; + input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_PLAYBACK; + starting_movie_str = + msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK); + + runloop_msg_queue_push(starting_movie_str, + 2, 180, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_LOG("%s.\n", starting_movie_str); + + return true; +} + + +/* Task infrastructure (also private) */ + +/* Future: replace stop functions with tasks that do the same. then + later we can replace the start_record/start_playback flags and + remove the entirety of input_driver_st bsv_state, which is only + needed due to mixing sync and async during initialization. */ + +typedef struct bsv_state moviectl_task_state_t; + +static void task_moviectl_playback_handler(retro_task_t *task) +{ + /* trivial handler */ + task_set_finished(task, true); + if (!task_get_error(task) && task_get_cancelled(task)) + task_set_error(task, strdup("Task canceled")); + + task_set_data(task, task->state); + task->state = NULL; + /* no need to free state here since I'm recycling it as data */ +} +static void moviectl_start_playback_cb(retro_task_t *task, + void *task_data, + void *user_data, const char *error) +{ + struct bsv_state *state = (struct bsv_state *)task_data; + input_driver_state_t *input_st = input_state_get_ptr(); + input_st->bsv_movie_state = *state; + bsv_movie_start_playback(input_st, state->movie_path); + free(state); +} +bool content_load_state_in_progress(void* data); +static void task_moviectl_record_handler(retro_task_t *task) +{ + if(content_load_state_in_progress(NULL)) + { + /* hang on until the state is loaded */ + return; + } + /* trivial handler */ + task_set_finished(task, true); + if (!task_get_error(task) && task_get_cancelled(task)) + task_set_error(task, strdup("Task canceled")); + + task_set_data(task, task->state); + task->state = NULL; + /* no need to free state here since I'm recycling it as data */ +} +static void moviectl_start_record_cb(retro_task_t *task, + void *task_data, + void *user_data, const char *error) +{ + struct bsv_state *state = (struct bsv_state *)task_data; + input_driver_state_t *input_st = input_state_get_ptr(); + input_st->bsv_movie_state = *state; + bsv_movie_start_record(input_st, state->movie_path); + free(state); +} + +/* Public functions */ + +bool movie_toggle_record(input_driver_state_t *input_st, settings_t *settings) +{ + if (!input_st->bsv_movie_state_handle) + { + size_t _len; + char path[8192]; + int state_slot = settings->ints.state_slot; + + configuration_set_uint(settings, settings->uints.rewind_granularity, 1); + + _len = strlcpy(path, + input_st->bsv_movie_state.movie_path, sizeof(path)); + if (state_slot > 0) + snprintf(path + _len, sizeof(path) - _len, "%d", state_slot); + strlcat(path, ".bsv", sizeof(path)); + + return movie_start_record(input_st, path); + } + + return movie_stop(input_st); +} + +/* in the future this should probably be a deferred task as well */ +bool movie_stop_playback(input_driver_state_t *input_st){ + const char *movie_playback_end_str = NULL; + /* Checks if movie is being played back. */ + if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)) + { + return false; + } + if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)) + return false; + movie_playback_end_str = msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED); + runloop_msg_queue_push( + movie_playback_end_str, 2, 180, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_LOG("%s\n", movie_playback_end_str); + + bsv_movie_deinit(input_st); + + input_st->bsv_movie_state.flags &= ~( + BSV_FLAG_MOVIE_END + | BSV_FLAG_MOVIE_PLAYBACK); + return true; +} +/* in the future this should probably be a deferred task as well */ +bool movie_stop_record(input_driver_state_t *input_st) { + const char *movie_rec_stopped_str = NULL; + movie_rec_stopped_str = msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED); + if(!(input_st->bsv_movie_state_handle)) + { + return false; + } + runloop_msg_queue_push(movie_rec_stopped_str, + 2, 180, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_LOG("%s\n", movie_rec_stopped_str); + bsv_movie_deinit(input_st); + return true; + +} +bool movie_stop(input_driver_state_t *input_st) { + if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK) + { + return movie_stop_playback(input_st); + } + else if(input_st->bsv_movie_state_handle) + { + return movie_stop_record(input_st); + } + return true; +} + +bool movie_start_playback(input_driver_state_t *input_st, char *path) +{ + retro_task_t *task = task_init(); + moviectl_task_state_t *state = (moviectl_task_state_t *) calloc(1, sizeof(*state)); + if (!task || !state) + goto error; + *state = input_st->bsv_movie_state; + strlcpy(state->movie_path, path, sizeof(state->movie_path)); + task->type = TASK_TYPE_NONE; + task->state = state; + task->handler = task_moviectl_playback_handler; + task->callback = moviectl_start_playback_cb; + task->title = strdup(msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK)); + + if (!task_queue_push(task)) + goto error; + + return true; + +error: + if (state) + free(state); + if (task) + free(task); + + return false; +} +bool movie_start_record(input_driver_state_t *input_st, char*path) +{ + const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO); + char msg[8192]; + retro_task_t *task = task_init(); + moviectl_task_state_t *state = (moviectl_task_state_t *) calloc(1, sizeof(*state)); + if (!task || !state) + goto error; + + *state = input_st->bsv_movie_state; + strlcpy(state->movie_path, path, sizeof(state->movie_path)); + + msg[0] = '\0'; + snprintf(msg, sizeof(msg), + "%s \"%s\".", movie_rec_str, + state->movie_path); + + task->type = TASK_TYPE_NONE; + task->state = state; + task->handler = task_moviectl_record_handler; + task->callback = moviectl_start_record_cb; + + task->title = strdup(msg); + + if (!task_queue_push(task)) + goto error; + + + return true; + +error: + if (state) + free(state); + if (task) + free(task); + + return false; +} +#endif diff --git a/tasks/task_save.c b/tasks/task_save.c index f0ed9662fc..18e2711c2b 100644 --- a/tasks/task_save.c +++ b/tasks/task_save.c @@ -1611,6 +1611,28 @@ void content_wait_for_save_state_task(void) task_queue_wait(content_save_state_in_progress, NULL); } + +static bool task_load_state_finder(retro_task_t *task, void *user_data) +{ + return (task && task->handler == task_load_handler); +} + +/* Returns true if a load state task is in progress */ +bool content_load_state_in_progress(void* data) +{ + task_finder_data_t find_data; + + find_data.func = task_load_state_finder; + find_data.userdata = NULL; + + return task_queue_find(&find_data); +} + +void content_wait_for_load_state_task(void) +{ + task_queue_wait(content_load_state_in_progress, NULL); +} + /** * content_load_state: * @path : path that state will be loaded from.