2013-11-01 18:52:26 +00:00
using System ;
using System.IO ;
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2013-11-01 18:52:26 +00:00
namespace BizHawk.Client.Common
2013-10-25 00:59:34 +00:00
{
2014-07-14 00:35:33 +00:00
public enum MovieEndAction { Stop , Pause , Record , Finish }
2013-10-25 00:59:34 +00:00
public class MovieSession
{
2013-12-05 00:44:56 +00:00
private readonly MultitrackRecording _multiTrack = new MultitrackRecording ( ) ;
2013-11-01 18:52:26 +00:00
2013-12-05 00:44:56 +00:00
public MovieSession ( )
{
ReadOnly = true ;
2014-06-17 01:13:20 +00:00
MovieControllerAdapter = MovieService . DefaultInstance . LogGeneratorInstance ( ) . MovieControllerAdapter ;
2013-12-05 00:44:56 +00:00
}
public MultitrackRecording MultiTrack { get { return _multiTrack ; } }
2014-06-19 01:47:22 +00:00
public IMovieController MovieControllerAdapter { get ; set ; }
2013-12-05 00:44:56 +00:00
public IMovie Movie { get ; set ; }
public bool ReadOnly { get ; set ; }
public Action < string > MessageCallback { get ; set ; }
public Func < string , string , bool > AskYesNoCallback { get ; set ; }
2013-12-03 18:08:45 +00:00
2014-07-14 00:35:33 +00:00
/// <summary>
/// Required
/// </summary>
public Action PauseCallback { get ; set ; }
/// <summary>
/// Required
/// </summary>
public Action ModeChangedCallback { get ; set ; }
2014-06-18 19:34:27 +00:00
/// <summary>
/// Simply shortens the verbosity necessary otherwise
/// </summary>
/// <returns></returns>
public ILogEntryGenerator LogGeneratorInstance ( )
{
return Movie . LogGeneratorInstance ( ) ;
}
public IMovieController MovieControllerInstance ( )
{
var adapter = Movie . LogGeneratorInstance ( ) . MovieControllerAdapter ;
adapter . Type = MovieControllerAdapter . Type ;
return adapter ;
}
2014-06-24 17:12:20 +00:00
// Convenience property that gets the controller state from the movie for the most recent frame
public IController CurrentInput
{
get
{
2014-06-29 14:42:20 +00:00
if ( Global . MovieSession . Movie . IsActive & & ! Global . MovieSession . Movie . IsFinished & & Global . Emulator . Frame > 0 )
2014-06-24 17:12:20 +00:00
{
return Global . MovieSession . Movie . GetInputState ( Global . Emulator . Frame - 1 ) ;
}
return null ;
}
}
2014-06-29 14:42:20 +00:00
public IController PreviousFrame
{
get
{
if ( Global . MovieSession . Movie . IsActive & & ! Global . MovieSession . Movie . IsFinished & & Global . Emulator . Frame > 1 )
{
return Global . MovieSession . Movie . GetInputState ( Global . Emulator . Frame - 2 ) ;
}
return null ;
}
}
2013-11-01 18:52:26 +00:00
private void Output ( string message )
{
if ( MessageCallback ! = null )
{
MessageCallback ( message ) ;
}
}
2013-12-05 00:44:56 +00:00
public void LatchMultitrackPlayerInput ( IController playerSource , MultitrackRewiringControllerAdapter rewiredSource )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:44:56 +00:00
if ( _multiTrack . IsActive )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:44:56 +00:00
rewiredSource . PlayerSource = 1 ;
rewiredSource . PlayerTargetMask = 1 < < _multiTrack . CurrentPlayer ;
if ( _multiTrack . RecordAll )
{
rewiredSource . PlayerTargetMask = unchecked ( ( int ) 0xFFFFFFFF ) ;
}
2013-11-01 18:52:26 +00:00
}
else
{
2013-12-05 00:44:56 +00:00
rewiredSource . PlayerSource = - 1 ;
2013-10-25 00:59:34 +00:00
}
2014-06-15 14:44:26 +00:00
MovieControllerAdapter . LatchPlayerFromSource ( rewiredSource , _multiTrack . CurrentPlayer ) ;
2013-10-25 00:59:34 +00:00
}
public void LatchInputFromPlayer ( IController source )
{
2014-06-15 14:44:26 +00:00
MovieControllerAdapter . LatchFromSource ( source ) ;
2013-10-25 00:59:34 +00:00
}
/// <summary>
2013-11-01 18:52:26 +00:00
/// Latch input from the input log, if available
2013-10-25 00:59:34 +00:00
/// </summary>
public void LatchInputFromLog ( )
{
2014-07-14 00:35:33 +00:00
if ( Global . Emulator . Frame < Movie . InputLogLength - ( Global . Config . MovieEndAction = = MovieEndAction . Pause ? 1 : 0 ) ) // Pause logic is a hack for now
2013-11-29 19:55:05 +00:00
{
2014-07-13 22:36:37 +00:00
var input = Movie . GetInputState ( Global . Emulator . Frame ) ;
2014-07-13 22:17:31 +00:00
MovieControllerAdapter . LatchFromSource ( input ) ;
2013-11-29 19:55:05 +00:00
}
2014-07-13 22:36:37 +00:00
else
{
2014-07-14 00:35:33 +00:00
HandlePlaybackEnd ( ) ;
2014-07-13 22:36:37 +00:00
}
2013-11-01 18:52:26 +00:00
}
2014-07-14 00:35:33 +00:00
private void HandlePlaybackEnd ( )
{
// TODO: mainform callback to update on mode change
switch ( Global . Config . MovieEndAction )
{
case MovieEndAction . Stop :
Movie . Stop ( ) ;
break ;
case MovieEndAction . Record :
Movie . SwitchToRecord ( ) ;
break ;
case MovieEndAction . Pause :
PauseCallback ( ) ; // TODO: one frame ago
break ;
default :
case MovieEndAction . Finish :
Movie . FinishedMode ( ) ;
break ;
}
ModeChangedCallback ( ) ;
}
2014-07-08 15:15:35 +00:00
public bool MovieLoad ( )
2014-06-21 14:33:33 +00:00
{
MovieControllerAdapter = Movie . LogGeneratorInstance ( ) . MovieControllerAdapter ;
2014-07-08 15:15:35 +00:00
return Movie . Load ( ) ;
2014-06-21 14:33:33 +00:00
}
2013-11-23 18:18:58 +00:00
public void StopMovie ( bool saveChanges = true )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:44:56 +00:00
var message = "Movie " ;
2013-11-01 18:52:26 +00:00
if ( Movie . IsRecording )
{
message + = "recording " ;
}
else if ( Movie . IsPlaying )
{
message + = "playback " ;
}
message + = "stopped." ;
if ( Movie . IsActive )
{
2013-11-23 18:18:58 +00:00
Movie . Stop ( saveChanges ) ;
if ( saveChanges )
2013-11-01 18:52:26 +00:00
{
Output ( Path . GetFileName ( Movie . Filename ) + " written to disk." ) ;
}
2013-12-05 00:44:56 +00:00
2013-11-01 18:52:26 +00:00
Output ( message ) ;
2013-12-03 18:08:45 +00:00
ReadOnly = true ;
2013-11-01 18:52:26 +00:00
}
}
2013-12-17 21:26:15 +00:00
public void HandleMovieSaveState ( TextWriter writer )
2013-11-01 18:52:26 +00:00
{
if ( Movie . IsActive )
{
2013-11-30 02:50:54 +00:00
writer . Write ( Movie . GetInputLog ( ) ) ;
2013-11-01 18:52:26 +00:00
}
}
public void ClearFrame ( )
{
if ( Movie . IsPlaying )
{
Movie . ClearFrame ( Global . Emulator . Frame ) ;
2013-12-03 18:08:45 +00:00
Output ( "Scrubbed input at frame " + Global . Emulator . Frame ) ;
2013-11-01 18:52:26 +00:00
}
}
2013-11-03 16:53:05 +00:00
public void HandleMovieOnFrameLoop ( )
2013-11-01 18:52:26 +00:00
{
if ( ! Movie . IsActive )
{
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
}
else if ( Movie . IsFinished )
{
2013-12-05 00:44:56 +00:00
if ( Global . Emulator . Frame < Movie . FrameCount ) // This scenario can happen from rewinding (suddenly we are back in the movie, so hook back up to the movie
2013-11-01 18:52:26 +00:00
{
Movie . SwitchToPlay ( ) ;
LatchInputFromLog ( ) ;
}
else
{
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
}
}
else if ( Movie . IsPlaying )
{
2013-11-29 19:55:05 +00:00
LatchInputFromLog ( ) ;
2013-12-05 00:44:56 +00:00
// Movie may go into finished mode as a result from latching
2013-11-29 19:55:05 +00:00
if ( ! Movie . IsFinished )
2013-11-01 18:52:26 +00:00
{
2013-11-03 16:53:05 +00:00
if ( Global . ClientControls [ "Scrub Input" ] )
2013-11-01 18:52:26 +00:00
{
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
ClearFrame ( ) ;
}
2013-11-29 19:55:05 +00:00
else if ( Global . Config . MoviePlaybackPokeMode )
2013-11-01 18:52:26 +00:00
{
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
2014-06-14 22:36:32 +00:00
var lg = Movie . LogGeneratorInstance ( ) ;
lg . SetSource ( Global . MovieOutputHardpoint ) ;
if ( ! lg . IsEmpty )
2013-11-01 18:52:26 +00:00
{
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
2013-12-07 16:31:04 +00:00
Movie . PokeFrame ( Global . Emulator . Frame , Global . MovieOutputHardpoint ) ;
2013-11-01 18:52:26 +00:00
}
else
{
LatchInputFromLog ( ) ;
}
}
}
}
else if ( Movie . IsRecording )
{
2013-12-05 00:44:56 +00:00
if ( _multiTrack . IsActive )
2013-11-01 18:52:26 +00:00
{
LatchMultitrackPlayerInput ( Global . MovieInputSourceAdapter , Global . MultitrackRewiringControllerAdapter ) ;
}
else
{
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
}
2013-12-03 18:08:45 +00:00
// the movie session makes sure that the correct input has been read and merged to its MovieControllerAdapter;
// this has been wired to Global.MovieOutputHardpoint in RewireInputChain
2013-12-07 16:31:04 +00:00
Movie . RecordFrame ( Global . Emulator . Frame , Global . MovieOutputHardpoint ) ;
2013-11-01 18:52:26 +00:00
}
}
2013-11-01 20:53:47 +00:00
public bool HandleMovieLoadState ( string path )
{
using ( var sr = new StreamReader ( path ) )
{
2013-12-03 18:08:45 +00:00
return HandleMovieLoadState ( sr ) ;
2013-11-01 20:53:47 +00:00
}
}
2014-04-18 17:41:14 +00:00
//TODO: maybe someone who understands more about what's going on here could rename these step1 and step2 into something more descriptive
public bool HandleMovieLoadState_HackyStep2 ( TextReader reader )
{
if ( ! Movie . IsActive )
{
return true ;
}
if ( ReadOnly )
{
}
else
{
string errorMsg ;
//// fixme: this is evil (it causes crashes in binary states because InflaterInputStream can't have its position set, even to zero.
//((StreamReader)reader).BaseStream.Position = 0;
//((StreamReader)reader).DiscardBufferedData();
//edit: zero 18-apr-2014 - this was solved by HackyStep1 and HackyStep2, so that the zip stream can be re-acquired instead of needing its position reset
var result = Movie . ExtractInputLog ( reader , out errorMsg ) ;
if ( ! result )
{
Output ( errorMsg ) ;
return false ;
}
}
return true ;
}
2013-12-17 21:26:15 +00:00
public bool HandleMovieLoadState ( TextReader reader )
2014-04-18 17:41:14 +00:00
{
if ( ! HandleMovieLoadState_HackyStep1 ( reader ) )
return false ;
return HandleMovieLoadState_HackyStep2 ( reader ) ;
}
public bool HandleMovieLoadState_HackyStep1 ( TextReader reader )
2013-11-01 18:52:26 +00:00
{
if ( ! Movie . IsActive )
{
return true ;
}
2013-12-05 00:44:56 +00:00
string errorMsg ;
2013-12-04 14:42:24 +00:00
if ( ReadOnly )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:20:21 +00:00
var result = Movie . CheckTimeLines ( reader , out errorMsg ) ;
2013-12-04 14:42:24 +00:00
if ( ! result )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:20:21 +00:00
Output ( errorMsg ) ;
2013-12-04 14:42:24 +00:00
return false ;
2013-11-01 18:52:26 +00:00
}
2013-12-04 14:42:24 +00:00
if ( Movie . IsRecording )
2013-11-01 18:52:26 +00:00
{
2013-12-04 14:42:24 +00:00
Movie . SwitchToPlay ( ) ;
2013-11-01 18:52:26 +00:00
}
2013-12-04 14:42:24 +00:00
else if ( Movie . IsFinished )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:20:21 +00:00
LatchInputFromPlayer ( Global . MovieInputSourceAdapter ) ;
2013-11-01 18:52:26 +00:00
}
}
2013-12-04 14:42:24 +00:00
else
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:20:21 +00:00
if ( Movie . IsFinished )
2013-11-01 18:52:26 +00:00
{
2013-12-05 00:20:21 +00:00
Movie . StartNewRecording ( ) ;
2013-12-04 14:42:24 +00:00
}
2013-12-05 00:20:21 +00:00
else if ( Movie . IsPlaying )
2013-12-04 14:42:24 +00:00
{
Movie . SwitchToRecord ( ) ;
}
2013-12-05 00:20:21 +00:00
2013-11-01 18:52:26 +00:00
}
return true ;
2013-10-25 00:59:34 +00:00
}
2014-06-29 23:13:44 +00:00
public void ToggleMultitrack ( )
{
if ( Movie . IsActive )
{
if ( Global . Config . VBAStyleMovieLoadState )
{
MessageCallback ( "Multi-track can not be used in Full Movie Loadstates mode" ) ;
}
else
{
Global . MovieSession . MultiTrack . IsActive = ! Global . MovieSession . MultiTrack . IsActive ;
if ( Global . MovieSession . MultiTrack . IsActive )
{
MessageCallback ( "MultiTrack Enabled" ) ;
MultiTrack . CurrentState = "Recording None" ;
}
else
{
MessageCallback ( "MultiTrack Disabled" ) ;
}
Global . MovieSession . MultiTrack . SelectNone ( ) ;
}
}
else
{
MessageCallback ( "MultiTrack cannot be enabled while not recording." ) ;
}
}
2013-10-25 00:59:34 +00:00
}
}