/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2015 - Daniel De Matteis * * 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 . */ #include "movie.h" #include "hash.h" #include #include #include #include "general.h" #include "dynamic.h" struct bsv_movie { FILE *file; /* 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; uint8_t *state; bool playback; bool first_rewind; bool did_rewind; }; static bool init_playback(bsv_movie_t *handle, const char *path) { uint32_t state_size; uint32_t header[4] = {0}; handle->playback = true; handle->file = fopen(path, "rb"); if (!handle->file) { RARCH_ERR("Couldn't open BSV file \"%s\" for playback.\n", path); return false; } if (fread(header, sizeof(uint32_t), 4, handle->file) != 4) { RARCH_ERR("Couldn't read movie header.\n"); return false; } /* 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("Movie file is not a valid BSV1 file.\n"); return false; } if (swap_if_big32(header[CRC_INDEX]) != g_extern.content_crc) RARCH_WARN("CRC32 checksum mismatch between content file and saved content checksum in replay file header; replay highly likely to desync on playback.\n"); state_size = swap_if_big32(header[STATE_SIZE_INDEX]); if (state_size) { handle->state = (uint8_t*)malloc(state_size); handle->state_size = state_size; if (!handle->state) return false; if (fread(handle->state, 1, state_size, handle->file) != state_size) { RARCH_ERR("Couldn't read state from movie.\n"); return false; } if (pretro_serialize_size() == state_size) pretro_unserialize(handle->state, state_size); else RARCH_WARN("Movie format seems to have a different serializer version. Will most likely fail.\n"); } handle->min_file_pos = sizeof(header) + state_size; return true; } static bool init_record(bsv_movie_t *handle, const char *path) { uint32_t state_size; uint32_t header[4] = {0}; handle->file = fopen(path, "wb"); if (!handle->file) { RARCH_ERR("Couldn't open BSV \"%s\" for recording.\n", path); return false; } /* 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(g_extern.content_crc); state_size = pretro_serialize_size(); header[STATE_SIZE_INDEX] = swap_if_big32(state_size); fwrite(header, 4, sizeof(uint32_t), handle->file); handle->min_file_pos = sizeof(header) + state_size; handle->state_size = state_size; if (state_size) { handle->state = (uint8_t*)malloc(state_size); if (!handle->state) return false; pretro_serialize(handle->state, state_size); fwrite(handle->state, 1, state_size, handle->file); } 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); } bool bsv_movie_get_input(bsv_movie_t *handle, int16_t *input) { if (fread(input, sizeof(int16_t), 1, handle->file) != 1) return false; *input = swap_if_big16(*input); return true; } void bsv_movie_set_input(bsv_movie_t *handle, int16_t input) { input = swap_if_big16(input); fwrite(&input, sizeof(int16_t), 1, handle->file); } bsv_movie_t *bsv_movie_init(const char *path, enum rarch_movie_type type) { bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle)); if (!handle) return NULL; if (type == RARCH_MOVIE_PLAYBACK) { if (!init_playback(handle, path)) goto error; } else if (!init_record(handle, path)) goto error; /* Just pick something really large * ~1 million frames rewind should do the trick. */ if (!(handle->frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t)))) goto error; handle->frame_pos[0] = handle->min_file_pos; handle->frame_mask = (1 << 20) - 1; return handle; error: bsv_movie_free(handle); return NULL; } void bsv_movie_set_frame_start(bsv_movie_t *handle) { if (!handle) return; handle->frame_pos[handle->frame_ptr] = ftell(handle->file); } void bsv_movie_set_frame_end(bsv_movie_t *handle) { if (!handle) return; handle->frame_ptr = (handle->frame_ptr + 1) & handle->frame_mask; handle->first_rewind = !handle->did_rewind; handle->did_rewind = false; } void bsv_movie_frame_rewind(bsv_movie_t *handle) { handle->did_rewind = true; if ((handle->frame_ptr <= 1) && (handle->frame_pos[0] == handle->min_file_pos)) { /* If we're at the beginning... */ handle->frame_ptr = 0; fseek(handle->file, handle->min_file_pos, SEEK_SET); } else { /* 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; fseek(handle->file, handle->frame_pos[handle->frame_ptr], SEEK_SET); } if (ftell(handle->file) <= (long)handle->min_file_pos) { /* We rewound past the beginning. */ if (!handle->playback) { /* If recording, we simply reset * the starting point. Nice and easy. */ fseek(handle->file, 4 * sizeof(uint32_t), SEEK_SET); pretro_serialize(handle->state, handle->state_size); fwrite(handle->state, 1, handle->state_size, handle->file); } else fseek(handle->file, handle->min_file_pos, SEEK_SET); } }