2013-10-25 00:59:34 +00:00
using System ;
using System.IO ;
using System.Globalization ;
namespace BizHawk.Client.Common
{
public class Movie
{
#region Constructors
public Movie ( string filename , string version )
: this ( version )
{
Rerecords = 0 ;
Filename = filename ;
Loaded = filename . Length > 0 ;
}
public Movie ( string version )
{
Header = new MovieHeader ( version ) ;
Filename = String . Empty ;
2013-10-27 22:13:08 +00:00
_preload_framecount = 0 ;
2013-10-25 00:59:34 +00:00
StartsFromSavestate = false ;
IsCountingRerecords = true ;
2013-10-27 22:13:08 +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
public MovieHeader Header ;
public SubtitleList Subtitles = new SubtitleList ( ) ;
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 ; }
public int Rerecords
{
2013-10-27 22:13:08 +00:00
get { return _rerecords ; }
2013-10-25 00:59:34 +00:00
set
{
2013-10-27 22:13:08 +00:00
_rerecords = value ;
2013-10-25 00:59:34 +00:00
Header . SetHeaderLine ( MovieHeader . RERECORDS , Rerecords . ToString ( ) ) ;
}
}
public string SysID
{
get { return Header . GetHeaderLine ( MovieHeader . PLATFORM ) ; }
}
public string GUID
{
get { return Header . GetHeaderLine ( MovieHeader . GUID ) ; }
}
public string GameName
{
get { return Header . GetHeaderLine ( MovieHeader . GAMENAME ) ; }
}
public int RawFrames
{
2013-10-28 00:44:01 +00:00
get { return Loaded ? _log . Length : _preload_framecount ; }
2013-10-25 00:59:34 +00:00
}
public int? Frames
{
get
{
if ( Loaded )
{
2013-10-27 22:13:08 +00:00
if ( _loopOffset . HasValue )
2013-10-25 00:59:34 +00:00
{
return null ;
}
else
{
2013-10-27 22:13:08 +00:00
return _log . Length ;
2013-10-25 00:59:34 +00:00
}
}
else
{
2013-10-27 22:13:08 +00:00
return _preload_framecount ;
2013-10-25 00:59:34 +00:00
}
}
}
public bool StartsFromSavestate
{
2013-10-27 22:13:08 +00:00
get { return _startsfromsavestate ; }
2013-10-25 00:59:34 +00:00
set
{
2013-10-27 22:13:08 +00:00
_startsfromsavestate = value ;
2013-10-25 00:59:34 +00:00
if ( value )
{
Header . AddHeaderLine ( MovieHeader . STARTSFROMSAVESTATE , "1" ) ;
}
else
{
Header . RemoveHeaderLine ( MovieHeader . STARTSFROMSAVESTATE ) ;
}
}
}
//TODO: these are getting too lengthy perhaps the log should just be exposed?
public int StateFirstIndex
{
2013-10-27 22:13:08 +00:00
get { return _log . StateFirstIndex ; }
2013-10-25 00:59:34 +00:00
}
public int StateLastIndex
{
2013-10-27 22:13:08 +00:00
get { return _log . StateLastIndex ; }
2013-10-25 00:59:34 +00:00
}
public bool StateCapturing
{
2013-10-27 22:13:08 +00:00
get { return _statecapturing ; }
2013-10-25 00:59:34 +00:00
set
{
2013-10-27 22:13:08 +00:00
_statecapturing = value ;
2013-10-25 00:59:34 +00:00
if ( value = = false )
{
2013-10-27 22:13:08 +00:00
_log . ClearStates ( ) ;
2013-10-25 00:59:34 +00:00
}
}
}
public byte [ ] GetState ( int frame )
{
2013-10-27 22:13:08 +00:00
return _log . GetState ( frame ) ;
2013-10-25 00:59:34 +00:00
}
public byte [ ] InitState
{
2013-10-27 22:13:08 +00:00
get { return _log . InitState ; }
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-10-27 22:13:08 +00:00
get { return _mode = = MOVIEMODE . PLAY | | _mode = = MOVIEMODE . FINISHED ; }
2013-10-25 00:59:34 +00:00
}
public bool IsRecording
{
2013-10-27 22:13:08 +00:00
get { return _mode = = MOVIEMODE . RECORD ; }
2013-10-25 00:59:34 +00:00
}
public bool IsActive
{
2013-10-27 22:13:08 +00:00
get { return _mode ! = MOVIEMODE . INACTIVE ; }
2013-10-25 00:59:34 +00:00
}
public bool IsFinished
{
2013-10-27 22:13:08 +00:00
get { return _mode = = MOVIEMODE . FINISHED ; }
2013-10-25 00:59:34 +00:00
}
public bool HasChanges
{
2013-10-27 22:13:08 +00:00
get { return _changes ; }
2013-10-25 00:59:34 +00:00
}
/// <summary>
/// Tells the movie to start recording from the beginning, this will clear sram, and the movie log
/// </summary>
/// <param name="truncate"></param>
public void StartRecording ( bool truncate = true )
{
2013-10-27 22:13:08 +00:00
_mode = MOVIEMODE . RECORD ;
if ( Global . Config . EnableBackupMovies & & MakeBackup & & _log . Length > 0 )
2013-10-25 00:59:34 +00:00
{
WriteBackup ( ) ;
MakeBackup = false ;
}
if ( truncate )
{
2013-10-27 22:13:08 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
}
}
public void StartPlayback ( )
{
2013-10-27 22:13:08 +00:00
_mode = MOVIEMODE . PLAY ;
2013-10-25 00:59:34 +00:00
}
/// <summary>
/// Tells the movie to recording mode
/// </summary>
public void SwitchToRecord ( )
{
2013-10-27 22:13:08 +00:00
_mode = MOVIEMODE . RECORD ;
2013-10-25 00:59:34 +00:00
}
/// <summary>
/// Tells the movie to go into playback mode
/// </summary>
public void SwitchToPlay ( )
{
2013-10-27 22:13:08 +00:00
_mode = MOVIEMODE . PLAY ;
2013-10-25 00:59:34 +00:00
WriteMovie ( ) ;
}
public void Stop ( bool abortchanges = false )
{
if ( ! abortchanges )
{
2013-10-27 22:13:08 +00:00
if ( _mode = = MOVIEMODE . RECORD | | _changes )
2013-10-25 00:59:34 +00:00
{
WriteMovie ( ) ;
}
}
2013-10-27 22:13:08 +00:00
_changes = false ;
_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-10-25 00:59:34 +00:00
public void Finish ( )
{
2013-10-27 22:13:08 +00:00
if ( _mode = = MOVIEMODE . PLAY )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_mode = MOVIEMODE . FINISHED ;
2013-10-25 00:59:34 +00:00
}
}
#endregion
#region Public File Handling
public void WriteMovie ( string path )
{
if ( ! Loaded )
{
return ;
}
var directory_info = new FileInfo ( Filename ) . Directory ;
if ( directory_info ! = null ) Directory . CreateDirectory ( directory_info . FullName ) ;
if ( IsText )
{
WriteText ( Filename ) ;
}
else
{
WriteBinary ( Filename ) ;
}
}
public void WriteMovie ( )
{
2013-10-28 00:44:01 +00:00
if ( ! Loaded | | String . IsNullOrWhiteSpace ( Filename ) )
2013-10-25 00:59:34 +00:00
{
return ;
}
WriteMovie ( Filename ) ;
2013-10-27 22:13:08 +00:00
_changes = false ;
2013-10-25 00:59:34 +00:00
}
public void WriteBackup ( )
{
2013-10-28 00:44:01 +00:00
if ( ! Loaded | | String . IsNullOrWhiteSpace ( Filename ) )
2013-10-25 00:59:34 +00:00
{
return ;
}
string BackupName = Filename ;
BackupName = BackupName . Insert ( Filename . LastIndexOf ( "." ) , String . Format ( ".{0:yyyy-MM-dd HH.mm.ss}" , DateTime . Now ) ) ;
2013-10-27 17:47:54 +00:00
BackupName = Path . Combine ( Global . Config . PathEntries [ "Global" , "Movie backups" ] . Path , Path . GetFileName ( BackupName ) ? ? String . Empty ) ;
2013-10-25 00:59:34 +00:00
var directory_info = new FileInfo ( BackupName ) . Directory ;
if ( directory_info ! = null ) Directory . CreateDirectory ( directory_info . FullName ) ;
if ( IsText )
{
WriteText ( BackupName ) ;
}
else
{
WriteBinary ( BackupName ) ;
}
}
/// <summary>
/// Load Header information only for displaying file information in dialogs such as play movie
/// </summary>
/// <returns></returns>
public bool PreLoadText ( )
{
Loaded = false ;
var file = new FileInfo ( Filename ) ;
if ( file . Exists = = false )
return false ;
else
{
Header . Clear ( ) ;
2013-10-27 22:13:08 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
}
using ( StreamReader sr = file . OpenText ( ) )
{
string str ;
while ( ( str = sr . ReadLine ( ) ) ! = null )
{
2013-10-28 00:44:01 +00:00
if ( String . IsNullOrWhiteSpace ( str ) | | Header . AddHeaderFromLine ( str ) )
2013-10-25 00:59:34 +00:00
{
continue ;
}
if ( str . StartsWith ( "subtitle" ) | | str . StartsWith ( "sub" ) )
{
Subtitles . AddSubtitle ( str ) ;
}
else if ( str [ 0 ] = = '|' )
{
string frames = sr . ReadToEnd ( ) ;
int length = str . Length ;
// Account for line breaks of either size.
if ( frames . IndexOf ( "\r\n" ) ! = - 1 )
{
length + + ;
}
length + + ;
// Count the remaining frames and the current one.
2013-10-27 22:13:08 +00:00
_preload_framecount = ( frames . Length / length ) + 1 ;
2013-10-25 00:59:34 +00:00
break ;
}
else
{
Header . Comments . Add ( str ) ;
}
}
}
return true ;
}
public bool LoadMovie ( )
{
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 )
{
int getframe ;
2013-10-27 22:13:08 +00:00
if ( _loopOffset . HasValue )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
if ( frame < _log . Length )
2013-10-25 00:59:34 +00:00
{
getframe = frame ;
}
else
{
2013-10-27 22:13:08 +00:00
getframe = ( ( frame - _loopOffset . Value ) % ( _log . Length - _loopOffset . Value ) ) + _loopOffset . Value ;
2013-10-25 00:59:34 +00:00
}
}
else
{
getframe = frame ;
}
2013-10-28 00:44:01 +00:00
return _log [ getframe ] ;
2013-10-25 00:59:34 +00:00
}
public void ModifyFrame ( string record , int frame )
{
2013-10-27 22:13:08 +00:00
_log . SetFrameAt ( frame , record ) ;
_changes = true ;
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 InsertFrame ( string record , int frame )
{
2013-10-27 22:13:08 +00:00
_log . AddFrameAt ( frame , record ) ;
_changes = true ;
2013-10-25 00:59:34 +00:00
}
public void InsertBlankFrame ( int frame )
{
2013-10-27 22:13:08 +00:00
_log . AddFrameAt ( frame , MnemonicsGenerator . GetEmptyMnemonic ) ;
_changes = true ;
2013-10-25 00:59:34 +00:00
}
public void DeleteFrame ( int frame )
{
2013-10-27 22:13:08 +00:00
_log . DeleteFrame ( frame ) ;
_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 MovieLog LogDump
{
2013-10-28 00:44:01 +00:00
get { return _log ; }
2013-10-25 00:59:34 +00:00
}
public bool FrameLagged ( int frame )
{
2013-10-27 22:13:08 +00:00
return _log . FrameLagged ( frame ) ;
2013-10-25 00:59:34 +00:00
}
public void CaptureState ( )
{
if ( StateCapturing )
{
2013-10-28 00:44:01 +00:00
_log . AddState ( Global . Emulator . SaveStateBinary ( ) ) ;
2013-10-25 00:59:34 +00:00
GC . Collect ( ) ;
}
}
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 )
{
//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"
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 ) ;
_log . TruncateStates ( Global . Emulator . Frame ) ;
2013-10-25 00:59:34 +00:00
}
}
2013-10-27 22:13:08 +00:00
_changes = true ;
2013-10-25 00:59:34 +00:00
MnemonicsGenerator mg = new MnemonicsGenerator ( ) ;
mg . SetSource ( source ) ;
2013-10-27 22:13:08 +00:00
_log . SetFrameAt ( frameNum , mg . GetControllersAsMnemonic ( ) ) ;
2013-10-25 00:59:34 +00:00
}
public void DumpLogIntoSavestateText ( TextWriter writer )
{
writer . WriteLine ( "[Input]" ) ;
2013-10-28 00:44:01 +00:00
writer . WriteLine ( MovieHeader . GUID + " " + Header . GetHeaderLine ( MovieHeader . GUID ) ) ;
2013-10-27 22:13:08 +00:00
for ( int x = 0 ; x < _log . Length ; x + + )
2013-10-25 00:59:34 +00:00
{
2013-10-28 00:44:01 +00:00
writer . WriteLine ( _log [ x ] ) ;
2013-10-25 00:59:34 +00:00
}
2013-10-28 00:44:01 +00:00
2013-10-25 00:59:34 +00:00
writer . WriteLine ( "[/Input]" ) ;
}
public void LoadLogFromSavestateText ( TextReader reader , bool isMultitracking )
{
int? stateFrame = null ;
//We are in record mode so replace the movie log with the one from the savestate
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
{
WriteBackup ( ) ;
MakeBackup = false ;
}
2013-10-27 22:13:08 +00:00
_log . Clear ( ) ;
2013-10-25 00:59:34 +00:00
while ( true )
{
string line = reader . ReadLine ( ) ;
if ( line = = null ) break ;
else if ( line . Trim ( ) = = "" ) continue ;
else if ( line = = "[Input]" ) continue ;
else if ( line = = "[/Input]" ) break ;
else if ( line . Contains ( "Frame 0x" ) ) //NES stores frame count in hex, yay
{
string [ ] strs = line . Split ( 'x' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
catch { } //TODO: message?
}
else if ( line . Contains ( "Frame " ) )
{
string [ ] strs = line . Split ( ' ' ) ;
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 )
{
string line = reader . ReadLine ( ) ;
if ( line = = null ) break ;
else if ( line . Trim ( ) = = "" ) continue ;
else if ( line = = "[Input]" ) continue ;
else if ( line = = "[/Input]" ) break ;
else if ( line . Contains ( "Frame 0x" ) ) //NES stores frame count in hex, yay
{
string [ ] strs = line . Split ( 'x' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
catch { } //TODO: message?
}
else if ( line . Contains ( "Frame " ) )
{
string [ ] strs = line . Split ( ' ' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
catch { } //TODO: message?
}
if ( line [ 0 ] = = '|' )
{
2013-10-27 22:13:08 +00:00
_log . SetFrameAt ( i , line ) ;
2013-10-25 00:59:34 +00:00
i + + ;
}
}
}
if ( stateFrame = = null )
throw new Exception ( "Couldn't find stateFrame" ) ;
int stateFramei = ( int ) stateFrame ;
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-10-27 22:13:08 +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-10-27 22:13:08 +00:00
_mode = MOVIEMODE . FINISHED ;
2013-10-25 00:59:34 +00:00
}
if ( IsCountingRerecords )
Rerecords + + ;
}
public string GetTime ( bool preLoad )
{
2013-10-28 00:44:01 +00:00
string time = String . Empty ;
2013-10-25 00:59:34 +00:00
double seconds ;
if ( preLoad )
{
2013-10-27 22:13:08 +00:00
seconds = GetSeconds ( _preload_framecount ) ;
2013-10-25 00:59:34 +00:00
}
else
{
2013-10-27 22:13:08 +00:00
seconds = GetSeconds ( _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 ) + ":" ;
if ( sec < 10 ) //Kludge
{
time + = "0" ;
}
time + = Math . Round ( ( decimal ) sec , 2 ) . ToString ( ) ;
return time ;
}
public enum LoadStateResult { Pass , GuidMismatch , TimeLineError , FutureEventError , NotInRecording , EmptyLog , MissingFrameNumber }
public LoadStateResult CheckTimeLines ( TextReader reader , bool OnlyGUID , bool IgnoreGuidMismatch , out string ErrorMessage )
{
//This function will compare the movie data to the savestate movie data to see if they match
ErrorMessage = String . Empty ;
var log = new MovieLog ( ) ;
int stateFrame = 0 ;
while ( true )
{
string line = reader . ReadLine ( ) ;
if ( line = = null )
{
return LoadStateResult . EmptyLog ;
}
else if ( line . Trim ( ) = = "" )
{
continue ;
}
else if ( line . Contains ( "GUID" ) )
{
string guid = ParseHeader ( line , MovieHeader . GUID ) ;
if ( Header . GetHeaderLine ( MovieHeader . GUID ) ! = guid )
{
if ( ! IgnoreGuidMismatch )
{
return LoadStateResult . GuidMismatch ;
}
}
}
else if ( line . Contains ( "Frame 0x" ) ) //NES stores frame count in hex, yay
{
string [ ] strs = line . Split ( 'x' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
catch
{
ErrorMessage = "Savestate Frame number failed to parse" ;
return LoadStateResult . MissingFrameNumber ;
}
}
else if ( line . Contains ( "Frame " ) )
{
string [ ] strs = line . Split ( ' ' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
catch
{
ErrorMessage = "Savestate Frame number failed to parse" ;
return LoadStateResult . MissingFrameNumber ;
}
}
else if ( line = = "[Input]" ) continue ;
else if ( line = = "[/Input]" ) break ;
else if ( line [ 0 ] = = '|' )
{
log . AppendFrame ( line ) ;
}
}
if ( OnlyGUID )
{
return LoadStateResult . Pass ;
}
if ( stateFrame = = 0 )
{
stateFrame = log . Length ; //In case the frame count failed to parse, revert to using the entire state input log
}
2013-10-27 22:13:08 +00:00
if ( _log . Length < stateFrame )
2013-10-25 00:59:34 +00:00
{
ErrorMessage = "The savestate is from frame "
+ log . Length . ToString ( )
+ " which is greater than the current movie length of "
2013-10-27 22:13:08 +00:00
+ _log . Length . ToString ( ) ;
2013-10-25 00:59:34 +00:00
return LoadStateResult . FutureEventError ;
}
for ( int i = 0 ; i < stateFrame ; i + + )
{
2013-10-28 00:44:01 +00:00
if ( _log [ i ] ! = log [ i ] )
2013-10-25 00:59:34 +00:00
{
ErrorMessage = "The savestate input does not match the movie input at frame "
+ ( i + 1 ) . ToString ( )
+ "." ;
return LoadStateResult . TimeLineError ;
}
}
if ( stateFrame > log . Length ) //stateFrame is greater than state input log, so movie finished mode
{
2013-10-27 22:13:08 +00:00
if ( _mode = = MOVIEMODE . PLAY | | _mode = = MOVIEMODE . FINISHED )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
_mode = MOVIEMODE . FINISHED ;
2013-10-25 00:59:34 +00:00
return LoadStateResult . Pass ;
}
else
{
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-27 22:13:08 +00:00
else if ( _mode = = MOVIEMODE . FINISHED )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +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-10-25 00:59:34 +00:00
private enum MOVIEMODE { INACTIVE , PLAY , RECORD , FINISHED } ;
2013-10-27 22:13:08 +00:00
private MOVIEMODE _mode = MOVIEMODE . INACTIVE ;
private bool _statecapturing ;
private bool _startsfromsavestate ;
private int _preload_framecount ; //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 int _rerecords ;
private bool _changes ;
private int? _loopOffset = null ;
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 ) )
WriteText ( fs ) ;
}
private void WriteBinary ( string fn )
{
using ( var fs = new FileStream ( fn , FileMode . Create , FileAccess . Write , FileShare . Read ) )
WriteBinary ( fs ) ;
}
private void WriteText ( Stream stream )
{
using ( StreamWriter sw = new StreamWriter ( stream ) )
{
Header . WriteText ( sw ) ;
//TODO: clean this up
2013-10-27 22:13:08 +00:00
if ( _loopOffset > = 0 )
2013-10-25 00:59:34 +00:00
{
2013-10-27 22:13:08 +00:00
sw . WriteLine ( "LoopOffset " + _loopOffset . ToString ( ) ) ;
2013-10-25 00:59:34 +00:00
}
Subtitles . WriteText ( sw ) ;
2013-10-27 22:13:08 +00:00
_log . WriteText ( sw ) ;
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
}
using ( StreamReader sr = file . OpenText ( ) )
{
string str ;
while ( ( str = sr . ReadLine ( ) ) ! = null )
{
if ( str = = "" )
{
continue ;
}
if ( str . Contains ( MovieHeader . RERECORDS ) )
{
string rerecordStr = ParseHeader ( str , MovieHeader . RERECORDS ) ;
try
{
Rerecords = int . Parse ( rerecordStr ) ;
}
catch
{
Rerecords = 0 ;
}
}
else if ( str . Contains ( MovieHeader . STARTSFROMSAVESTATE ) )
{
str = ParseHeader ( str , MovieHeader . STARTSFROMSAVESTATE ) ;
if ( str = = "1" )
StartsFromSavestate = true ;
}
else if ( str . Contains ( "LoopOffset" ) )
{
str = ParseHeader ( str , "LoopOffset" ) ;
try
{
2013-10-27 22:13:08 +00:00
_loopOffset = int . Parse ( str ) ;
2013-10-25 00:59:34 +00:00
}
catch
{
//Do nothing
}
}
else if ( str . StartsWith ( "subtitle" ) | | str . StartsWith ( "sub" ) )
{
Subtitles . AddSubtitle ( str ) ;
}
else if ( Header . AddHeaderFromLine ( str ) )
{
continue ;
}
else if ( str [ 0 ] = = '|' )
{
2013-10-27 22:13:08 +00:00
_log . AppendFrame ( str ) ;
2013-10-25 00:59:34 +00:00
}
else
{
Header . Comments . Add ( str ) ;
}
}
}
Loaded = true ;
return true ;
}
private bool LoadBinary ( )
{
return true ;
}
private string MakeDigits ( int num )
{
if ( num < 10 )
{
return "0" + num . ToString ( ) ;
}
else
{
return num . ToString ( ) ;
}
}
private double GetSeconds ( int frameCount )
{
const double NES_PAL = 50.006977968268290849 ;
const double NES_NTSC = 60.098813897440515532 ;
const double SNES_NTSC = ( double ) 21477272 / ( 4 * 341 * 262 ) ;
const double SNES_PAL = ( double ) 21281370 / ( 4 * 341 * 312 ) ;
const double PCE = ( 7159090.90909090 / 455 / 263 ) ; //~59.826
const double SMS_NTSC = ( 3579545 / 262.0 / 228.0 ) ;
const double SMS_PAL = ( 3546893 / 313.0 / 228.0 ) ;
const double NGP = ( 6144000.0 / ( 515 * 198 ) ) ;
const double VBOY = ( 20000000 / ( 259 * 384 * 4 ) ) ; //~50.273
const double LYNX = 59.8 ;
const double WSWAN = ( 3072000.0 / ( 159 * 256 ) ) ;
const double GB = 262144.0 / 4389.0 ;
const double A26 = 59.9227510135505 ;
double frames = frameCount ;
if ( frames < 1 )
{
return 0 ;
}
bool pal = false ;
if ( Header . HeaderParams . ContainsKey ( MovieHeader . PAL ) )
if ( Header . HeaderParams [ MovieHeader . PAL ] = = "1" )
pal = true ;
switch ( Header . GetHeaderLine ( MovieHeader . PLATFORM ) )
{
case "GG" :
case "SG" :
case "SMS" :
if ( pal )
return frames / SMS_PAL ;
else
return frames / SMS_NTSC ;
case "FDS" :
case "NES" :
if ( pal )
return frames / NES_PAL ;
else
return frames / NES_NTSC ;
case "SNES" :
case "SGB" :
if ( pal )
return frames / SNES_PAL ;
else
return frames / SNES_NTSC ;
case "PCE" :
case "PCECD" :
return frames / PCE ;
case "GB" :
case "GBC" :
case "GBA" :
return frames / GB ;
case "A26" :
case "A78" :
case "Coleco" :
return frames / A26 ;
//One Day!
case "VBOY" :
return frames / VBOY ;
case "NGP" :
return frames / NGP ;
case "LYNX" :
return frames / LYNX ;
case "WSWAN" :
return frames / WSWAN ;
//********
case "" :
default :
if ( pal )
return frames / 50.0 ;
else
return frames / 60.0 ;
}
}
private static string ParseHeader ( string line , string headerName )
{
int x = line . LastIndexOf ( headerName ) + headerName . Length ;
return line . Substring ( x + 1 , line . Length - x - 1 ) ;
}
#endregion
}
}