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>
2015-09-14 01:33:50 +00:00
# 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
2013-11-04 04:57:41 +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
} ;
2015-12-01 00:47:26 +00:00
struct bsv_state
{
/* Movie playback/recording support. */
bsv_movie_t * movie ;
char movie_path [ PATH_MAX_LENGTH ] ;
bool movie_playback ;
bool eof_exit ;
/* Immediate playback/recording. */
char movie_start_path [ PATH_MAX_LENGTH ] ;
bool movie_start_recording ;
bool movie_start_playback ;
bool movie_end ;
} ;
struct bsv_state bsv_movie_state ;
2011-02-02 10:47:05 +00:00
static bool init_playback ( bsv_movie_t * handle , const char * path )
{
2015-01-10 05:43:56 +00:00
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 " ) ;
2015-01-10 05:43:56 +00:00
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
2015-01-10 05:43:56 +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 ) ;
2012-03-10 17:10:54 +00:00
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 ;
}
2015-10-06 17:34:09 +00:00
if ( core . retro_serialize_size ( ) = = state_size )
core . retro_unserialize ( handle - > state , state_size ) ;
2012-03-10 17:10:54 +00:00
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 )
{
2015-01-10 05:43:56 +00:00
uint32_t state_size ;
uint32_t header [ 4 ] = { 0 } ;
2015-04-01 16:32:40 +00:00
global_t * global = global_get_ptr ( ) ;
2015-01-10 05:43:56 +00:00
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 ;
}
2014-09-03 04:14:13 +00:00
/* 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 ) ;
2015-10-06 17:34:09 +00:00
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
2012-03-10 17:10:54 +00:00
if ( state_size )
2011-11-18 17:03:24 +00:00
{
2012-03-10 17:10:54 +00:00
handle - > state = ( uint8_t * ) malloc ( state_size ) ;
if ( ! handle - > state )
return false ;
2015-10-06 17:34:09 +00:00
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 )
{
2015-01-10 05:43:56 +00:00
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
}
2015-12-01 00:20:22 +00:00
bool bsv_movie_get_input ( int16_t * input )
2011-02-02 10:47:05 +00:00
{
2015-12-01 00:47:26 +00:00
bsv_movie_t * handle = bsv_movie_state . 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
}
2015-12-01 00:20:22 +00:00
void bsv_movie_set_input ( int16_t input )
2011-02-02 10:47:05 +00:00
{
2015-12-01 00:47:26 +00:00
bsv_movie_t * handle = bsv_movie_state . movie ;
2015-12-01 00:20:22 +00:00
2012-01-10 23:55:50 +00:00
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
2011-02-25 23:31:13 +00:00
void bsv_movie_set_frame_start ( bsv_movie_t * handle )
{
2015-01-10 05:43:56 +00:00
if ( ! handle )
return ;
2011-02-25 23:31:13 +00:00
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 )
{
2015-01-10 05:43:56 +00:00
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 ) ;
2015-10-06 17:34:09 +00:00
core . retro_serialize ( handle - > state , handle - > state_size ) ;
2012-03-10 17:10:54 +00:00
fwrite ( handle - > state , 1 , handle - > state_size , handle - > file ) ;
2011-02-25 10:47:27 +00:00
}
2011-02-25 23:31:13 +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 ( ) ;
if ( bsv_movie_ctl ( BSV_MOVIE_CTL_START_PLAYBACK , NULL ) )
{
2015-12-01 00:47:26 +00:00
if ( ! ( bsv_movie_init_handle ( bsv_movie_state . movie_start_path ,
2015-11-30 23:04:04 +00:00
RARCH_MOVIE_PLAYBACK ) ) )
{
RARCH_ERR ( " %s: \" %s \" . \n " ,
msg_hash_to_str ( MSG_FAILED_TO_LOAD_MOVIE_FILE ) ,
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_path ) ;
2015-11-30 23:04:04 +00:00
retro_fail ( 1 , " event_init_movie() " ) ;
}
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_playback = true ;
2015-11-30 23:04:04 +00:00
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 ) ,
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_path ) ;
2015-11-30 23:04:04 +00:00
2015-12-01 00:47:26 +00:00
if ( ! ( bsv_movie_init_handle ( bsv_movie_state . movie_start_path ,
2015-11-30 23:04:04 +00:00
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 ) ,
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_path ) ;
2015-11-30 23:04:04 +00:00
settings - > rewind_granularity = 1 ;
}
}
bool bsv_movie_ctl ( enum bsv_ctl_state state , void * data )
{
switch ( state )
{
case BSV_MOVIE_CTL_IS_INITED :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie ;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_PLAYBACK_ON :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie & & bsv_movie_state . movie_playback ;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_PLAYBACK_OFF :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie & & ! bsv_movie_state . movie_playback ;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_START_RECORDING :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie_start_recording ;
2015-12-01 00:25:36 +00:00
case BSV_MOVIE_CTL_SET_START_RECORDING :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_recording = true ;
2015-12-01 00:25:36 +00:00
break ;
case BSV_MOVIE_CTL_UNSET_START_RECORDING :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_recording = false ;
2015-12-01 00:25:36 +00:00
break ;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_START_PLAYBACK :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie_start_playback ;
2015-12-01 00:25:36 +00:00
case BSV_MOVIE_CTL_SET_START_PLAYBACK :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_playback = true ;
2015-12-01 00:25:36 +00:00
break ;
case BSV_MOVIE_CTL_UNSET_START_PLAYBACK :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_start_playback = false ;
2015-12-01 00:25:36 +00:00
break ;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_END :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie_end ;
2015-12-01 00:29:16 +00:00
case BSV_MOVIE_CTL_SET_END_EOF :
2015-12-01 00:47:26 +00:00
bsv_movie_state . eof_exit = true ;
2015-12-01 00:29:16 +00:00
break ;
case BSV_MOVIE_CTL_END_EOF :
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie_end & & bsv_movie_state . eof_exit ;
2015-11-30 23:16:48 +00:00
case BSV_MOVIE_CTL_SET_END :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_end = true ;
2015-11-30 23:16:48 +00:00
break ;
case BSV_MOVIE_CTL_UNSET_END :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_end = false ;
2015-11-30 23:16:48 +00:00
break ;
case BSV_MOVIE_CTL_UNSET_PLAYBACK :
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie_playback = false ;
2015-11-30 23:16:48 +00:00
break ;
2015-11-30 23:04:04 +00:00
case BSV_MOVIE_CTL_DEINIT :
2015-12-01 00:47:26 +00:00
if ( bsv_movie_state . movie )
bsv_movie_free ( bsv_movie_state . movie ) ;
bsv_movie_state . movie = NULL ;
2015-11-30 23:04:04 +00:00
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 :
2015-12-01 00:47:26 +00:00
bsv_movie_set_frame_start ( bsv_movie_state . movie ) ;
2015-11-30 23:16:48 +00:00
break ;
case BSV_MOVIE_CTL_SET_FRAME_END :
2015-12-01 00:47:26 +00:00
bsv_movie_set_frame_end ( bsv_movie_state . movie ) ;
2015-11-30 23:16:48 +00:00
break ;
2015-11-30 23:19:15 +00:00
case BSV_MOVIE_CTL_FRAME_REWIND :
2015-12-01 00:47:26 +00:00
bsv_movie_frame_rewind ( bsv_movie_state . movie ) ;
2015-11-30 23:19:15 +00:00
break ;
2015-11-30 23:04:04 +00:00
default :
return false ;
}
return true ;
}
2015-12-01 00:38:45 +00:00
const char * bsv_movie_get_path ( void )
{
2015-12-01 00:47:26 +00:00
return bsv_movie_state . movie_path ;
2015-12-01 00:38:45 +00:00
}
void bsv_movie_set_path ( const char * path )
{
2015-12-01 00:47:26 +00:00
strlcpy ( bsv_movie_state . movie_path , path , sizeof ( bsv_movie_state . movie_path ) ) ;
2015-12-01 00:38:45 +00:00
}
void bsv_movie_set_start_path ( const char * path )
{
2015-12-01 00:47:26 +00:00
strlcpy ( bsv_movie_state . movie_start_path , path ,
sizeof ( bsv_movie_state . movie_start_path ) ) ;
2015-12-01 00:38:45 +00:00
}
bool bsv_movie_init_handle ( const char * path , enum rarch_movie_type type )
{
2015-12-01 00:47:26 +00:00
bsv_movie_state . movie = bsv_movie_init ( path , type ) ;
if ( ! bsv_movie_state . movie )
2015-12-01 00:38:45 +00:00
return false ;
return true ;
}