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.