2013-10-25 00:59:34 +00:00
using System ;
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-04 01:39:19 +00:00
2014-06-08 19:36:33 +00:00
using BizHawk.Common ;
2013-12-07 16:31:04 +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
{
2014-04-06 19:01:00 +00:00
private readonly MovieLog _log = new MovieLog ( ) ;
private readonly PlatformFrameRates _frameRates = new PlatformFrameRates ( ) ;
private Moviemode _mode = Moviemode . Inactive ;
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
private bool _changes ;
private int? _loopOffset ;
2013-10-25 00:59:34 +00:00
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 ;
2014-04-06 19:01:00 +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 ( ) ;
2014-04-06 19:01:00 +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
}
2014-04-06 19:01:00 +00:00
private enum Moviemode { Inactive , Play , Record , Finished }
2013-10-25 00:59:34 +00:00
#region Properties
2013-11-30 03:23:19 +00:00
2014-06-08 19:36:33 +00:00
public string PreferredExtension { get { return "bkm" ; } }
public static string Extension { get { return "bkm" ; } }
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-12-10 17:41:30 +00:00
get { return _log . Length ; }
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 ;
}
2014-04-06 19:01:00 +00:00
if ( Loaded )
2013-11-30 03:10:05 +00:00
{
return _log . Length ;
2013-10-25 00:59:34 +00:00
}
2014-04-06 19:01:00 +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
{
2014-05-18 01:00:35 +00:00
// adelikat: ClearSaveRam shouldn't be here at all most likely, especially considering this is an implementation detail
// If Starting a new recording requires clearing sram it shoudl be done at a higher layer and not rely on all IMovies doing this
// Haven't removed it yet because I coudln't guarantee that power-on movies coudl live without it
// And the immediate fire is that Savestate movies are breaking
2014-06-02 16:33:08 +00:00
if ( ! Header . StartsFromSavestate ) // && Global.Emulator.SystemId != "WSWAN")
2014-05-18 01:00:35 +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-12-10 17:59:04 +00:00
SaveBackup ( ) ;
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
{
2014-05-18 01:00:35 +00:00
// See StartNewRecording for details as to why this savestate check is here
2014-06-02 16:33:08 +00:00
if ( ! Header . StartsFromSavestate ) // && Global.Emulator.SystemId != "WSWAN")
2014-05-18 01:00:35 +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
{
2013-12-10 17:59:04 +00:00
Filename = 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
{
2014-04-06 19:01:00 +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-12-10 17:59:04 +00:00
public void SaveBackup ( )
2013-10-25 00:59:34 +00:00
{
2014-04-06 19:01:00 +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 ;
2014-04-06 19:01:00 +00:00
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
2014-04-25 02:19:46 +00:00
/// TODO - consider not loading the SavestateBinaryBase64Blob key?
2013-10-25 00:59:34 +00:00
/// </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
}
2014-04-06 19:01:00 +00:00
Header . Clear ( ) ;
_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
2014-05-16 00:12:08 +00:00
// No using block because we're sharing the stream and need to give it back undisposed.
2013-11-30 02:20:34 +00:00
var sr = new StreamReader ( hawkFile . GetStream ( ) ) ;
2014-05-16 00:12:08 +00:00
for ( ; ; )
2013-10-25 00:59:34 +00:00
{
2014-05-16 00:12:08 +00:00
//read to first space (key/value delimeter), or pipe, or EOF
int first = sr . Read ( ) ;
if ( first = = - 1 ) break ; //EOF
else if ( first = = '|' ) //pipe: begin input log
2013-10-25 00:59:34 +00:00
{
2014-05-16 00:12:08 +00:00
//NOTE - this code is a bit convoluted due to its predating the basic outline of the parser which was upgraded in may 2014
string line = '|' + sr . ReadLine ( ) ;
//how many bytes are left, total?
long remain = sr . BaseStream . Length - sr . BaseStream . Position ;
//try to find out whether we use \r\n or \n
//but only look for 1K characters.
bool usesR = false ;
for ( int i = 0 ; i < 1024 ; i + + )
2013-11-30 03:10:05 +00:00
{
2014-05-16 00:12:08 +00:00
int c = sr . Read ( ) ;
if ( c = = - 1 )
break ;
if ( c = = '\r' )
2013-11-30 03:10:05 +00:00
{
2014-05-16 00:12:08 +00:00
usesR = true ;
break ;
2013-11-30 03:10:05 +00:00
}
2014-05-16 00:12:08 +00:00
if ( c = = '\n' )
break ;
2013-11-30 03:10:05 +00:00
}
2014-05-16 00:12:08 +00:00
int lineLen = line . Length + 1 ; //account for \n
if ( usesR ) lineLen + + ; //account for \r
_preloadFramecount = ( int ) ( remain / lineLen ) ; //length is remaining bytes / length per line
_preloadFramecount + + ; //account for the current line
break ;
}
else
{
//a header line. finish reading key token, to make sure it isn't one of the FORBIDDEN keys
StringBuilder sbLine = new StringBuilder ( ) ;
sbLine . Append ( ( char ) first ) ;
for ( ; ; )
2013-10-25 00:59:34 +00:00
{
2014-05-16 00:12:08 +00:00
int c = sr . Read ( ) ;
if ( c = = - 1 ) break ;
if ( c = = '\n' ) break ;
if ( c = = ' ' ) break ;
sbLine . Append ( ( char ) c ) ;
2013-10-25 00:59:34 +00:00
}
2013-11-30 02:20:34 +00:00
2014-05-16 00:12:08 +00:00
string line = sbLine . ToString ( ) ;
2013-10-25 00:59:34 +00:00
2014-05-16 00:12:08 +00:00
//ignore these suckers, theyre way too big for preloading. seriously, we will get out of memory errors.
bool skip = false ;
if ( line = = HeaderKeys . SAVESTATEBINARYBASE64BLOB ) skip = true ;
if ( skip )
2013-10-25 00:59:34 +00:00
{
2014-05-16 00:12:08 +00:00
//skip remainder of the line
sr . DiscardBufferedData ( ) ;
var stream = sr . BaseStream ;
for ( ; ; )
{
int c = stream . ReadByte ( ) ;
if ( c = = - 1 ) break ;
if ( c = = '\n' ) break ;
}
//proceed to next line
continue ;
2013-10-25 00:59:34 +00:00
}
2014-05-16 00:12:08 +00:00
string remainder = sr . ReadLine ( ) ;
sbLine . Append ( ' ' ) ;
sbLine . Append ( remainder ) ;
line = sbLine . ToString ( ) ;
if ( string . IsNullOrWhiteSpace ( line ) | | Header . ParseLineFromFile ( line ) )
continue ;
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 ) ;
2014-04-06 19:01:00 +00:00
2013-10-25 00:59:34 +00:00
if ( file . Exists = = false )
{
Loaded = false ;
return false ;
}
2014-04-06 19:01:00 +00:00
Header . Clear ( ) ;
_log . Clear ( ) ;
using ( var sr = file . OpenText ( ) )
{
string line ;
while ( ( line = sr . ReadLine ( ) ) ! = null )
{
if ( line = = string . Empty )
{
continue ;
}
if ( line . Contains ( "LoopOffset" ) )
{
try
{
_loopOffset = int . Parse ( line . Split ( new [ ] { ' ' } , 2 ) [ 1 ] ) ;
}
catch ( Exception )
{
continue ;
}
}
else if ( Header . ParseLineFromFile ( line ) )
{
continue ;
}
else if ( line . StartsWith ( "|" ) )
{
_log . AppendFrame ( line ) ;
}
else
{
Header . Comments . Add ( line ) ;
}
}
}
2013-10-25 00:59:34 +00:00
2014-04-06 19:01:00 +00:00
Loaded = true ;
return true ;
2013-10-25 00:59:34 +00:00
}
#endregion
#region Public Log Editing
public string GetInput ( int frame )
{
2014-05-05 01:12:15 +00:00
if ( frame < FrameCount )
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
}
2014-04-06 19:01:00 +00:00
return string . Empty ;
2013-10-25 00:59:34 +00:00
}
2014-04-06 19:01:00 +00:00
Finish ( ) ;
return string . Empty ;
2013-10-25 00:59:34 +00:00
}
public void ClearFrame ( int frame )
{
2013-12-07 16:31:04 +00:00
_log . SetFrameAt ( frame , new MnemonicsGenerator ( ) . EmptyMnemonic ) ;
2013-10-27 22:13:08 +00:00
_changes = true ;
2013-10-25 00:59:34 +00:00
}
2013-12-07 16:31:04 +00:00
public void AppendFrame ( IController source )
2013-10-25 00:59:34 +00:00
{
2014-04-06 19:01:00 +00:00
var mg = new MnemonicsGenerator ( ) ;
2013-12-07 16:31:04 +00:00
mg . SetSource ( source ) ;
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-07 16:31:04 +00:00
public void PokeFrame ( int frame , IController source )
2013-10-25 00:59:34 +00:00
{
2014-04-06 19:01:00 +00:00
var mg = new MnemonicsGenerator ( ) ;
2013-12-07 16:31:04 +00:00
mg . SetSource ( source ) ;
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-07 16:31:04 +00:00
public void RecordFrame ( int frame , IController source )
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
2014-04-06 19:01:00 +00:00
var mg = new MnemonicsGenerator ( ) ;
2013-12-07 16:31:04 +00:00
mg . SetSource ( source ) ;
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
{
2014-04-06 19:01:00 +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-12-10 17:59:04 +00:00
SaveBackup ( ) ;
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 ;
}
2014-04-06 19:01:00 +00:00
if ( line . Trim ( ) = = string . Empty | | line = = "[Input]" )
2013-11-30 02:20:34 +00:00
{
continue ;
}
2014-04-06 19:01:00 +00:00
if ( line = = "[/Input]" )
2013-11-30 02:20:34 +00:00
{
break ;
}
2014-04-06 19:01:00 +00:00
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
}
2013-12-05 00:44:56 +00:00
else if ( line [ 0 ] = = '|' )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_log . AppendFrame ( line ) ;
2013-10-25 00:59:34 +00:00
}
}
}
else
{
2014-04-06 19:01:00 +00:00
var i = 0 ;
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 ;
}
2014-04-06 19:01:00 +00:00
if ( line . Trim ( ) = = string . Empty | | line = = "[Input]" )
2013-11-30 02:20:34 +00:00
{
continue ;
}
2014-04-06 19:01:00 +00:00
if ( line = = "[/Input]" )
2013-10-25 00:59:34 +00:00
{
2013-11-30 02:20:34 +00:00
break ;
}
2014-04-06 19:01:00 +00:00
if ( line . Contains ( "Frame 0x" ) ) // NES stores frame count in hex, yay
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
}
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
2014-04-06 19:01:00 +00:00
if ( ! stateFrame . HasValue )
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
}
2014-04-06 19:01:00 +00:00
var stateFramei = stateFrame ? ? 0 ;
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
{
2014-04-06 19:01:00 +00:00
var dblseconds = GetSeconds ( Loaded ? _log . Length : _preloadFramecount ) ;
var seconds = ( int ) ( dblseconds % 60 ) ;
var days = seconds / 86400 ;
var hours = seconds / 3600 ;
var minutes = ( seconds / 60 ) % 60 ;
var milliseconds = ( int ) ( ( dblseconds - seconds ) * 1000 ) ;
2013-12-02 21:57:48 +00:00
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
2014-04-06 19:01:00 +00:00
errorMessage = string . Empty ;
2013-10-25 00:59:34 +00:00
var log = new MovieLog ( ) ;
2014-04-06 19:01:00 +00:00
var stateFrame = 0 ;
2013-10-25 00:59:34 +00:00
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
}
2014-04-06 19:01:00 +00:00
if ( line . Trim ( ) = = string . Empty )
2013-10-25 00:59:34 +00:00
{
continue ;
}
2014-04-06 19:01:00 +00:00
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 ) ;
}
}
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
}
2014-04-06 19:01:00 +00:00
errorMessage = "The savestate is from frame "
+ log . Length
+ " which is greater than the current movie length of "
+ _log . Length ;
return false ;
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
+ "." ;
2014-04-06 19:01:00 +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
}
2014-04-06 19:01:00 +00:00
return false ;
2013-10-25 00:59:34 +00:00
}
2014-04-06 19:01:00 +00:00
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
#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
{
2014-04-06 19:01:00 +00:00
using ( var sw = new StreamWriter ( fs ) )
2013-10-25 00:59:34 +00:00
{
2014-04-06 19:01:00 +00:00
sw . Write ( Header . ToString ( ) ) ;
2013-10-25 00:59:34 +00:00
2014-04-06 19:01:00 +00:00
// TODO: clean this up
if ( _loopOffset . HasValue )
2013-10-25 00:59:34 +00:00
{
2014-04-06 19:01:00 +00:00
sw . WriteLine ( "LoopOffset " + _loopOffset ) ;
2013-10-25 00:59:34 +00:00
}
2014-04-06 19:01:00 +00:00
foreach ( var input in _log )
2013-10-25 00:59:34 +00:00
{
2014-04-06 19:01:00 +00:00
sw . WriteLine ( input ) ;
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
}
}