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-25 00:59:34 +00:00
IsText = true ;
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 ; }
public bool IsText { 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-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-16 21:05:59 +00:00
_mode = Moviemode . Play ;
2013-11-29 19:55:05 +00:00
Global . Emulator . ClearSaveRam ( ) ;
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-10-25 00:59:34 +00:00
if ( IsText )
{
WriteText ( Filename ) ;
}
else
{
WriteBinary ( Filename ) ;
}
}
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
if ( IsText )
{
2013-11-30 02:20:34 +00:00
WriteText ( backupName ) ;
2013-10-25 00:59:34 +00:00
}
else
{
2013-11-30 02:20:34 +00:00
WriteBinary ( 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
}
public void AppendFrame ( string record )
{
2013-10-27 22:13:08 +00:00
_log . AppendFrame ( record ) ;
_changes = true ;
2013-10-25 00:59:34 +00:00
}
public void TruncateMovie ( int frame )
{
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
public void PokeFrame ( int frameNum , string input )
{
2013-10-27 22:13:08 +00:00
_changes = true ;
_log . SetFrameAt ( frameNum , input ) ;
2013-10-25 00:59:34 +00:00
}
public void CommitFrame ( int frameNum , IController source )
{
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-11-30 02:20:34 +00:00
var mg = new MnemonicsGenerator ( ) ;
2013-10-25 00:59:34 +00:00
mg . SetSource ( source ) ;
2013-10-27 22:13:08 +00:00
_log . SetFrameAt ( frameNum , 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 ( ) ;
sb
. AppendLine ( "[Input]" )
. AppendLine ( HeaderKeys . GUID + " " + Header [ HeaderKeys . GUID ] ) ;
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-01 01:55:41 +00:00
public void ExtractInputLog ( TextReader reader , bool isMultitracking )
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-10-25 00:59:34 +00:00
if ( ! isMultitracking )
{
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-11-30 02:20:34 +00:00
catch { } // TODO: message?
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 { } //TODO: message?
}
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-11-30 02:20:34 +00:00
catch { } // TODO: message?
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-11-30 02:20:34 +00:00
catch { } // TODO: message?
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-10-25 00:59:34 +00:00
throw new Exception ( "Couldn't find stateFrame" ) ;
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-10-25 00:59:34 +00:00
}
public string GetTime ( bool preLoad )
{
2013-11-30 02:20:34 +00:00
var time = String . Empty ;
2013-10-25 00:59:34 +00:00
2013-11-30 02:20:34 +00:00
double seconds = GetSeconds ( preLoad ? _preloadFramecount : _log . Length ) ;
2013-10-25 00:59:34 +00:00
int hours = ( ( int ) seconds ) / 3600 ;
int minutes = ( ( ( int ) seconds ) / 60 ) % 60 ;
double sec = seconds % 60 ;
if ( hours > 0 )
{
time + = MakeDigits ( hours ) + ":" ;
}
time + = MakeDigits ( minutes ) + ":" ;
2013-11-30 02:20:34 +00:00
if ( sec < 10 ) // Kludge
2013-10-25 00:59:34 +00:00
{
time + = "0" ;
}
time + = Math . Round ( ( decimal ) sec , 2 ) . ToString ( ) ;
return time ;
}
2013-11-16 21:05:59 +00:00
public LoadStateResult CheckTimeLines ( TextReader reader , bool onlyGuid , bool ignoreGuidMismatch , 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 )
{
return LoadStateResult . EmptyLog ;
}
2013-11-30 02:20:34 +00:00
else if ( line . Trim ( ) = = string . Empty )
2013-10-25 00:59:34 +00:00
{
continue ;
}
else if ( line . Contains ( "GUID" ) )
{
2013-11-30 02:20:34 +00:00
var guid = line . Split ( new [ ] { ' ' } , 2 ) [ 1 ] ;
if ( Header [ HeaderKeys . GUID ] ! = guid )
2013-10-25 00:59:34 +00:00
{
2013-11-16 21:05:59 +00:00
if ( ! ignoreGuidMismatch )
2013-10-25 00:59:34 +00:00
{
return LoadStateResult . GuidMismatch ;
}
}
}
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-10-25 00:59:34 +00:00
return LoadStateResult . MissingFrameNumber ;
}
}
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-10-25 00:59:34 +00:00
return LoadStateResult . MissingFrameNumber ;
}
}
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-11-16 21:05:59 +00:00
if ( onlyGuid )
2013-10-25 00:59:34 +00:00
{
return LoadStateResult . Pass ;
}
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 )
{
return LoadStateResult . Pass ;
}
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-11-23 00:13:36 +00:00
return LoadStateResult . FutureEventError ;
}
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
+ "." ;
return LoadStateResult . TimeLineError ;
}
}
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-10-25 00:59:34 +00:00
return LoadStateResult . Pass ;
}
else
{
2013-11-30 02:20:34 +00:00
return LoadStateResult . NotInRecording ; // TODO: For now throw an error if recording, ideally what should happen is that the state gets loaded, and the movie set to movie finished, the movie at its current state is preserved and the state is loaded just fine. This should probably also only happen if checktimelines passes
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
}
return LoadStateResult . Pass ;
}
#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-10-27 22:13:08 +00:00
2013-10-25 00:59:34 +00:00
#endregion
#region Helpers
private void WriteText ( string fn )
{
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 WriteBinary ( string fn )
{
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
WriteBinary ( 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 void WriteBinary ( Stream stream )
{
}
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 ;
}
private bool LoadBinary ( )
{
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-11-30 02:20:34 +00:00
return frames / this . 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-11-30 02:20:34 +00:00
return FrameRates [ system , pal ] ;
2013-11-16 21:49:47 +00:00
}
}
2013-10-25 00:59:34 +00:00
#endregion
2013-11-16 21:05:59 +00:00
2013-11-30 02:20:34 +00:00
private readonly PlatformFrameRates _frameRates = new PlatformFrameRates ( ) ;
public PlatformFrameRates FrameRates
2013-11-16 21:05:59 +00:00
{
2013-11-30 02:20:34 +00:00
get { return _frameRates ; }
2013-11-16 21:05:59 +00:00
}
2013-10-25 00:59:34 +00:00
}
}