RetroArch/movie.c

361 lines
10 KiB
C
Raw Normal View History

2012-04-21 21:13:50 +00:00
/* RetroArch - A frontend for libretro.
2014-01-01 00:50:59 +00:00
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
2015-01-07 16:46:50 +00:00
* Copyright (C) 2011-2015 - Daniel De Matteis
2011-02-02 10:47:05 +00:00
*
2012-04-21 21:13:50 +00:00
* RetroArch is free software: you can redistribute it and/or modify it under the terms
2011-02-02 10:47:05 +00:00
* 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.
*
2012-04-21 21:13:50 +00:00
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
2011-02-02 10:47:05 +00:00
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
2012-04-21 21:31:57 +00:00
* You should have received a copy of the GNU General Public License along with RetroArch.
2011-02-02 10:47:05 +00:00
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
2012-01-11 18:22:18 +00:00
#include <string.h>
2015-09-05 17:51:55 +00:00
#include <rhash.h>
#include <retro_endianness.h>
2015-09-05 17:51:55 +00:00
2015-11-30 23:04:04 +00:00
#include "movie.h"
2011-02-02 10:47:05 +00:00
#include "general.h"
2015-11-30 23:04:04 +00:00
#include "msg_hash.h"
#include "runloop.h"
2015-11-23 11:03:38 +00:00
#include "verbosity.h"
2011-02-02 10:47:05 +00:00
struct bsv_movie
{
FILE *file;
2011-02-25 10:47:27 +00:00
2014-09-02 14:13:42 +00:00
/* A ring buffer keeping track of positions
* in the file for each frame. */
size_t *frame_pos;
2011-02-25 10:47:27 +00:00
size_t frame_mask;
size_t frame_ptr;
2011-11-10 23:55:35 +00:00
size_t min_file_pos;
2011-02-26 00:13:52 +00:00
size_t state_size;
uint8_t *state;
bool playback;
2011-02-25 23:59:17 +00:00
bool first_rewind;
2011-02-26 00:13:52 +00:00
bool did_rewind;
2011-02-02 10:47:05 +00:00
};
static bool init_playback(bsv_movie_t *handle, const char *path)
{
uint32_t state_size;
uint32_t header[4] = {0};
2015-04-01 16:32:40 +00:00
global_t *global = global_get_ptr();
handle->playback = true;
handle->file = fopen(path, "rb");
2011-02-02 10:47:05 +00:00
if (!handle->file)
{
2012-04-21 21:25:32 +00:00
RARCH_ERR("Couldn't open BSV file \"%s\" for playback.\n", path);
2011-02-02 10:47:05 +00:00
return false;
}
2011-02-02 11:45:56 +00:00
if (fread(header, sizeof(uint32_t), 4, handle->file) != 4)
2011-02-02 10:47:05 +00:00
{
2012-04-21 21:25:32 +00:00
RARCH_ERR("Couldn't read movie header.\n");
2011-02-02 10:47:05 +00:00
return false;
}
2014-09-02 14:13:42 +00:00
/* 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)
2011-02-02 10:47:05 +00:00
{
2012-04-21 21:25:32 +00:00
RARCH_ERR("Movie file is not a valid BSV1 file.\n");
2011-02-02 10:47:05 +00:00
return false;
}
2015-03-21 03:43:18 +00:00
if (swap_if_big32(header[CRC_INDEX]) != global->content_crc)
2014-08-12 01:19:02 +00:00
RARCH_WARN("CRC32 checksum mismatch between content file and saved content checksum in replay file header; replay highly likely to desync on playback.\n");
2011-02-02 10:47:05 +00:00
state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
2011-02-02 10:47:05 +00:00
2011-11-10 23:55:35 +00:00
if (state_size)
2011-02-02 10:47:05 +00:00
{
2015-04-01 16:32:40 +00:00
handle->state = (uint8_t*)malloc(state_size);
handle->state_size = state_size;
2011-11-10 23:55:35 +00:00
if (!handle->state)
return false;
2011-02-02 10:47:05 +00:00
2011-11-10 23:55:35 +00:00
if (fread(handle->state, 1, state_size, handle->file) != state_size)
{
2012-04-21 21:25:32 +00:00
RARCH_ERR("Couldn't read state from movie.\n");
2011-11-10 23:55:35 +00:00
return false;
}
if (core.retro_serialize_size() == state_size)
core.retro_unserialize(handle->state, state_size);
else
2012-04-21 21:25:32 +00:00
RARCH_WARN("Movie format seems to have a different serializer version. Will most likely fail.\n");
2011-02-02 19:15:00 +00:00
}
2011-11-10 23:55:35 +00:00
handle->min_file_pos = sizeof(header) + state_size;
2011-02-02 10:47:05 +00:00
return true;
}
static bool init_record(bsv_movie_t *handle, const char *path)
{
uint32_t state_size;
uint32_t header[4] = {0};
2015-04-01 16:32:40 +00:00
global_t *global = global_get_ptr();
2015-04-01 16:32:40 +00:00
handle->file = fopen(path, "wb");
2011-02-02 10:47:05 +00:00
if (!handle->file)
{
2012-04-21 21:25:32 +00:00
RARCH_ERR("Couldn't open BSV \"%s\" for recording.\n", path);
2011-02-02 10:47:05 +00:00
return false;
}
/* This value is supposed to show up as
* BSV1 in a HEX editor, big-endian. */
2015-04-01 16:32:40 +00:00
header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC);
header[CRC_INDEX] = swap_if_big32(global->content_crc);
state_size = core.retro_serialize_size();
2011-02-02 10:47:05 +00:00
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
2015-04-01 16:32:40 +00:00
2011-02-02 10:47:05 +00:00
fwrite(header, 4, sizeof(uint32_t), handle->file);
2015-04-01 16:32:40 +00:00
handle->min_file_pos = sizeof(header) + state_size;
handle->state_size = state_size;
2011-02-25 10:47:27 +00:00
if (state_size)
2011-11-18 17:03:24 +00:00
{
handle->state = (uint8_t*)malloc(state_size);
if (!handle->state)
return false;
core.retro_serialize(handle->state, state_size);
2011-11-18 17:03:24 +00:00
fwrite(handle->state, 1, state_size, handle->file);
}
2011-02-02 10:47:05 +00:00
return true;
}
void bsv_movie_free(bsv_movie_t *handle)
{
if (!handle)
return;
if (handle->file)
fclose(handle->file);
free(handle->state);
free(handle->frame_pos);
free(handle);
2011-02-02 10:47:05 +00:00
}
bool bsv_movie_get_input(int16_t *input)
2011-02-02 10:47:05 +00:00
{
global_t *global = global_get_ptr();
bsv_movie_t *handle = global->bsv.movie;
2011-02-25 23:59:17 +00:00
if (fread(input, sizeof(int16_t), 1, handle->file) != 1)
return false;
2011-02-02 10:47:05 +00:00
2011-02-25 23:59:17 +00:00
*input = swap_if_big16(*input);
return true;
2011-02-02 10:47:05 +00:00
}
void bsv_movie_set_input(int16_t input)
2011-02-02 10:47:05 +00:00
{
global_t *global = global_get_ptr();
bsv_movie_t *handle = global->bsv.movie;
input = swap_if_big16(input);
2011-02-25 23:59:17 +00:00
fwrite(&input, sizeof(int16_t), 1, handle->file);
2011-02-02 10:47:05 +00:00
}
2012-04-21 21:25:32 +00:00
bsv_movie_t *bsv_movie_init(const char *path, enum rarch_movie_type type)
2011-02-02 10:47:05 +00:00
{
2011-12-24 12:46:12 +00:00
bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle));
2011-02-02 10:47:05 +00:00
if (!handle)
return NULL;
2012-04-21 21:25:32 +00:00
if (type == RARCH_MOVIE_PLAYBACK)
2011-02-02 10:47:05 +00:00
{
if (!init_playback(handle, path))
goto error;
}
else if (!init_record(handle, path))
goto error;
2014-09-02 14:13:42 +00:00
/* Just pick something really large
* ~1 million frames rewind should do the trick. */
2011-12-24 12:46:12 +00:00
if (!(handle->frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
2011-02-25 10:47:27 +00:00
goto error;
2011-11-10 23:55:35 +00:00
2015-04-01 16:32:40 +00:00
handle->frame_pos[0] = handle->min_file_pos;
handle->frame_mask = (1 << 20) - 1;
2011-02-25 10:47:27 +00:00
2011-02-02 10:47:05 +00:00
return handle;
error:
bsv_movie_free(handle);
return NULL;
}
2011-02-25 10:47:27 +00:00
void bsv_movie_set_frame_start(bsv_movie_t *handle)
{
if (!handle)
return;
handle->frame_pos[handle->frame_ptr] = ftell(handle->file);
}
2011-02-25 10:47:27 +00:00
void bsv_movie_set_frame_end(bsv_movie_t *handle)
{
if (!handle)
return;
2015-04-01 16:32:40 +00:00
handle->frame_ptr = (handle->frame_ptr + 1) & handle->frame_mask;
2011-02-26 00:13:52 +00:00
handle->first_rewind = !handle->did_rewind;
2015-04-01 16:32:40 +00:00
handle->did_rewind = false;
2011-02-25 10:47:27 +00:00
}
void bsv_movie_frame_rewind(bsv_movie_t *handle)
{
2011-02-26 00:13:52 +00:00
handle->did_rewind = true;
2011-11-10 23:55:35 +00:00
if ((handle->frame_ptr <= 1) && (handle->frame_pos[0] == handle->min_file_pos))
2011-02-26 00:13:52 +00:00
{
2014-09-02 14:13:42 +00:00
/* If we're at the beginning... */
2011-02-25 23:59:17 +00:00
handle->frame_ptr = 0;
2011-02-26 00:13:52 +00:00
fseek(handle->file, handle->min_file_pos, SEEK_SET);
}
2011-02-25 23:59:17 +00:00
else
{
2014-09-02 14:13:42 +00:00
/* First time rewind is performed, the old frame is simply replayed.
* However, playing back that frame caused us to read data, and push
* data to the ring buffer.
*
* Sucessively rewinding frames, we need to rewind past the read data,
* plus another. */
handle->frame_ptr = (handle->frame_ptr -
(handle->first_rewind ? 1 : 2)) & handle->frame_mask;
2011-02-25 23:59:17 +00:00
fseek(handle->file, handle->frame_pos[handle->frame_ptr], SEEK_SET);
}
2011-02-25 10:47:27 +00:00
if (ftell(handle->file) <= (long)handle->min_file_pos)
{
2014-09-02 14:13:42 +00:00
/* We rewound past the beginning. */
2011-02-25 10:47:27 +00:00
if (!handle->playback)
{
2014-09-02 14:13:42 +00:00
/* If recording, we simply reset
* the starting point. Nice and easy. */
2011-02-25 10:47:27 +00:00
fseek(handle->file, 4 * sizeof(uint32_t), SEEK_SET);
core.retro_serialize(handle->state, handle->state_size);
fwrite(handle->state, 1, handle->state_size, handle->file);
2011-02-25 10:47:27 +00:00
}
else
fseek(handle->file, handle->min_file_pos, SEEK_SET);
2011-02-25 10:47:27 +00:00
}
}
2012-01-11 18:22:18 +00:00
2015-11-30 23:04:04 +00:00
static void bsv_movie_init_state(void)
{
settings_t *settings = config_get_ptr();
global_t *global = global_get_ptr();
if (bsv_movie_ctl(BSV_MOVIE_CTL_START_PLAYBACK, NULL))
{
if (!(global->bsv.movie = bsv_movie_init(global->bsv.movie_start_path,
RARCH_MOVIE_PLAYBACK)))
{
RARCH_ERR("%s: \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE),
global->bsv.movie_start_path);
retro_fail(1, "event_init_movie()");
}
global->bsv.movie_playback = true;
rarch_main_msg_queue_push_new(MSG_STARTING_MOVIE_PLAYBACK, 2, 180, false);
RARCH_LOG("%s.\n", msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK));
settings->rewind_granularity = 1;
}
else if (bsv_movie_ctl(BSV_MOVIE_CTL_START_RECORDING, NULL))
{
char msg[PATH_MAX_LENGTH] = {0};
snprintf(msg, sizeof(msg),
"%s \"%s\".",
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
global->bsv.movie_start_path);
if (!(global->bsv.movie = bsv_movie_init(global->bsv.movie_start_path,
RARCH_MOVIE_RECORD)))
{
rarch_main_msg_queue_push_new(MSG_FAILED_TO_START_MOVIE_RECORD, 1, 180, true);
RARCH_ERR("%s.\n", msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD));
return;
}
rarch_main_msg_queue_push(msg, 1, 180, true);
RARCH_LOG("%s \"%s\".\n",
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
global->bsv.movie_start_path);
settings->rewind_granularity = 1;
}
}
bool bsv_movie_ctl(enum bsv_ctl_state state, void *data)
{
global_t *global = global_get_ptr();
switch (state)
{
case BSV_MOVIE_CTL_IS_INITED:
return global->bsv.movie;
case BSV_MOVIE_CTL_PLAYBACK_ON:
return global->bsv.movie && global->bsv.movie_playback;
case BSV_MOVIE_CTL_PLAYBACK_OFF:
return global->bsv.movie && !global->bsv.movie_playback;
case BSV_MOVIE_CTL_START_RECORDING:
return global->bsv.movie_start_recording;
case BSV_MOVIE_CTL_START_PLAYBACK:
return global->bsv.movie_start_playback;
case BSV_MOVIE_CTL_END:
return global->bsv.movie_end && global->bsv.eof_exit;
2015-11-30 23:16:48 +00:00
case BSV_MOVIE_CTL_SET_END:
global->bsv.movie_end = true;
break;
case BSV_MOVIE_CTL_UNSET_END:
global->bsv.movie_end = false;
break;
case BSV_MOVIE_CTL_UNSET_PLAYBACK:
global->bsv.movie_playback = false;
break;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_DEINIT:
if (global->bsv.movie)
bsv_movie_free(global->bsv.movie);
global->bsv.movie = NULL;
break;
case BSV_MOVIE_CTL_INIT:
bsv_movie_init_state();
break;
2015-11-30 23:16:48 +00:00
case BSV_MOVIE_CTL_SET_FRAME_START:
bsv_movie_set_frame_start(global->bsv.movie);
break;
case BSV_MOVIE_CTL_SET_FRAME_END:
bsv_movie_set_frame_end(global->bsv.movie);
break;
case BSV_MOVIE_CTL_FRAME_REWIND:
bsv_movie_frame_rewind(global->bsv.movie);
break;
2015-11-30 23:04:04 +00:00
default:
return false;
}
return true;
}