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 <jcoa2018@pomona.edu>
This commit is contained in:
Joe Osborn 2023-01-24 22:15:32 -08:00 committed by GitHub
parent afe3bf72bb
commit f149519351
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 612 additions and 399 deletions

View File

@ -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 \

View File

@ -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)

View File

@ -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,

View File

@ -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);

View File

@ -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"

View File

@ -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,

View File

@ -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
/**

View File

@ -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();

View File

@ -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;

484
tasks/task_movie.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_BSV_MOVIE
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <time.h>
#include <compat/strl.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <unistd.h>
#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

View File

@ -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.