mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-23 16:09:47 +00:00
2baf9ec7e0
* Enqueue replay record/playback until next frame This fixes a bug where stdin commands activated via polling would trigger replay record/playback in the middle of a frame, then input checking would pull from them before the next frame came around---it only makes sense to change the active replay in between frames, not during them. * fix comment syntax
484 lines
14 KiB
C
484 lines
14 KiB
C
/* 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 <time/rtime.h>
|
|
#include <compat/strl.h>
|
|
#include <file/file_path.h>
|
|
#include <streams/file_stream.h>
|
|
#include <retro_endianness.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 VERSION_INDEX 1
|
|
#define CRC_INDEX 2
|
|
#define STATE_SIZE_INDEX 3
|
|
/* Identifier is int64_t, so takes up two slots */
|
|
#define IDENTIFIER_INDEX 4
|
|
#define HEADER_LEN 6
|
|
|
|
#define REPLAY_FORMAT_VERSION 0
|
|
#define REPLAY_MAGIC 0x42535632
|
|
|
|
/* Forward declaration */
|
|
bool content_load_state_in_progress(void* data);
|
|
|
|
/* Private functions */
|
|
|
|
static bool bsv_movie_init_playback(
|
|
bsv_movie_t *handle, const char *path)
|
|
{
|
|
int64_t *identifier_loc;
|
|
uint32_t state_size = 0;
|
|
uint32_t header[HEADER_LEN] = {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 replay file for playback, path : \"%s\".\n", path);
|
|
return false;
|
|
}
|
|
|
|
handle->file = file;
|
|
handle->playback = true;
|
|
|
|
intfstream_read(handle->file, header, sizeof(uint32_t) * HEADER_LEN);
|
|
if (swap_if_big32(header[MAGIC_INDEX]) != REPLAY_MAGIC)
|
|
{
|
|
RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_REPLAY_FILE));
|
|
return false;
|
|
}
|
|
#if 0
|
|
if (swap_if_big32(header[VERSION_INDEX]) > REPLAY_FORMAT_VERSION)
|
|
{
|
|
RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_REPLAY_FILE));
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
|
|
identifier_loc = (int64_t *)(header+IDENTIFIER_INDEX);
|
|
handle->identifier = swap_if_big64(*identifier_loc);
|
|
|
|
#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]);
|
|
RARCH_ERR("----- debug %u -----\n", header[4]);
|
|
RARCH_ERR("----- debug %u -----\n", header[5]);
|
|
#endif
|
|
|
|
if (state_size)
|
|
{
|
|
size_t info_size;
|
|
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;
|
|
}
|
|
info_size = core_serialize_size();
|
|
/* For cores like dosbox, the reported size is not always
|
|
correct. So we just give a warning if they don't match up. */
|
|
serial_info.data_const = handle->state;
|
|
serial_info.size = state_size;
|
|
core_unserialize(&serial_info);
|
|
if (info_size != state_size)
|
|
{
|
|
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)
|
|
{
|
|
size_t info_size;
|
|
time_t t = time(NULL);
|
|
time_t time_lil = swap_if_big64(t);
|
|
uint32_t state_size = 0;
|
|
uint32_t content_crc = 0;
|
|
uint32_t header[HEADER_LEN] = {0};
|
|
intfstream_t *file = intfstream_open_file(path,
|
|
RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
|
|
if (!file)
|
|
{
|
|
RARCH_ERR("Could not open replay file for recording, path : \"%s\".\n", path);
|
|
return false;
|
|
}
|
|
|
|
handle->file = file;
|
|
|
|
content_crc = content_get_crc();
|
|
|
|
header[MAGIC_INDEX] = swap_if_big32(REPLAY_MAGIC);
|
|
header[VERSION_INDEX] = REPLAY_FORMAT_VERSION;
|
|
header[CRC_INDEX] = swap_if_big32(content_crc);
|
|
|
|
info_size = core_serialize_size();
|
|
|
|
state_size = (unsigned)info_size;
|
|
|
|
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
|
|
handle->identifier = (int64_t)t;
|
|
*((int64_t *)(header+IDENTIFIER_INDEX)) = time_lil;
|
|
intfstream_write(handle->file, header, HEADER_LEN * 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;
|
|
}
|
|
|
|
static bool bsv_movie_start_record(input_driver_state_t * input_st, char *path)
|
|
{
|
|
size_t _len;
|
|
char msg[8192];
|
|
bsv_movie_t *state = NULL;
|
|
const char *movie_rec_str = NULL;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
bsv_movie_enqueue(input_st, state, BSV_FLAG_MOVIE_RECORDING);
|
|
movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO);
|
|
_len = strlcpy(msg, movie_rec_str, sizeof(msg));
|
|
snprintf(msg + _len, sizeof(msg) - _len, " \"%s\".", 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;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
bsv_movie_enqueue(input_st, state, 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_start_path);
|
|
free(state);
|
|
}
|
|
|
|
static void task_moviectl_record_handler(retro_task_t *task)
|
|
{
|
|
/* Hang on until the state is loaded */
|
|
if (content_load_state_in_progress(NULL))
|
|
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_start_path);
|
|
free(state);
|
|
}
|
|
|
|
/* Public functions */
|
|
|
|
/* 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;
|
|
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_full(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 = 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_full(input_st);
|
|
input_st->bsv_movie_state.flags &= ~(
|
|
BSV_FLAG_MOVIE_END
|
|
| BSV_FLAG_MOVIE_RECORDING);
|
|
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.flags & BSV_FLAG_MOVIE_RECORDING)
|
|
return movie_stop_record(input_st);
|
|
if(input_st->bsv_movie_state_handle)
|
|
RARCH_ERR("Didn't really stop movie!\n");
|
|
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));
|
|
bool file_exists = filestream_exists(path);
|
|
|
|
if (!task || !state || !file_exists)
|
|
goto error;
|
|
|
|
*state = input_st->bsv_movie_state;
|
|
strlcpy(state->movie_start_path, path, sizeof(state->movie_start_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))
|
|
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)
|
|
{
|
|
size_t _len;
|
|
char msg[8192];
|
|
const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO);
|
|
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_start_path, path, sizeof(state->movie_start_path));
|
|
|
|
_len = strlcpy(msg, movie_rec_str, sizeof(msg));
|
|
snprintf(msg + _len, sizeof(msg) - _len, " \"%s\".", 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
|