2013-10-25 00:59:34 +00:00
using System ;
2013-11-30 02:20:34 +00:00
using System.Collections.Generic ;
2013-10-25 00:59:34 +00:00
using System.Globalization ;
2013-11-30 02:20:34 +00:00
using System.IO ;
2013-11-30 02:50:54 +00:00
using System.Text ;
2013-11-27 21:45:50 +00:00
using BizHawk.Common ;
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2013-10-25 00:59:34 +00:00
namespace BizHawk.Client.Common
{
2013-11-23 17:26:33 +00:00
public class Movie : IMovie
2013-10-25 00:59:34 +00:00
{
#region Constructors
2013-11-29 19:55:05 +00:00
public Movie ( string filename , bool startsFromSavestate = false )
: this ( startsFromSavestate )
2013-10-25 00:59:34 +00:00
{
2013-12-02 17:50:29 +00:00
Header . Rerecords = 0 ;
2013-10-25 00:59:34 +00:00
Filename = filename ;
2013-11-10 02:55:11 +00:00
Loaded = ! String . IsNullOrWhiteSpace ( filename ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-29 19:55:05 +00:00
public Movie ( bool startsFromSavestate = false )
2013-10-25 00:59:34 +00:00
{
2013-11-10 02:55:11 +00:00
Header = new MovieHeader ( ) ;
2013-10-25 00:59:34 +00:00
Filename = String . Empty ;
2013-11-16 21:05:59 +00:00
_preloadFramecount = 0 ;
2013-11-30 02:20:34 +00:00
Header . StartsFromSavestate = startsFromSavestate ;
2013-10-25 00:59:34 +00:00
IsCountingRerecords = true ;
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Inactive ;
2013-10-27 22:13:08 +00:00
MakeBackup = true ;
2013-10-25 00:59:34 +00:00
}
#endregion
#region Properties
2013-11-30 03:23:19 +00:00
2013-11-29 23:18:46 +00:00
public IMovieHeader Header { get ; private set ; }
2013-11-29 20:26:24 +00:00
2013-10-27 22:13:08 +00:00
public bool MakeBackup { get ; set ; }
public string Filename { get ; set ; }
public bool IsCountingRerecords { get ; set ; }
2013-10-25 00:59:34 +00:00
public bool Loaded { get ; private set ; }
2013-11-30 03:10:05 +00:00
public int InputLogLength
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
get { return Loaded ? _log . Length : _preloadFramecount ; }
2013-10-25 00:59:34 +00:00
}
2013-11-30 03:10:05 +00:00
public double FrameCount
2013-10-25 00:59:34 +00:00
{
get
{
2013-11-30 03:10:05 +00:00
if ( _loopOffset . HasValue )
2013-10-25 00:59:34 +00:00
{
2013-11-30 03:10:05 +00:00
return double . PositiveInfinity ;
}
else if ( Loaded )
{
return _log . Length ;
2013-10-25 00:59:34 +00:00
}
else
{
2013-11-16 21:05:59 +00:00
return _preloadFramecount ;
2013-10-25 00:59:34 +00:00
}
}
}
#endregion
2013-10-27 22:13:08 +00:00
#region Mode API
2013-10-25 00:59:34 +00:00
public bool IsPlaying
{
2013-11-16 21:05:59 +00:00
get { return _mode = = Moviemode . Play | | _mode = = Moviemode . Finished ; }
2013-10-25 00:59:34 +00:00
}
public bool IsRecording
{
2013-11-16 21:05:59 +00:00
get { return _mode = = Moviemode . Record ; }
2013-10-25 00:59:34 +00:00
}
public bool IsActive
{
2013-11-16 21:05:59 +00:00
get { return _mode ! = Moviemode . Inactive ; }
2013-10-25 00:59:34 +00:00
}
public bool IsFinished
{
2013-11-16 21:05:59 +00:00
get { return _mode = = Moviemode . Finished ; }
2013-10-25 00:59:34 +00:00
}
2013-11-23 18:18:58 +00:00
public bool Changes
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
get { return _changes ; }
2013-10-25 00:59:34 +00:00
}
2013-11-29 19:55:05 +00:00
public void StartNewRecording ( )
2013-10-25 00:59:34 +00:00
{
2013-12-05 00:20:21 +00:00
Global . Emulator . ClearSaveRam ( ) ;
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Record ;
2013-10-27 22:13:08 +00:00
if ( Global . Config . EnableBackupMovies & & MakeBackup & & _log . Length > 0 )
2013-10-25 00:59:34 +00:00
{
2013-11-23 18:18:58 +00:00
SaveAs ( ) ;
2013-10-25 00:59:34 +00:00
MakeBackup = false ;
}
2013-11-30 02:20:34 +00:00
2013-11-29 19:55:05 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-29 19:55:05 +00:00
public void StartNewPlayback ( )
2013-10-25 00:59:34 +00:00
{
2013-11-29 19:55:05 +00:00
Global . Emulator . ClearSaveRam ( ) ;
2013-12-05 00:20:21 +00:00
_mode = Moviemode . Play ;
2013-10-25 00:59:34 +00:00
}
public void SwitchToRecord ( )
{
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Record ;
2013-10-25 00:59:34 +00:00
}
public void SwitchToPlay ( )
{
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Play ;
2013-11-23 18:18:58 +00:00
Save ( ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-23 18:18:58 +00:00
public void Stop ( bool saveChanges = true )
2013-10-25 00:59:34 +00:00
{
2013-11-23 18:18:58 +00:00
if ( saveChanges )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
if ( _mode = = Moviemode . Record | | _changes )
2013-10-25 00:59:34 +00:00
{
2013-11-23 18:18:58 +00:00
Save ( ) ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-30 02:20:34 +00:00
2013-10-27 22:13:08 +00:00
_changes = false ;
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Inactive ;
2013-10-25 00:59:34 +00:00
}
2013-10-27 22:13:08 +00:00
/// <summary>
/// If a movie is in playback mode, this will set it to movie finished
/// </summary>
2013-11-29 19:55:05 +00:00
private void Finish ( )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
if ( _mode = = Moviemode . Play )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Finished ;
2013-10-25 00:59:34 +00:00
}
}
#endregion
#region Public File Handling
2013-11-23 18:18:58 +00:00
public void SaveAs ( string path )
2013-10-25 00:59:34 +00:00
{
if ( ! Loaded )
{
return ;
}
2013-11-30 02:20:34 +00:00
2013-10-25 00:59:34 +00:00
var directory_info = new FileInfo ( Filename ) . Directory ;
2013-11-30 02:20:34 +00:00
if ( directory_info ! = null )
{
Directory . CreateDirectory ( directory_info . FullName ) ;
}
2013-12-04 03:22:27 +00:00
Write ( Filename ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-23 18:18:58 +00:00
public void Save ( )
2013-10-25 00:59:34 +00:00
{
2013-10-28 00:44:01 +00:00
if ( ! Loaded | | String . IsNullOrWhiteSpace ( Filename ) )
2013-10-25 00:59:34 +00:00
{
return ;
}
2013-11-23 18:18:58 +00:00
SaveAs ( Filename ) ;
2013-10-27 22:13:08 +00:00
_changes = false ;
2013-10-25 00:59:34 +00:00
}
2013-11-23 18:18:58 +00:00
public void SaveAs ( )
2013-10-25 00:59:34 +00:00
{
2013-10-28 00:44:01 +00:00
if ( ! Loaded | | String . IsNullOrWhiteSpace ( Filename ) )
2013-10-25 00:59:34 +00:00
{
return ;
}
2013-11-30 02:20:34 +00:00
var backupName = Filename ;
backupName = backupName . Insert ( Filename . LastIndexOf ( "." ) , String . Format ( ".{0:yyyy-MM-dd HH.mm.ss}" , DateTime . Now ) ) ;
backupName = Path . Combine ( Global . Config . PathEntries [ "Global" , "Movie backups" ] . Path , Path . GetFileName ( backupName ) ? ? String . Empty ) ;
2013-10-25 00:59:34 +00:00
2013-11-30 02:20:34 +00:00
var directory_info = new FileInfo ( backupName ) . Directory ;
if ( directory_info ! = null )
{
Directory . CreateDirectory ( directory_info . FullName ) ;
}
2013-10-25 00:59:34 +00:00
2013-12-04 03:22:27 +00:00
Write ( backupName ) ;
2013-10-25 00:59:34 +00:00
}
/// <summary>
/// Load Header information only for displaying file information in dialogs such as play movie
/// </summary>
2013-11-27 21:45:50 +00:00
public bool PreLoadText ( HawkFile hawkFile )
2013-10-25 00:59:34 +00:00
{
Loaded = false ;
2013-11-27 21:45:50 +00:00
var file = new FileInfo ( hawkFile . CanonicalFullPath ) ;
2013-10-25 00:59:34 +00:00
if ( file . Exists = = false )
2013-11-30 02:20:34 +00:00
{
2013-10-25 00:59:34 +00:00
return false ;
2013-11-30 02:20:34 +00:00
}
2013-10-25 00:59:34 +00:00
else
{
Header . Clear ( ) ;
2013-10-27 22:13:08 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
var origStreamPosn = hawkFile . GetStream ( ) . Position ;
hawkFile . GetStream ( ) . Position = 0 ; // Reset to start
var sr = new StreamReader ( hawkFile . GetStream ( ) ) ;
// No using block because we're sharing the stream and need to give it back undisposed.
if ( ! sr . EndOfStream )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
string line ;
while ( ( line = sr . ReadLine ( ) ) ! = null )
2013-10-25 00:59:34 +00:00
{
2013-11-30 03:10:05 +00:00
if ( line . Contains ( "LoopOffset" ) )
{
try
{
_loopOffset = int . Parse ( line . Split ( new [ ] { ' ' } , 2 ) [ 1 ] ) ;
}
catch ( Exception )
{
continue ;
}
}
else if ( String . IsNullOrWhiteSpace ( line ) | | Header . ParseLineFromFile ( line ) )
2013-10-25 00:59:34 +00:00
{
continue ;
}
2013-11-30 03:10:05 +00:00
else if ( line . StartsWith ( "|" ) )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
var frames = sr . ReadToEnd ( ) ;
var length = line . Length ;
2013-10-25 00:59:34 +00:00
// Account for line breaks of either size.
if ( frames . IndexOf ( "\r\n" ) ! = - 1 )
{
length + + ;
}
length + + ;
2013-11-30 02:20:34 +00:00
_preloadFramecount = ( frames . Length / length ) + 1 ; // Count the remaining frames and the current one.
2013-10-25 00:59:34 +00:00
break ;
}
else
{
2013-11-30 02:20:34 +00:00
Header . Comments . Add ( line ) ;
2013-10-25 00:59:34 +00:00
}
}
}
2013-11-30 02:20:34 +00:00
2013-11-27 21:45:50 +00:00
hawkFile . GetStream ( ) . Position = origStreamPosn ;
2013-10-25 00:59:34 +00:00
return true ;
}
2013-11-23 18:18:58 +00:00
public bool Load ( )
2013-10-25 00:59:34 +00:00
{
var file = new FileInfo ( Filename ) ;
if ( file . Exists = = false )
{
Loaded = false ;
return false ;
}
return LoadText ( ) ;
}
#endregion
#region Public Log Editing
public string GetInput ( int frame )
{
2013-11-29 19:55:05 +00:00
if ( frame < _log . Length )
2013-10-25 00:59:34 +00:00
{
2013-11-29 19:55:05 +00:00
if ( frame > = 0 )
2013-10-25 00:59:34 +00:00
{
2013-11-29 19:55:05 +00:00
int getframe ;
if ( _loopOffset . HasValue )
2013-10-28 00:47:12 +00:00
{
2013-11-29 19:55:05 +00:00
if ( frame < _log . Length )
{
getframe = frame ;
}
else
{
getframe = ( ( frame - _loopOffset . Value ) % ( _log . Length - _loopOffset . Value ) ) + _loopOffset . Value ;
}
2013-10-28 00:47:12 +00:00
}
else
{
2013-11-29 19:55:05 +00:00
getframe = frame ;
2013-10-28 00:47:12 +00:00
}
2013-11-29 19:55:05 +00:00
return _log [ getframe ] ;
2013-10-25 00:59:34 +00:00
}
else
{
2013-11-29 19:55:05 +00:00
return String . Empty ;
2013-10-25 00:59:34 +00:00
}
}
else
{
2013-11-29 19:55:05 +00:00
Finish ( ) ;
2013-10-28 00:47:12 +00:00
return String . Empty ;
2013-10-25 00:59:34 +00:00
}
}
public void ClearFrame ( int frame )
{
2013-10-27 22:13:08 +00:00
_log . SetFrameAt ( frame , MnemonicsGenerator . GetEmptyMnemonic ) ;
_changes = true ;
2013-10-25 00:59:34 +00:00
}
2013-12-03 02:10:17 +00:00
public void AppendFrame ( MnemonicsGenerator mg )
2013-10-25 00:59:34 +00:00
{
2013-12-03 02:10:17 +00:00
_log . AppendFrame ( mg . GetControllersAsMnemonic ( ) ) ;
2013-10-27 22:13:08 +00:00
_changes = true ;
2013-10-25 00:59:34 +00:00
}
2013-12-03 01:43:02 +00:00
public void Truncate ( int frame )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_log . TruncateMovie ( frame ) ;
_log . TruncateStates ( frame ) ;
_changes = true ;
2013-10-25 00:59:34 +00:00
}
#endregion
#region Public Misc Methods
2013-12-03 15:59:46 +00:00
public void PokeFrame ( int frame , MnemonicsGenerator mg )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_changes = true ;
2013-12-03 15:59:46 +00:00
_log . SetFrameAt ( frame , mg . GetControllersAsMnemonic ( ) ) ;
2013-10-25 00:59:34 +00:00
}
2013-12-03 15:59:46 +00:00
public void RecordFrame ( int frame , MnemonicsGenerator mg )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
// Note: Truncation here instead of loadstate will make VBA style loadstates
// (Where an entire movie is loaded then truncated on the next frame
// this allows users to restore a movie with any savestate from that "timeline"
2013-10-25 00:59:34 +00:00
if ( Global . Config . VBAStyleMovieLoadState )
{
2013-10-27 22:13:08 +00:00
if ( Global . Emulator . Frame < _log . Length )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_log . TruncateMovie ( Global . Emulator . Frame ) ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-30 02:20:34 +00:00
2013-10-27 22:13:08 +00:00
_changes = true ;
2013-12-03 15:59:46 +00:00
_log . SetFrameAt ( frame , mg . GetControllersAsMnemonic ( ) ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:50:54 +00:00
public string GetInputLog ( )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:50:54 +00:00
var sb = new StringBuilder ( ) ;
2013-12-04 03:04:29 +00:00
sb . AppendLine ( "[Input]" ) ;
2013-10-28 00:44:01 +00:00
2013-11-30 02:50:54 +00:00
foreach ( var record in _log )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:50:54 +00:00
sb . AppendLine ( record ) ;
2013-10-25 00:59:34 +00:00
}
2013-10-28 00:44:01 +00:00
2013-11-30 02:50:54 +00:00
sb . AppendLine ( "[/Input]" ) ;
return sb . ToString ( ) ;
2013-10-25 00:59:34 +00:00
}
2013-12-05 00:20:21 +00:00
public bool ExtractInputLog ( TextReader reader , out string errorMessage )
2013-10-25 00:59:34 +00:00
{
2013-12-05 00:20:21 +00:00
errorMessage = String . Empty ;
2013-10-25 00:59:34 +00:00
int? stateFrame = null ;
2013-11-30 02:20:34 +00:00
// We are in record mode so replace the movie log with the one from the savestate
2013-12-03 18:08:45 +00:00
if ( ! Global . MovieSession . MultiTrack . IsActive )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
if ( Global . Config . EnableBackupMovies & & MakeBackup & & _log . Length > 0 )
2013-10-25 00:59:34 +00:00
{
2013-11-23 18:18:58 +00:00
SaveAs ( ) ;
2013-10-25 00:59:34 +00:00
MakeBackup = false ;
}
2013-11-30 02:20:34 +00:00
2013-10-27 22:13:08 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
while ( true )
{
2013-11-30 02:20:34 +00:00
var line = reader . ReadLine ( ) ;
if ( line = = null )
{
break ;
}
else if ( line . Trim ( ) = = String . Empty )
{
continue ;
}
else if ( line = = "[Input]" )
{
continue ;
}
else if ( line = = "[/Input]" )
{
break ;
}
else if ( line . Contains ( "Frame 0x" ) ) // NES stores frame count in hex, yay
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
var strs = line . Split ( 'x' ) ;
2013-10-25 00:59:34 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
2013-12-05 00:20:21 +00:00
catch
{
errorMessage = "Savestate Frame number failed to parse" ;
return false ;
}
2013-10-25 00:59:34 +00:00
}
else if ( line . Contains ( "Frame " ) )
{
2013-11-30 02:20:34 +00:00
var strs = line . Split ( ' ' ) ;
2013-10-25 00:59:34 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
2013-12-05 00:20:21 +00:00
catch
{
errorMessage = "Savestate Frame number failed to parse" ;
return false ;
}
2013-10-25 00:59:34 +00:00
}
if ( line [ 0 ] = = '|' )
{
2013-10-27 22:13:08 +00:00
_log . AppendFrame ( line ) ;
2013-10-25 00:59:34 +00:00
}
}
}
else
{
int i = 0 ;
while ( true )
{
2013-11-30 02:20:34 +00:00
var line = reader . ReadLine ( ) ;
if ( line = = null )
{
break ;
}
else if ( line . Trim ( ) = = string . Empty )
{
continue ;
}
else if ( line = = "[Input]" )
{
continue ;
}
else if ( line = = "[/Input]" )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
break ;
}
else if ( line . Contains ( "Frame 0x" ) ) // NES stores frame count in hex, yay
{
var strs = line . Split ( 'x' ) ;
2013-10-25 00:59:34 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
2013-12-05 00:20:21 +00:00
catch
{
errorMessage = "Savestate Frame number failed to parse" ;
return false ;
}
2013-10-25 00:59:34 +00:00
}
else if ( line . Contains ( "Frame " ) )
{
2013-11-30 02:20:34 +00:00
var strs = line . Split ( ' ' ) ;
2013-10-25 00:59:34 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
2013-12-05 00:20:21 +00:00
catch
{
errorMessage = "Savestate Frame number failed to parse" ;
return false ;
}
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
else if ( line . StartsWith ( "|" ) )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_log . SetFrameAt ( i , line ) ;
2013-10-25 00:59:34 +00:00
i + + ;
}
}
}
2013-11-30 02:20:34 +00:00
2013-10-25 00:59:34 +00:00
if ( stateFrame = = null )
2013-11-30 02:20:34 +00:00
{
2013-12-05 00:20:21 +00:00
errorMessage = "Savestate Frame number failed to parse" ;
2013-11-30 02:20:34 +00:00
}
var stateFramei = ( int ) stateFrame ;
2013-10-25 00:59:34 +00:00
2013-10-27 22:13:08 +00:00
if ( stateFramei > 0 & & stateFramei < _log . Length )
2013-10-25 00:59:34 +00:00
{
if ( ! Global . Config . VBAStyleMovieLoadState )
{
2013-10-27 22:13:08 +00:00
_log . TruncateStates ( stateFramei ) ;
_log . TruncateMovie ( stateFramei ) ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-30 02:20:34 +00:00
else if ( stateFramei > _log . Length ) // Post movie savestate
2013-10-25 00:59:34 +00:00
{
if ( ! Global . Config . VBAStyleMovieLoadState )
{
2013-10-27 22:13:08 +00:00
_log . TruncateStates ( _log . Length ) ;
_log . TruncateMovie ( _log . Length ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Finished ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
2013-10-25 00:59:34 +00:00
if ( IsCountingRerecords )
2013-11-30 02:20:34 +00:00
{
2013-12-02 17:50:29 +00:00
Header . Rerecords + + ;
2013-11-30 02:20:34 +00:00
}
2013-12-05 00:20:21 +00:00
return true ;
2013-10-25 00:59:34 +00:00
}
2013-12-02 21:57:48 +00:00
public TimeSpan Time
2013-10-25 00:59:34 +00:00
{
2013-12-02 21:57:48 +00:00
get
2013-10-25 00:59:34 +00:00
{
2013-12-02 21:57:48 +00:00
double dblseconds = GetSeconds ( Loaded ? _log . Length : _preloadFramecount ) ;
int seconds = ( int ) ( dblseconds % 60 ) ;
int days = seconds / 86400 ;
int hours = seconds / 3600 ;
int minutes = ( seconds / 60 ) % 60 ;
int milliseconds = ( int ) ( ( dblseconds - ( double ) seconds ) * 1000 ) ;
return new TimeSpan ( days , hours , minutes , seconds , milliseconds ) ;
2013-10-25 00:59:34 +00:00
}
}
2013-12-04 03:16:35 +00:00
public bool CheckTimeLines ( TextReader reader , out string errorMessage )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
// This function will compare the movie data to the savestate movie data to see if they match
2013-11-16 21:05:59 +00:00
errorMessage = String . Empty ;
2013-10-25 00:59:34 +00:00
var log = new MovieLog ( ) ;
int stateFrame = 0 ;
while ( true )
{
2013-11-30 02:20:34 +00:00
var line = reader . ReadLine ( ) ;
2013-10-25 00:59:34 +00:00
if ( line = = null )
{
2013-12-04 03:16:35 +00:00
return false ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
else if ( line . Trim ( ) = = string . Empty )
2013-10-25 00:59:34 +00:00
{
continue ;
}
2013-11-30 02:20:34 +00:00
else if ( line . Contains ( "Frame 0x" ) ) // NES stores frame count in hex, yay
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
var strs = line . Split ( 'x' ) ;
2013-10-25 00:59:34 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
catch
{
2013-11-16 21:05:59 +00:00
errorMessage = "Savestate Frame number failed to parse" ;
2013-12-04 03:16:35 +00:00
return false ;
2013-10-25 00:59:34 +00:00
}
}
else if ( line . Contains ( "Frame " ) )
{
2013-11-30 02:20:34 +00:00
var strs = line . Split ( ' ' ) ;
2013-10-25 00:59:34 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
catch
{
2013-11-16 21:05:59 +00:00
errorMessage = "Savestate Frame number failed to parse" ;
2013-12-04 03:16:35 +00:00
return false ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-30 02:20:34 +00:00
else if ( line = = "[Input]" )
{
continue ;
}
else if ( line = = "[/Input]" )
{
break ;
}
2013-10-25 00:59:34 +00:00
else if ( line [ 0 ] = = '|' )
{
log . AppendFrame ( line ) ;
}
}
2013-12-05 00:20:21 +00:00
2013-10-25 00:59:34 +00:00
if ( stateFrame = = 0 )
{
2013-11-30 02:20:34 +00:00
stateFrame = log . Length ; // In case the frame count failed to parse, revert to using the entire state input log
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
2013-10-27 22:13:08 +00:00
if ( _log . Length < stateFrame )
2013-10-25 00:59:34 +00:00
{
2013-11-23 00:13:36 +00:00
if ( IsFinished )
{
2013-12-04 03:16:35 +00:00
return true ;
2013-11-23 00:13:36 +00:00
}
else
{
errorMessage = "The savestate is from frame "
2013-11-30 02:20:34 +00:00
+ log . Length
2013-11-23 00:13:36 +00:00
+ " which is greater than the current movie length of "
2013-11-30 02:20:34 +00:00
+ _log . Length ;
2013-12-04 03:16:35 +00:00
return false ;
2013-11-23 00:13:36 +00:00
}
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
for ( var i = 0 ; i < stateFrame ; i + + )
2013-10-25 00:59:34 +00:00
{
2013-10-28 00:44:01 +00:00
if ( _log [ i ] ! = log [ i ] )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
errorMessage = "The savestate input does not match the movie input at frame "
2013-11-30 02:20:34 +00:00
+ ( i + 1 )
2013-10-25 00:59:34 +00:00
+ "." ;
2013-12-04 03:16:35 +00:00
return false ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-30 02:20:34 +00:00
if ( stateFrame > log . Length ) // stateFrame is greater than state input log, so movie finished mode
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
if ( _mode = = Moviemode . Play | | _mode = = Moviemode . Finished )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Finished ;
2013-12-04 03:16:35 +00:00
return true ;
2013-10-25 00:59:34 +00:00
}
else
{
2013-12-04 03:16:35 +00:00
return false ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-16 21:05:59 +00:00
else if ( _mode = = Moviemode . Finished )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
_mode = Moviemode . Play ;
2013-10-25 00:59:34 +00:00
}
2013-12-04 03:16:35 +00:00
return true ;
2013-10-25 00:59:34 +00:00
}
#endregion
2013-10-27 22:13:08 +00:00
#region Private Vars
2013-10-25 00:59:34 +00:00
2013-10-27 22:13:08 +00:00
private readonly MovieLog _log = new MovieLog ( ) ;
2013-11-16 21:05:59 +00:00
private enum Moviemode { Inactive , Play , Record , Finished } ;
private Moviemode _mode = Moviemode . Inactive ;
2013-11-30 02:20:34 +00:00
private int _preloadFramecount ; // Not a a reliable number, used for preloading (when no log has yet been loaded), this is only for quick stat compilation for dialogs such as play movie
2013-10-27 22:13:08 +00:00
private bool _changes ;
2013-11-16 21:05:59 +00:00
private int? _loopOffset ;
2013-12-04 03:22:27 +00:00
private readonly PlatformFrameRates _frameRates = new PlatformFrameRates ( ) ;
2013-10-27 22:13:08 +00:00
2013-10-25 00:59:34 +00:00
#endregion
#region Helpers
2013-12-04 03:22:27 +00:00
private void Write ( string fn )
2013-10-25 00:59:34 +00:00
{
using ( var fs = new FileStream ( fn , FileMode . Create , FileAccess . Write , FileShare . Read ) )
2013-11-30 02:20:34 +00:00
{
2013-10-25 00:59:34 +00:00
WriteText ( fs ) ;
2013-11-30 02:20:34 +00:00
}
2013-10-25 00:59:34 +00:00
}
private void WriteText ( Stream stream )
{
2013-11-29 20:26:24 +00:00
using ( var sw = new StreamWriter ( stream ) )
2013-10-25 00:59:34 +00:00
{
2013-10-28 01:04:38 +00:00
sw . Write ( Header . ToString ( ) ) ;
2013-10-25 00:59:34 +00:00
2013-11-29 20:26:24 +00:00
// TODO: clean this up
2013-10-28 01:04:38 +00:00
if ( _loopOffset . HasValue )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
sw . WriteLine ( "LoopOffset " + _loopOffset ) ;
2013-10-25 00:59:34 +00:00
}
2013-10-28 01:04:38 +00:00
for ( int i = 0 ; i < _log . Length ; i + + )
{
sw . WriteLine ( _log [ i ] ) ;
}
2013-10-25 00:59:34 +00:00
}
}
private bool LoadText ( )
{
var file = new FileInfo ( Filename ) ;
if ( file . Exists = = false )
{
Loaded = false ;
return false ;
}
else
{
Header . Clear ( ) ;
2013-10-27 22:13:08 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
using ( var sr = file . OpenText ( ) )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
string line ;
2013-10-25 00:59:34 +00:00
2013-11-30 02:20:34 +00:00
while ( ( line = sr . ReadLine ( ) ) ! = null )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
if ( line = = String . Empty )
2013-10-25 00:59:34 +00:00
{
continue ;
}
2013-11-30 02:20:34 +00:00
if ( line . Contains ( "LoopOffset" ) )
2013-10-25 00:59:34 +00:00
{
try
{
2013-11-30 02:20:34 +00:00
_loopOffset = int . Parse ( line . Split ( new [ ] { ' ' } , 2 ) [ 1 ] ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
catch ( Exception )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
continue ;
2013-10-25 00:59:34 +00:00
}
}
2013-11-30 02:20:34 +00:00
else if ( Header . ParseLineFromFile ( line ) )
2013-10-25 00:59:34 +00:00
{
continue ;
}
2013-11-30 02:20:34 +00:00
else if ( line . StartsWith ( "|" ) )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
_log . AppendFrame ( line ) ;
2013-10-25 00:59:34 +00:00
}
else
{
2013-11-30 02:20:34 +00:00
Header . Comments . Add ( line ) ;
2013-10-25 00:59:34 +00:00
}
}
}
2013-11-30 02:20:34 +00:00
2013-10-25 00:59:34 +00:00
Loaded = true ;
return true ;
}
2013-11-30 02:20:34 +00:00
private static string MakeDigits ( int num )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
return num < 10 ? "0" + num : num . ToString ( ) ;
2013-10-25 00:59:34 +00:00
}
private double GetSeconds ( int frameCount )
{
double frames = frameCount ;
if ( frames < 1 )
{
return 0 ;
}
2013-11-30 02:20:34 +00:00
var system = Header [ HeaderKeys . PLATFORM ] ;
var pal = Header . ContainsKey ( HeaderKeys . PAL ) & &
Header [ HeaderKeys . PAL ] = = "1" ;
2013-10-25 00:59:34 +00:00
2013-12-03 18:08:45 +00:00
return frames / _frameRates [ system , pal ] ;
2013-10-25 00:59:34 +00:00
}
2013-11-16 21:49:47 +00:00
public double Fps
{
get
{
2013-11-30 02:20:34 +00:00
var system = Header [ HeaderKeys . PLATFORM ] ;
var pal = Header . ContainsKey ( HeaderKeys . PAL ) & &
Header [ HeaderKeys . PAL ] = = "1" ;
2013-11-16 21:49:47 +00:00
2013-12-03 18:08:45 +00:00
return _frameRates [ system , pal ] ;
2013-11-16 21:49:47 +00:00
}
}
2013-10-25 00:59:34 +00:00
#endregion
}
}