2011-02-24 22:25:53 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2011-02-25 19:49:29 +00:00
using System.IO ;
2011-06-26 14:36:41 +00:00
using System.Drawing ;
2011-07-31 16:41:27 +00:00
using System.Windows.Forms ;
2011-09-07 00:40:42 +00:00
using System.Globalization ;
2011-02-24 22:25:53 +00:00
namespace BizHawk.MultiClient
{
2011-06-12 14:42:50 +00:00
public class Movie
{
2012-09-03 15:05:09 +00:00
#region Constructors
2011-07-13 01:29:13 +00:00
2012-09-03 19:42:53 +00:00
public Movie ( string filename )
2011-06-12 14:42:50 +00:00
{
2012-09-03 19:42:53 +00:00
Mode = MOVIEMODE . INACTIVE ;
2012-09-03 15:05:09 +00:00
lastlog = 0 ;
2011-07-17 17:52:39 +00:00
Rerecords = 0 ;
2011-07-13 01:45:40 +00:00
this . Filename = filename ;
2011-07-17 14:39:15 +00:00
IsText = true ;
2012-09-03 19:42:53 +00:00
preload_framecount = 0 ;
IsCountingRerecords = true ;
2011-07-30 20:49:36 +00:00
StartsFromSavestate = false ;
2011-07-31 01:15:14 +00:00
if ( filename . Length > 0 )
Loaded = true ;
2011-06-12 14:42:50 +00:00
}
public Movie ( )
{
2011-07-30 23:59:31 +00:00
Filename = "" ;
2011-07-17 14:39:15 +00:00
Mode = MOVIEMODE . INACTIVE ;
IsText = true ;
2012-09-03 19:42:53 +00:00
preload_framecount = 0 ;
2011-07-30 20:49:36 +00:00
StartsFromSavestate = false ;
2011-07-30 23:59:31 +00:00
Loaded = false ;
2012-09-03 19:42:53 +00:00
IsCountingRerecords = true ;
2011-06-12 14:42:50 +00:00
}
2012-09-03 15:05:09 +00:00
#endregion
#region Properties
public MovieHeader Header = new MovieHeader ( ) ;
public SubtitleList Subtitles = new SubtitleList ( ) ;
2012-09-03 19:42:53 +00:00
2012-09-03 15:05:09 +00:00
public bool MakeBackup = true ; //make backup before altering movie
2012-09-03 19:42:53 +00:00
public string Filename ;
public bool IsCountingRerecords ;
2012-09-03 15:05:09 +00:00
public bool Loaded { get ; private set ; }
2012-09-03 19:42:53 +00:00
public bool IsText { get ; private set ; }
public int Rerecords
{
get
{
return rerecords ;
}
set
{
rerecords = value ;
Header . SetHeaderLine ( MovieHeader . RERECORDS , Rerecords . ToString ( ) ) ;
}
}
2012-09-03 15:05:09 +00:00
public string SysID
2011-06-12 14:42:50 +00:00
{
2012-09-03 15:05:09 +00:00
get
{
return Header . GetHeaderLine ( MovieHeader . PLATFORM ) ;
}
2011-06-12 14:42:50 +00:00
}
2012-09-03 15:05:09 +00:00
public string GUID
2011-07-31 01:15:14 +00:00
{
2012-09-03 15:05:09 +00:00
get
{
return Header . GetHeaderLine ( MovieHeader . GUID ) ;
}
2011-07-31 01:15:14 +00:00
}
2012-09-03 15:05:09 +00:00
public string GameName
2011-06-12 14:42:50 +00:00
{
2012-09-03 15:05:09 +00:00
get
{
return Header . GetHeaderLine ( MovieHeader . GAMENAME ) ;
}
2011-06-12 14:42:50 +00:00
}
-Began working on the importer.
--Created ImportFile to decide what function to use for each filetype.
---It currently automatically writes to a .TAS file, but that option will eventually only be applied when specified in the GUI, hopefully completely external from this class.
--Made IsValidMovieExtension work.
--Created LoadText to do the majority of the work that both .FM2 and .MC2 need to be done.
--.MC2 seems to work perfectly, not that it was a hard conversion!
--.FM2 seems to convert most headers correct, except for subtitles, which replaces the beginning portions of each subtitle's text with 0 0 120 4294967295. Not sure what that's about, though this sure feels like deja vu...
--I still need to switch around the order of the buttons the frames are added, but I need to find out what way I can do this without reinventing the wheel.
-Added the FixMnemonic function to Movie.cs. It currently does nothing, but my goal is to have it correct the mnemonic for all frames in a movie file based on the position of the characters.
--As of right now, ImportFile uses this.
-MainForm.IsValidMovieExtension only checks whether or not its .TAS or not now.
TODO:
-Fix the FM2 subtitles.
-Shift around the FM2 buttons.
--After completed, test a .FM2 file that should sync and see if it works, with and without FixMnemonic being used.
-Make FixMnemonic actually do something.
-Refactor code? I originally thought it'd be best to treat Movie.LoadText just like any other importer, but I think at this point it might just be best to keep these things completely separate.
-Consider the possibility of working with the binary file importers.
--Yes adelikat, I am somewhat interested, especially considering how useful it would be to have a working .FCM importer so I can compare old runs when TASing. I already was hoping to learn about .VBM and .SMV for my ButtonCount.lua script. By the way, might this be bundled with bizhawk as it is with FCEUX 2.1.6? :)
2012-02-15 06:54:09 +00:00
2012-09-03 19:42:53 +00:00
public int Frames
2012-06-07 04:47:54 +00:00
{
2012-09-03 15:05:09 +00:00
get
{
if ( Loaded )
{
return Log . MovieLength ( ) ;
}
else
{
2012-09-03 19:42:53 +00:00
return preload_framecount ;
}
}
}
public bool StartsFromSavestate
{
get
{
return startsfromsavestate ;
}
set
{
startsfromsavestate = value ;
if ( value = = true )
{
Header . AddHeaderLine ( MovieHeader . STARTSFROMSAVESTATE , "1" ) ;
}
else
{
Header . RemoveHeaderLine ( MovieHeader . STARTSFROMSAVESTATE ) ;
2012-09-03 15:05:09 +00:00
}
}
2012-06-07 04:47:54 +00:00
}
2012-09-03 15:05:09 +00:00
public int StateFirstIndex
{
get
{
return Log . StateFirstIndex ( ) ;
}
}
public int StateLastIndex
{
get
{
return Log . StateLastIndex ( ) ;
}
}
2012-09-03 19:42:53 +00:00
public bool StateCapturing
2012-08-18 00:04:12 +00:00
{
2012-09-03 19:42:53 +00:00
get
2012-09-03 15:05:09 +00:00
{
2012-09-03 19:42:53 +00:00
return statecapturing ;
2012-09-03 15:05:09 +00:00
}
2012-09-03 19:42:53 +00:00
set
2012-06-03 04:04:13 +00:00
{
2012-09-03 19:42:53 +00:00
Log . ClearStates ( ) ;
statecapturing = value ;
2012-06-03 04:04:13 +00:00
}
}
2012-09-03 19:42:53 +00:00
#endregion
2012-06-01 05:49:26 +00:00
2012-09-03 19:42:53 +00:00
#region Public Mode Methods
public bool IsPlaying
2012-06-01 05:49:26 +00:00
{
2012-09-03 19:42:53 +00:00
get
2012-06-01 05:49:26 +00:00
{
2012-09-03 19:42:53 +00:00
if ( Mode = = MOVIEMODE . PLAY | | Mode = = MOVIEMODE . FINISHED )
2012-06-18 01:36:38 +00:00
{
2012-09-03 19:42:53 +00:00
return true ;
2012-06-18 01:36:38 +00:00
}
else
{
2012-09-03 19:42:53 +00:00
return false ;
2012-06-18 01:36:38 +00:00
}
}
2012-09-03 19:42:53 +00:00
}
public bool IsRecording
{
get
2012-06-18 01:36:38 +00:00
{
2012-09-03 19:42:53 +00:00
if ( Mode = = MOVIEMODE . RECORD )
{
return true ;
}
else
{
return false ;
}
2012-06-01 05:49:26 +00:00
}
}
2012-09-03 19:42:53 +00:00
public bool IsActive
2012-06-01 05:49:26 +00:00
{
2012-09-03 19:42:53 +00:00
get
2012-06-18 01:36:38 +00:00
{
2012-09-03 19:42:53 +00:00
if ( Mode = = MOVIEMODE . INACTIVE )
2012-06-18 01:36:38 +00:00
{
2012-09-03 19:42:53 +00:00
return false ;
2012-06-18 01:36:38 +00:00
}
else
{
2012-09-03 19:42:53 +00:00
return true ;
2012-06-18 01:36:38 +00:00
}
}
2012-06-01 05:49:26 +00:00
}
2012-09-03 19:42:53 +00:00
public bool IsFinished
2011-09-17 14:23:23 +00:00
{
2012-09-03 19:42:53 +00:00
get
{
if ( Mode = = MOVIEMODE . FINISHED )
{
return true ;
}
else
{
return false ;
}
}
2011-09-17 14:23:23 +00:00
}
2012-09-03 19:42:53 +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>
2012-09-03 20:17:57 +00:00
public void StartRecording ( bool truncate = true )
2011-06-12 14:42:50 +00:00
{
2012-09-03 19:42:53 +00:00
Global . MainForm . ClearSaveRAM ( ) ;
2011-07-17 14:39:15 +00:00
Mode = MOVIEMODE . RECORD ;
2012-06-07 04:47:54 +00:00
if ( Global . Config . EnableBackupMovies & & MakeBackup & & Log . MovieLength ( ) > 0 )
2011-06-18 19:16:05 +00:00
{
WriteBackup ( ) ;
MakeBackup = false ;
}
2012-09-03 19:42:53 +00:00
if ( truncate )
{
Log . Clear ( ) ;
}
2011-06-12 14:42:50 +00:00
}
public void StartPlayback ( )
{
2012-09-03 19:42:53 +00:00
Global . MainForm . ClearSaveRAM ( ) ;
2011-07-17 14:39:15 +00:00
Mode = MOVIEMODE . PLAY ;
2011-06-12 14:42:50 +00:00
}
2012-09-03 19:42:53 +00:00
/// <summary>
/// Tells the movie to recording mode
/// </summary>
2012-09-03 20:17:57 +00:00
public void SwitchToRecord ( )
2011-09-07 01:18:58 +00:00
{
Mode = MOVIEMODE . RECORD ;
}
2012-09-03 20:17:57 +00:00
2012-09-03 19:42:53 +00:00
/// <summary>
/// Tells the movie to go into playback mode
/// </summary>
2012-09-03 20:17:57 +00:00
public void SwitchToPlay ( )
2012-08-17 02:26:47 +00:00
{
2012-09-03 19:42:53 +00:00
Mode = MOVIEMODE . PLAY ;
2012-08-17 02:26:47 +00:00
}
2012-09-03 19:42:53 +00:00
public void Stop ( )
2012-08-17 02:26:47 +00:00
{
2012-09-03 19:42:53 +00:00
if ( Mode = = MOVIEMODE . RECORD )
{
WriteMovie ( ) ;
}
2012-08-17 02:26:47 +00:00
2012-09-03 19:42:53 +00:00
Mode = MOVIEMODE . INACTIVE ;
2011-06-12 14:42:50 +00:00
}
2012-09-03 19:42:53 +00:00
public void Finish ( )
2011-06-12 14:42:50 +00:00
{
2012-09-03 19:42:53 +00:00
if ( Mode = = MOVIEMODE . PLAY )
{
Mode = MOVIEMODE . FINISHED ;
}
2012-08-17 02:18:25 +00:00
}
2012-09-03 19:42:53 +00:00
#endregion
2012-06-01 05:49:26 +00:00
2012-09-03 19:42:53 +00:00
#region Public File Handling
2011-06-12 14:42:50 +00:00
public void WriteMovie ( )
{
2011-07-30 23:59:31 +00:00
if ( ! Loaded ) return ;
2011-07-13 01:29:13 +00:00
if ( Filename = = "" ) return ;
2011-05-22 17:41:22 +00:00
Directory . CreateDirectory ( new FileInfo ( Filename ) . Directory . FullName ) ;
2011-06-12 14:42:50 +00:00
if ( IsText )
2011-06-18 18:27:51 +00:00
WriteText ( Filename ) ;
2011-06-12 14:42:50 +00:00
else
2011-06-18 18:27:51 +00:00
WriteBinary ( Filename ) ;
2011-06-12 14:42:50 +00:00
}
2011-06-18 18:27:51 +00:00
public void WriteBackup ( )
2011-06-12 14:42:50 +00:00
{
2011-07-30 23:59:31 +00:00
if ( ! Loaded ) return ;
2011-07-13 01:29:13 +00:00
if ( Filename = = "" ) return ;
2011-06-18 18:27:51 +00:00
Directory . CreateDirectory ( new FileInfo ( Filename ) . Directory . FullName ) ;
string BackupName = Filename ;
BackupName = BackupName . Insert ( Filename . LastIndexOf ( "." ) , String . Format ( ".{0:yyyy-MM-dd HH.mm.ss}" , DateTime . Now ) ) ;
2012-04-16 08:18:41 +00:00
Global . OSD . AddMessage ( "Backup movie saved to " + BackupName ) ;
2011-06-18 18:27:51 +00:00
if ( IsText )
2012-09-03 19:42:53 +00:00
{
2011-06-18 18:27:51 +00:00
WriteText ( BackupName ) ;
2012-09-03 19:42:53 +00:00
}
2011-06-18 18:27:51 +00:00
else
2012-09-03 19:42:53 +00:00
{
2011-06-18 18:27:51 +00:00
WriteBinary ( BackupName ) ;
2012-09-03 19:42:53 +00:00
}
2011-06-18 18:27:51 +00:00
}
2011-07-05 23:33:13 +00:00
/// <summary>
/// Load Header information only for displaying file information in dialogs such as play movie
/// </summary>
/// <returns></returns>
2011-06-12 14:42:50 +00:00
public bool PreLoadText ( )
{
2011-07-30 23:59:31 +00:00
Loaded = false ;
2011-06-12 14:42:50 +00:00
var file = new FileInfo ( Filename ) ;
if ( file . Exists = = false )
return false ;
else
{
Header . Clear ( ) ;
Log . Clear ( ) ;
}
using ( StreamReader sr = file . OpenText ( ) )
{
string str = "" ;
while ( ( str = sr . ReadLine ( ) ) ! = null )
{
2012-07-25 08:01:02 +00:00
if ( str = = "" | | Header . AddHeaderFromLine ( str ) )
2011-07-30 20:49:36 +00:00
continue ;
2011-07-30 23:59:31 +00:00
if ( str . StartsWith ( "subtitle" ) | | str . StartsWith ( "sub" ) )
2011-06-26 21:11:12 +00:00
Subtitles . AddSubtitle ( str ) ;
2011-06-12 14:42:50 +00:00
else if ( str [ 0 ] = = '|' )
{
2012-07-25 08:01:02 +00:00
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.
2012-09-03 19:42:53 +00:00
this . preload_framecount = ( frames . Length / length ) + 1 ;
2011-06-12 14:42:50 +00:00
break ;
}
else
Header . Comments . Add ( str ) ;
}
sr . Close ( ) ;
}
return true ;
2011-07-05 23:33:13 +00:00
}
2011-06-12 14:42:50 +00:00
public bool LoadMovie ( )
{
var file = new FileInfo ( Filename ) ;
2011-07-30 23:59:31 +00:00
if ( file . Exists = = false )
{
Loaded = false ;
return false ;
}
2011-06-12 14:42:50 +00:00
return LoadText ( ) ;
}
2012-09-03 19:42:53 +00:00
#endregion
#region Public Log Editing
public string GetInput ( int frame )
{
lastlog = frame ;
if ( frame < Log . MovieLength ( ) )
{
return Log . GetFrame ( frame ) ;
}
else
{
return "" ;
}
}
public void ModifyFrame ( string record , int frame )
{
Log . SetFrameAt ( frame , record ) ;
}
public void ClearFrame ( int frame )
{
MnemonicsGenerator mg = new MnemonicsGenerator ( ) ;
Log . SetFrameAt ( frame , mg . GetEmptyMnemonic ( ) ) ;
}
public void AppendFrame ( string record )
{
Log . AddFrame ( record ) ;
}
public void InsertFrame ( string record , int frame )
{
Log . AddFrameAt ( record , frame ) ;
}
public void InsertBlankFrame ( int frame )
{
MnemonicsGenerator mg = new MnemonicsGenerator ( ) ;
Log . AddFrameAt ( mg . GetEmptyMnemonic ( ) , frame ) ;
}
public void DeleteFrame ( int frame )
{
if ( frame < = StateLastIndex )
{
if ( frame < = StateFirstIndex )
{
RewindToFrame ( 0 ) ;
}
else
{
RewindToFrame ( frame ) ;
}
}
Log . DeleteFrame ( frame ) ;
}
public void TruncateMovie ( int frame )
{
Log . TruncateMovie ( frame ) ;
}
#endregion
#region Public Misc Methods
public void CaptureState ( )
{
if ( StateCapturing = = true )
{
byte [ ] state = Global . Emulator . SaveStateBinary ( ) ;
Log . AddState ( state ) ;
}
}
public void RewindToFrame ( int frame )
{
if ( Mode = = MOVIEMODE . INACTIVE | | Mode = = MOVIEMODE . FINISHED )
{
return ;
}
if ( frame < = Global . Emulator . Frame )
{
if ( frame < = Log . StateFirstIndex ( ) )
{
Global . Emulator . LoadStateBinary ( new BinaryReader ( new MemoryStream ( Log . GetInitState ( ) ) ) ) ;
2012-09-03 20:17:57 +00:00
if ( Global . MainForm . EmulatorPaused = = true & & frame > 0 )
2012-09-03 19:42:53 +00:00
{
Global . MainForm . UnpauseEmulator ( ) ;
}
if ( MOVIEMODE . RECORD = = Mode )
{
Mode = MOVIEMODE . PLAY ;
Global . MainForm . RestoreReadWriteOnStop = true ;
}
}
else
{
if ( frame = = 0 )
{
Global . Emulator . LoadStateBinary ( new BinaryReader ( new MemoryStream ( Log . GetInitState ( ) ) ) ) ;
}
else
{
//frame-1 because we need to go back an extra frame and then run a frame, otherwise the display doesn't get updated.
if ( frame - 1 < Log . StateCount )
{
Global . Emulator . LoadStateBinary ( new BinaryReader ( new MemoryStream ( Log . GetState ( frame - 1 ) ) ) ) ;
Global . MainForm . UpdateFrame = true ;
}
}
}
}
else
{
Global . MainForm . UnpauseEmulator ( ) ;
}
}
public void CommitFrame ( int frameNum , IController source )
{
//if (Global.Emulator.Frame < Log.Length())
//{
// Log.Truncate(Global.Emulator.Frame);
//}
//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"
MnemonicsGenerator mg = new MnemonicsGenerator ( ) ;
mg . SetSource ( source ) ;
Log . SetFrameAt ( frameNum , mg . GetControllersAsMnemonic ( ) ) ;
}
2011-06-12 14:42:50 +00:00
public void DumpLogIntoSavestateText ( TextWriter writer )
{
writer . WriteLine ( "[Input]" ) ;
2011-07-31 01:15:14 +00:00
string s = MovieHeader . GUID + " " + Header . GetHeaderLine ( MovieHeader . GUID ) ;
writer . WriteLine ( s ) ;
2012-06-07 04:47:54 +00:00
for ( int x = 0 ; x < Log . MovieLength ( ) ; x + + )
2011-06-12 14:42:50 +00:00
writer . WriteLine ( Log . GetFrame ( x ) ) ;
writer . WriteLine ( "[/Input]" ) ;
}
2011-08-20 19:27:00 +00:00
public void LoadLogFromSavestateText ( string path )
2011-06-12 14:42:50 +00:00
{
2011-08-20 19:27:00 +00:00
var reader = new StreamReader ( path ) ;
2011-09-07 01:18:58 +00:00
int stateFrame = 0 ;
2012-02-19 07:09:24 +00:00
//We are in record mode so replace the movie log with the one from the savestate
2011-07-24 23:14:16 +00:00
if ( ! Global . MovieSession . MultiTrack . IsActive )
2011-06-19 20:50:46 +00:00
{
2012-06-07 04:47:54 +00:00
if ( Global . Config . EnableBackupMovies & & MakeBackup & & Log . MovieLength ( ) > 0 )
2011-06-18 19:16:05 +00:00
{
WriteBackup ( ) ;
MakeBackup = false ;
}
Log . Clear ( ) ;
2011-06-19 20:50:46 +00:00
while ( true )
{
string line = reader . ReadLine ( ) ;
2011-09-07 00:40:42 +00:00
if ( line . Contains ( ".[NES" ) ) //TODO: Remove debug
2011-08-20 19:27:00 +00:00
{
2011-09-07 00:40:42 +00:00
MessageBox . Show ( "OOPS! Corrupted file stream" ) ;
2011-08-20 19:27:00 +00:00
}
2011-06-19 20:50:46 +00:00
if ( line = = null ) break ;
2011-09-07 01:18:58 +00:00
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
{
2011-09-10 02:08:16 +00:00
string [ ] strs = line . Split ( 'x' ) ;
2011-09-07 01:18:58 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
2012-04-16 08:18:41 +00:00
catch { Global . OSD . AddMessage ( "Savestate Frame failed to parse" ) ; } //TODO: message?
2011-09-07 01:18:58 +00:00
}
else if ( line . Contains ( "Frame " ) )
{
string [ ] strs = line . Split ( ' ' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
2012-04-16 08:18:41 +00:00
catch { Global . OSD . AddMessage ( "Savestate Frame failed to parse" ) ; } //TODO: message?
2011-09-07 01:18:58 +00:00
}
2011-06-19 20:50:46 +00:00
if ( line [ 0 ] = = '|' )
2011-09-07 00:40:42 +00:00
{
2011-06-19 20:50:46 +00:00
Log . AddFrame ( line ) ;
2011-09-07 00:40:42 +00:00
}
2011-06-19 20:50:46 +00:00
}
}
else
{
int i = 0 ;
while ( true )
{
string line = reader . ReadLine ( ) ;
if ( line = = null ) break ;
2011-09-07 01:18:58 +00:00
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 ( ' ' ) ;
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?
}
2011-06-19 20:50:46 +00:00
if ( line [ 0 ] = = '|' )
{
2011-07-30 20:49:36 +00:00
Log . SetFrameAt ( i , line ) ;
2011-06-19 20:50:46 +00:00
i + + ;
}
}
}
2012-06-07 04:47:54 +00:00
if ( stateFrame > 0 & & stateFrame < Log . MovieLength ( ) )
2011-07-05 23:33:13 +00:00
{
2012-08-25 22:18:54 +00:00
Log . TruncateStates ( stateFrame ) ;
Log . TruncateMovie ( stateFrame ) ;
2011-07-05 23:33:13 +00:00
}
2012-09-03 19:42:53 +00:00
Rerecords + + ;
2011-08-20 19:27:00 +00:00
reader . Close ( ) ;
2011-06-12 14:42:50 +00:00
}
public string GetTime ( bool preLoad )
{
string time = "" ;
double seconds ;
if ( preLoad )
2012-09-03 15:05:09 +00:00
{
2012-09-03 19:42:53 +00:00
seconds = GetSeconds ( preload_framecount ) ;
2012-09-03 15:05:09 +00:00
}
2011-06-12 14:42:50 +00:00
else
2012-09-03 15:05:09 +00:00
{
2012-06-07 04:47:54 +00:00
seconds = GetSeconds ( Log . MovieLength ( ) ) ;
2012-09-03 15:05:09 +00:00
}
2011-06-12 14:42:50 +00:00
int hours = ( ( int ) seconds ) / 3600 ;
int minutes = ( ( ( int ) seconds ) / 60 ) % 60 ;
double sec = seconds % 60 ;
if ( hours > 0 )
2012-09-03 15:05:09 +00:00
{
2011-06-12 14:42:50 +00:00
time + = MakeDigits ( hours ) + ":" ;
2012-09-03 15:05:09 +00:00
}
2011-06-12 14:42:50 +00:00
time + = MakeDigits ( minutes ) + ":" ;
time + = Math . Round ( ( decimal ) sec , 2 ) . ToString ( ) ;
2012-09-03 15:05:09 +00:00
2011-06-12 14:42:50 +00:00
return time ;
}
2011-08-20 19:27:00 +00:00
public bool CheckTimeLines ( string path , bool OnlyGUID )
2011-06-12 14:42:50 +00:00
{
//This function will compare the movie data to the savestate movie data to see if they match
2011-08-20 19:27:00 +00:00
var reader = new StreamReader ( path ) ;
2012-04-05 01:42:24 +00:00
2011-06-12 14:42:50 +00:00
MovieLog l = new MovieLog ( ) ;
string line ;
2011-07-31 16:41:27 +00:00
string GUID ;
2011-09-07 00:40:42 +00:00
int stateFrame = 0 ;
2011-06-12 14:42:50 +00:00
while ( true )
{
line = reader . ReadLine ( ) ;
2012-04-22 13:38:12 +00:00
if ( line = = null )
return false ;
2011-06-12 14:42:50 +00:00
if ( line . Trim ( ) = = "" ) continue ;
2011-07-31 16:41:27 +00:00
else if ( line . Contains ( "GUID" ) )
{
GUID = ParseHeader ( line , MovieHeader . GUID ) ;
if ( Header . GetHeaderLine ( MovieHeader . GUID ) ! = GUID )
{
//GUID Mismatch error
2012-02-19 07:09:24 +00:00
var result = MessageBox . Show ( GUID + " : " + Header . GetHeaderLine ( MovieHeader . GUID ) + "\n" +
2011-07-31 16:41:27 +00:00
"The savestate GUID does not match the current movie. Proceed anyway?" , "GUID Mismatch error" ,
MessageBoxButtons . YesNo , MessageBoxIcon . Question ) ;
if ( result = = DialogResult . No )
2011-08-20 19:27:00 +00:00
{
reader . Close ( ) ;
2011-07-31 16:41:27 +00:00
return false ;
2011-08-20 19:27:00 +00:00
}
2011-07-31 16:41:27 +00:00
}
2011-07-31 17:35:02 +00:00
else if ( OnlyGUID )
{
2011-08-20 19:27:00 +00:00
reader . Close ( ) ;
2011-07-31 17:35:02 +00:00
return true ;
}
2011-07-31 16:41:27 +00:00
}
2011-09-07 00:40:42 +00:00
else if ( line . Contains ( "Frame 0x" ) ) //NES stores frame count in hex, yay
{
2011-09-10 02:08:16 +00:00
string [ ] strs = line . Split ( 'x' ) ;
2011-09-07 00:40:42 +00:00
try
{
stateFrame = int . Parse ( strs [ 1 ] , NumberStyles . HexNumber ) ;
}
2012-04-16 08:18:41 +00:00
catch { Global . OSD . AddMessage ( "Savestate Frame number failed to parse" ) ; }
2011-09-07 00:40:42 +00:00
}
else if ( line . Contains ( "Frame " ) )
{
string [ ] strs = line . Split ( ' ' ) ;
try
{
stateFrame = int . Parse ( strs [ 1 ] ) ;
}
2012-04-16 08:18:41 +00:00
catch { Global . OSD . AddMessage ( "Savestate Frame number failed to parse" ) ; }
2011-09-07 00:40:42 +00:00
}
2011-06-12 14:42:50 +00:00
else if ( line = = "[Input]" ) continue ;
else if ( line = = "[/Input]" ) break ;
else if ( line [ 0 ] = = '|' )
l . AddFrame ( line ) ;
}
2011-07-31 16:41:27 +00:00
reader . BaseStream . Position = 0 ; //Reset position because this stream may be read again by other code
2011-08-20 19:27:00 +00:00
if ( OnlyGUID )
{
reader . Close ( ) ;
return true ;
}
2011-07-31 17:35:02 +00:00
2012-06-07 04:47:54 +00:00
if ( stateFrame > l . MovieLength ( ) ) //stateFrame is greater than state input log, so movie finished mode
2011-09-29 01:46:35 +00:00
{
if ( Mode = = MOVIEMODE . PLAY | | Mode = = MOVIEMODE . FINISHED )
{
Mode = MOVIEMODE . FINISHED ;
return true ;
}
else
return false ; //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
}
2012-08-25 23:31:54 +00:00
else if ( Mode = = MOVIEMODE . FINISHED )
{
Mode = MOVIEMODE . PLAY ;
}
2011-09-29 01:46:35 +00:00
if ( stateFrame = = 0 )
2011-09-10 02:08:16 +00:00
{
2012-06-07 04:47:54 +00:00
stateFrame = l . MovieLength ( ) ; //In case the frame count failed to parse, revert to using the entire state input log
2011-09-10 02:08:16 +00:00
}
2012-06-07 04:47:54 +00:00
if ( Log . MovieLength ( ) < stateFrame )
2011-07-31 16:41:27 +00:00
{
//Future event error
2012-06-07 04:47:54 +00:00
MessageBox . Show ( "The savestate is from frame " + l . MovieLength ( ) . ToString ( ) + " which is greater than the current movie length of " +
Log . MovieLength ( ) . ToString ( ) + ".\nCan not load this savestate." , "Future event Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
2012-08-18 00:04:12 +00:00
reader . Close ( ) ;
return false ;
2011-07-31 16:41:27 +00:00
}
2011-09-07 00:40:42 +00:00
for ( int x = 0 ; x < stateFrame ; x + + )
2011-06-12 14:42:50 +00:00
{
string xs = Log . GetFrame ( x ) ;
string ys = l . GetFrame ( x ) ;
2011-09-03 18:07:30 +00:00
if ( xs ! = ys )
2011-07-31 16:41:27 +00:00
{
//TimeLine Error
MessageBox . Show ( "The savestate input does not match the movie input at frame " + ( x + 1 ) . ToString ( ) + "." ,
"Timeline Error" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
2011-08-20 19:27:00 +00:00
reader . Close ( ) ;
2011-07-31 16:41:27 +00:00
return false ;
}
2011-06-12 14:42:50 +00:00
}
2011-08-20 19:27:00 +00:00
reader . Close ( ) ;
2011-07-31 16:41:27 +00:00
return true ;
2011-06-12 14:42:50 +00:00
}
2012-09-03 15:05:09 +00:00
#endregion
#region Private Fields
2012-09-03 19:42:53 +00:00
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
2012-09-03 15:05:09 +00:00
private MovieLog Log = new MovieLog ( ) ;
private int lastlog ;
2012-09-03 19:42:53 +00:00
private int rerecords ;
private bool statecapturing ;
private bool startsfromsavestate ;
private enum MOVIEMODE { INACTIVE , PLAY , RECORD , FINISHED } ;
private MOVIEMODE Mode = MOVIEMODE . INACTIVE ;
2012-09-03 15:05:09 +00:00
#endregion
2012-09-03 19:42:53 +00:00
#region Helpers
2012-09-03 15:05:09 +00:00
private void WriteText ( string file )
{
if ( file . Length = = 0 ) return ; //Nothing to write
int length = Log . MovieLength ( ) ;
using ( StreamWriter sw = new StreamWriter ( file ) )
{
Header . WriteText ( sw ) ;
Subtitles . WriteText ( sw ) ;
Log . WriteText ( sw ) ;
}
}
private void WriteBinary ( string file )
{
}
private bool LoadText ( )
{
var file = new FileInfo ( Filename ) ;
if ( file . Exists = = false )
{
Loaded = false ;
return false ;
}
else
{
Header . Clear ( ) ;
Log . Clear ( ) ;
}
using ( StreamReader sr = file . OpenText ( ) )
{
string str = "" ;
string rerecordStr = "" ;
while ( ( str = sr . ReadLine ( ) ) ! = null )
{
if ( str = = "" )
{
continue ;
}
if ( str . Contains ( MovieHeader . RERECORDS ) )
{
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 ;
}
if ( Header . AddHeaderFromLine ( str ) )
continue ;
if ( str . StartsWith ( "subtitle" ) | | str . StartsWith ( "sub" ) )
{
Subtitles . AddSubtitle ( str ) ;
}
else if ( str [ 0 ] = = '|' )
{
Log . AddFrame ( str ) ;
}
else
{
Header . Comments . Add ( str ) ;
}
}
}
Loaded = true ;
return true ;
}
private bool LoadBinary ( )
{
return true ;
}
private string MakeDigits ( decimal num )
{
return MakeDigits ( ( int ) num ) ;
}
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 = ( double ) 60.098813897440515532 ;
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 ) ) ;
double seconds = 0 ;
double frames = ( double ) frameCount ;
if ( frames < 1 )
return seconds ;
bool pal = false ; //TODO: pal flag
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" :
case "SNES" :
if ( pal )
return frames / NES_PAL ;
else
return frames / NES_NTSC ;
case "PCE" :
case "PCECD" :
return frames / PCE ;
//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 bool IsStateFromAMovie ( StreamReader reader )
{
while ( true )
{
if ( reader . ReadLine ( ) . Contains ( "GUID" ) )
break ;
if ( reader . EndOfStream )
return false ;
}
return true ;
}
private static string ParseHeader ( string line , string headerName )
{
string str ;
int x = line . LastIndexOf ( headerName ) + headerName . Length ;
str = line . Substring ( x + 1 , line . Length - x - 1 ) ;
return str ;
}
#endregion
#region ComparisonLogic
2011-06-12 14:42:50 +00:00
public int CompareTo ( Movie Other , string parameter )
{
int compare = 0 ;
if ( parameter = = "File" )
{
compare = CompareFileName ( Other ) ;
if ( compare = = 0 )
{
compare = CompareSysID ( Other ) ;
if ( compare = = 0 )
{
compare = CompareGameName ( Other ) ;
if ( compare = = 0 )
compare = CompareLength ( Other ) ;
}
}
}
else if ( parameter = = "SysID" )
{
compare = CompareSysID ( Other ) ;
if ( compare = = 0 )
{
compare = CompareFileName ( Other ) ;
if ( compare = = 0 )
{
compare = CompareGameName ( Other ) ;
if ( compare = = 0 )
compare = CompareLength ( Other ) ;
}
}
}
else if ( parameter = = "Game" )
{
compare = CompareGameName ( Other ) ;
if ( compare = = 0 )
{
compare = CompareFileName ( Other ) ;
if ( compare = = 0 )
{
compare = CompareSysID ( Other ) ;
if ( compare = = 0 )
compare = CompareLength ( Other ) ;
}
}
}
else if ( parameter = = "Length" )
{
compare = CompareLength ( Other ) ;
if ( compare = = 0 )
{
compare = CompareFileName ( Other ) ;
if ( compare = = 0 )
{
compare = CompareSysID ( Other ) ;
if ( compare = = 0 )
compare = CompareGameName ( Other ) ;
}
}
}
return compare ;
}
private int CompareFileName ( Movie Other )
{
2011-07-17 14:39:15 +00:00
string otherName = Path . GetFileName ( Other . Filename ) ;
2011-06-12 14:42:50 +00:00
string thisName = Path . GetFileName ( this . Filename ) ;
return thisName . CompareTo ( otherName ) ;
}
private int CompareSysID ( Movie Other )
{
2012-09-03 15:05:09 +00:00
string otherSysID = Other . SysID ;
string thisSysID = this . SysID ;
2011-06-12 14:42:50 +00:00
if ( thisSysID = = null & & otherSysID = = null )
return 0 ;
else if ( thisSysID = = null )
return - 1 ;
else if ( otherSysID = = null )
return 1 ;
else
return thisSysID . CompareTo ( otherSysID ) ;
}
private int CompareGameName ( Movie Other )
{
2012-09-03 15:05:09 +00:00
string otherGameName = Other . GameName ;
string thisGameName = this . GameName ;
2011-06-12 14:42:50 +00:00
if ( thisGameName = = null & & otherGameName = = null )
return 0 ;
else if ( thisGameName = = null )
return - 1 ;
else if ( otherGameName = = null )
return 1 ;
else
return thisGameName . CompareTo ( otherGameName ) ;
}
private int CompareLength ( Movie Other )
{
2012-09-03 19:42:53 +00:00
int otherLength = Other . preload_framecount ;
int thisLength = this . preload_framecount ;
2011-06-12 14:42:50 +00:00
if ( thisLength < otherLength )
2012-09-03 19:42:53 +00:00
{
2011-06-12 14:42:50 +00:00
return - 1 ;
2012-09-03 19:42:53 +00:00
}
2011-06-12 14:42:50 +00:00
else if ( thisLength > otherLength )
2012-09-03 19:42:53 +00:00
{
2011-06-12 14:42:50 +00:00
return 1 ;
2012-09-03 19:42:53 +00:00
}
2011-06-12 14:42:50 +00:00
else
2012-09-03 19:42:53 +00:00
{
2011-06-12 14:42:50 +00:00
return 0 ;
2012-09-03 19:42:53 +00:00
}
2011-06-12 14:42:50 +00:00
}
2011-07-04 01:57:18 +00:00
2012-09-03 15:05:09 +00:00
#endregion
2011-06-12 14:42:50 +00:00
}
-Began working on the importer.
--Created ImportFile to decide what function to use for each filetype.
---It currently automatically writes to a .TAS file, but that option will eventually only be applied when specified in the GUI, hopefully completely external from this class.
--Made IsValidMovieExtension work.
--Created LoadText to do the majority of the work that both .FM2 and .MC2 need to be done.
--.MC2 seems to work perfectly, not that it was a hard conversion!
--.FM2 seems to convert most headers correct, except for subtitles, which replaces the beginning portions of each subtitle's text with 0 0 120 4294967295. Not sure what that's about, though this sure feels like deja vu...
--I still need to switch around the order of the buttons the frames are added, but I need to find out what way I can do this without reinventing the wheel.
-Added the FixMnemonic function to Movie.cs. It currently does nothing, but my goal is to have it correct the mnemonic for all frames in a movie file based on the position of the characters.
--As of right now, ImportFile uses this.
-MainForm.IsValidMovieExtension only checks whether or not its .TAS or not now.
TODO:
-Fix the FM2 subtitles.
-Shift around the FM2 buttons.
--After completed, test a .FM2 file that should sync and see if it works, with and without FixMnemonic being used.
-Make FixMnemonic actually do something.
-Refactor code? I originally thought it'd be best to treat Movie.LoadText just like any other importer, but I think at this point it might just be best to keep these things completely separate.
-Consider the possibility of working with the binary file importers.
--Yes adelikat, I am somewhat interested, especially considering how useful it would be to have a working .FCM importer so I can compare old runs when TASing. I already was hoping to learn about .VBM and .SMV for my ButtonCount.lua script. By the way, might this be bundled with bizhawk as it is with FCEUX 2.1.6? :)
2012-02-15 06:54:09 +00:00
}