From 3cda83c2bc24fca403930a708ff18dfc8713c5d9 Mon Sep 17 00:00:00 2001
From: adelikat <adelikat@tasvideos.org>
Date: Fri, 1 Nov 2013 18:52:26 +0000
Subject: [PATCH] Move a bunch of logic from MainForm.Movie.cs to
 MovieSession.cs which is more conceptually appropriate, this also means that
 movie handling logic (including complex movie loadstate logic) is moved to
 Client.Common

---
 BizHawk.Client.Common/movie/MovieSession.cs | 441 ++++++++++++++++++-
 BizHawk.MultiClient/MainForm.Events.cs      |   6 +-
 BizHawk.MultiClient/MainForm.Movie.cs       | 451 +-------------------
 BizHawk.MultiClient/MainForm.cs             |  40 +-
 BizHawk.MultiClient/tools/TAStudio.cs       |   4 +-
 5 files changed, 489 insertions(+), 453 deletions(-)

diff --git a/BizHawk.Client.Common/movie/MovieSession.cs b/BizHawk.Client.Common/movie/MovieSession.cs
index 83695879ca..6ff8a3c138 100644
--- a/BizHawk.Client.Common/movie/MovieSession.cs
+++ b/BizHawk.Client.Common/movie/MovieSession.cs
@@ -1,10 +1,45 @@
-namespace BizHawk.Client.Common
+using System;
+using System.IO;
+
+namespace BizHawk.Client.Common
 {
 	public class MovieSession
 	{
 		public MultitrackRecording MultiTrack = new MultitrackRecording();
 		public Movie Movie;
 		public MovieControllerAdapter MovieControllerAdapter = new MovieControllerAdapter();
+		public bool EditorMode { get; set; }
+		public Action ClearSRAMCallback; //Required
+		public Action<string> MessageCallback; //Not Required
+		public Func<string, string, bool> AskYesNoCallback; //Not Required
+
+		private void Output(string message)
+		{
+			if (MessageCallback != null)
+			{
+				MessageCallback(message);
+			}
+		}
+
+		private bool AskYesNo(string title, string message)
+		{
+			if (AskYesNoCallback != null)
+			{
+				return AskYesNoCallback(title, message);
+			}
+			else
+			{
+				return true;
+			}
+		}
+
+		private bool HandleGuidError()
+		{
+			return AskYesNo(
+				"GUID Mismatch error",
+				"The savestate GUID does not match the current movie.  Proceed anyway?"
+			);
+		}
 
 		public void LatchMultitrackPlayerInput(IController playerSource, MultitrackRewiringControllerAdapter rewiredSource)
 		{
@@ -25,12 +60,410 @@
 		}
 
 		/// <summary>
-		/// latch input from the input log, if available
+		/// Latch input from the input log, if available
 		/// </summary>
 		public void LatchInputFromLog()
 		{
-			string loggedFrame = Movie.GetInput(Global.Emulator.Frame);
-			MovieControllerAdapter.SetControllersAsMnemonic(loggedFrame);
+			MovieControllerAdapter.SetControllersAsMnemonic(
+				Movie.GetInput(Global.Emulator.Frame)
+			);
+		}
+
+		public void StopMovie(bool abortchanges = false)
+		{
+			string message = "Movie ";
+			if (Movie.IsRecording)
+			{
+				message += "recording ";
+			}
+			else if (Movie.IsPlaying)
+			{
+				message += "playback ";
+			}
+
+			message += "stopped.";
+
+			if (Movie.IsActive)
+			{
+				Movie.Stop(abortchanges);
+				if (!abortchanges)
+				{
+					Output(Path.GetFileName(Movie.Filename) + " written to disk.");
+				}
+				Output(message);
+				Global.ReadOnly = true;
+			}
+		}
+
+		//State handling
+		public void HandleMovieSaveState(StreamWriter writer)
+		{
+			if (Movie.IsActive)
+			{
+				Movie.DumpLogIntoSavestateText(writer);
+			}
+		}
+
+		public void ClearFrame()
+		{
+			if (Movie.IsPlaying)
+			{
+				Movie.ClearFrame(Global.Emulator.Frame);
+				Output("Scrubbed input at frame " + Global.Emulator.Frame.ToString());
+			}
+		}
+
+		public void HandleMovieOnFrameLoop(bool clearFramePressed)
+		{
+			if (!Movie.IsActive)
+			{
+				LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+			}
+
+			else if (Movie.IsFinished)
+			{
+				if (Global.Emulator.Frame < Movie.Frames) //This scenario can happen from rewinding (suddenly we are back in the movie, so hook back up to the movie
+				{
+					Movie.SwitchToPlay();
+					LatchInputFromLog();
+				}
+				else
+				{
+					LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+				}
+			}
+
+			else if (Movie.IsPlaying)
+			{
+				if (Global.Emulator.Frame >= Movie.Frames)
+				{
+					if (EditorMode)
+					{
+						Movie.CaptureState();
+						LatchInputFromLog();
+						Movie.CommitFrame(Global.Emulator.Frame, Global.MovieOutputHardpoint);
+					}
+					else
+					{
+						Movie.Finish();
+					}
+				}
+				else
+				{
+					Movie.CaptureState();
+					LatchInputFromLog();
+					if (clearFramePressed)
+					{
+						LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+						ClearFrame();
+					}
+					else if (EditorMode || Global.Config.MoviePlaybackPokeMode)
+					{
+						LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+						var mg = new MnemonicsGenerator();
+						mg.SetSource(Global.MovieOutputHardpoint);
+						if (!mg.IsEmpty)
+						{
+							LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+							Movie.PokeFrame(Global.Emulator.Frame, mg.GetControllersAsMnemonic());
+						}
+						else
+						{
+							LatchInputFromLog();
+						}
+					}
+				}
+			}
+
+			else if (Movie.IsRecording)
+			{
+				Movie.CaptureState();
+				if (MultiTrack.IsActive)
+				{
+					LatchMultitrackPlayerInput(Global.MovieInputSourceAdapter, Global.MultitrackRewiringControllerAdapter);
+				}
+				else
+				{
+					LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+				}
+				//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
+				Movie.CommitFrame(Global.Emulator.Frame, Global.MovieOutputHardpoint);
+			}
+		}
+
+		//OMG this needs to be refactored!
+		public bool HandleMovieLoadState(StreamReader reader)
+		{
+			string ErrorMSG = String.Empty;
+
+			if (!Movie.IsActive)
+			{
+				return true;
+			}
+
+			else if (Movie.IsRecording)
+			{
+				if (Global.ReadOnly)
+				{
+					var result = Movie.CheckTimeLines(reader, OnlyGUID: false, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
+					if (result == Movie.LoadStateResult.Pass)
+					{
+						Movie.WriteMovie();
+						Movie.SwitchToPlay();
+						
+						return true;
+					}
+					else
+					{
+						if (result == Movie.LoadStateResult.GuidMismatch)
+						{
+							if (HandleGuidError())
+							{
+								var newresult = Movie.CheckTimeLines(reader, OnlyGUID: false, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
+								if (newresult == Movie.LoadStateResult.Pass)
+								{
+									Movie.WriteMovie();
+									Movie.SwitchToPlay();
+									return true;
+								}
+								else
+								{
+									Output(ErrorMSG);
+									return false;
+								}
+							}
+							else
+							{
+								return false;
+							}
+						}
+						else
+						{
+							Output(ErrorMSG);
+							return false;
+						}
+					}
+				}
+				else
+				{
+					var result = Movie.CheckTimeLines(reader, OnlyGUID: true, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
+					if (result == Movie.LoadStateResult.Pass)
+					{
+						reader.BaseStream.Position = 0;
+						reader.DiscardBufferedData();
+						Movie.LoadLogFromSavestateText(reader, MultiTrack.IsActive);
+					}
+					else
+					{
+						if (result == Movie.LoadStateResult.GuidMismatch)
+						{
+							if (HandleGuidError())
+							{
+								var newresult = Movie.CheckTimeLines(reader, OnlyGUID: false, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
+								if (newresult == Movie.LoadStateResult.Pass)
+								{
+									reader.BaseStream.Position = 0;
+									reader.DiscardBufferedData();
+									Movie.LoadLogFromSavestateText(reader, MultiTrack.IsActive);
+									return true;
+								}
+								else
+								{
+									Output(ErrorMSG);
+									return false;
+								}
+							}
+							else
+							{
+								return false;
+							}
+						}
+						else
+						{
+							Output(ErrorMSG);
+							return false;
+						}
+					}
+				}
+			}
+
+			else if (Movie.IsPlaying && !Movie.IsFinished)
+			{
+				if (Global.ReadOnly)
+				{
+					var result = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
+					if (result == Movie.LoadStateResult.Pass)
+					{
+						//Frame loop automatically handles the rewinding effect based on Global.Emulator.Frame so nothing else is needed here
+						return true;
+					}
+					else
+					{
+						if (result == Movie.LoadStateResult.GuidMismatch)
+						{
+							if (HandleGuidError())
+							{
+								var newresult = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
+								if (newresult == Movie.LoadStateResult.Pass)
+								{
+									return true;
+								}
+								else
+								{
+									Output(ErrorMSG);
+									return false;
+								}
+							}
+							else
+							{
+								return false;
+							}
+						}
+						else
+						{
+							Output(ErrorMSG);
+							return false;
+						}
+					}
+				}
+				else
+				{
+					var result = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
+					if (result == Movie.LoadStateResult.Pass)
+					{
+						Movie.SwitchToRecord();
+						reader.BaseStream.Position = 0;
+						reader.DiscardBufferedData();
+						Movie.LoadLogFromSavestateText(reader, MultiTrack.IsActive);
+						return true;
+					}
+					else
+					{
+						if (result == Movie.LoadStateResult.GuidMismatch)
+						{
+							if (HandleGuidError())
+							{
+								var newresult = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
+								if (newresult == Movie.LoadStateResult.Pass)
+								{
+									Movie.SwitchToRecord();
+									reader.BaseStream.Position = 0;
+									reader.DiscardBufferedData();
+									Movie.LoadLogFromSavestateText(reader, MultiTrack.IsActive);
+									return true;
+								}
+								else
+								{
+									Output(ErrorMSG);
+									return false;
+								}
+							}
+							else
+							{
+								return false;
+							}
+						}
+						else
+						{
+							Output(ErrorMSG);
+							return false;
+						}
+					}
+				}
+			}
+			else if (Movie.IsFinished)
+			{
+				if (Global.ReadOnly)
+				{
+					var result = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
+					if (result != Movie.LoadStateResult.Pass)
+					{
+						if (result == Movie.LoadStateResult.GuidMismatch)
+						{
+							if (HandleGuidError())
+							{
+								var newresult = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
+								if (newresult == Movie.LoadStateResult.Pass)
+								{
+									Movie.SwitchToPlay();
+									return true;
+								}
+								else
+								{
+									Output(ErrorMSG);
+									return false;
+								}
+							}
+							else
+							{
+								return false;
+							}
+						}
+						else
+						{
+							Output(ErrorMSG);
+							return false;
+						}
+					}
+					else if (Movie.IsFinished) //TimeLine check can change a movie to finished, hence the check here (not a good design)
+					{
+						LatchInputFromPlayer(Global.MovieInputSourceAdapter);
+					}
+					else
+					{
+						Movie.SwitchToPlay();
+					}
+				}
+				else
+				{
+					var result = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
+					if (result == Movie.LoadStateResult.Pass)
+					{
+						ClearSRAMCallback();
+						Movie.StartRecording();
+						reader.BaseStream.Position = 0;
+						reader.DiscardBufferedData();
+						Movie.LoadLogFromSavestateText(reader, MultiTrack.IsActive);
+						return true;
+					}
+					else
+					{
+						if (result == Movie.LoadStateResult.GuidMismatch)
+						{
+							if (HandleGuidError())
+							{
+								var newresult = Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
+								if (newresult == Movie.LoadStateResult.Pass)
+								{
+									ClearSRAMCallback();
+									Movie.StartRecording();
+									reader.BaseStream.Position = 0;
+									reader.DiscardBufferedData();
+									Movie.LoadLogFromSavestateText(reader, MultiTrack.IsActive);
+									return true;
+								}
+								else
+								{
+									Output(ErrorMSG);
+									return false;
+								}
+							}
+							else
+							{
+								return false;
+							}
+						}
+						else
+						{
+							Output(ErrorMSG);
+							return false;
+						}
+					}
+				}
+			}
+
+			return true;
 		}
 	}
 
diff --git a/BizHawk.MultiClient/MainForm.Events.cs b/BizHawk.MultiClient/MainForm.Events.cs
index 4c2a9bc788..0a32af3780 100644
--- a/BizHawk.MultiClient/MainForm.Events.cs
+++ b/BizHawk.MultiClient/MainForm.Events.cs
@@ -369,12 +369,12 @@ namespace BizHawk.MultiClient
 
 		private void RecordMovieMenuItem_Click(object sender, EventArgs e)
 		{
-			RecordMovie();
+			LoadRecordMovieDialog();
 		}
 
 		private void PlayMovieMenuItem_Click(object sender, EventArgs e)
 		{
-			PlayMovie();
+			LoadPlayMovieDialog();
 		}
 
 		private void StopMovieMenuItem_Click(object sender, EventArgs e)
@@ -384,7 +384,7 @@ namespace BizHawk.MultiClient
 
 		private void PlayFromBeginningMenuItem_Click(object sender, EventArgs e)
 		{
-			PlayMovieFromBeginning();
+			RestartMovie();
 		}
 
 		private void ImportMovieMenuItem_Click(object sender, EventArgs e)
diff --git a/BizHawk.MultiClient/MainForm.Movie.cs b/BizHawk.MultiClient/MainForm.Movie.cs
index 3af297fb6d..32475efa0e 100644
--- a/BizHawk.MultiClient/MainForm.Movie.cs
+++ b/BizHawk.MultiClient/MainForm.Movie.cs
@@ -8,15 +8,6 @@ namespace BizHawk.MultiClient
 {
 	partial class MainForm
 	{
-		public void ClearFrame()
-		{
-			if (Global.MovieSession.Movie.IsPlaying)
-			{
-				Global.MovieSession.Movie.ClearFrame(Global.Emulator.Frame);
-				GlobalWinF.OSD.AddMessage("Scrubbed input at frame " + Global.Emulator.Frame.ToString());
-			}
-		}
-
 		public void StartNewMovie(Movie m, bool record)
 		{
 			//If a movie is already loaded, save it before starting a new movie
@@ -25,7 +16,14 @@ namespace BizHawk.MultiClient
 				Global.MovieSession.Movie.WriteMovie();
 			}
 
-			Global.MovieSession = new MovieSession { Movie = m };
+			Global.MovieSession = new MovieSession
+			{
+				Movie = m,
+				ClearSRAMCallback = ClearSaveRAM,
+				MessageCallback = GlobalWinF.OSD.AddMessage,
+				AskYesNoCallback = StateErrorAskUser
+			};
+
 			RewireInputChain();
 
 			if (!record)
@@ -84,31 +82,30 @@ namespace BizHawk.MultiClient
 			}
 		}
 
-		public void PlayMovie()
+		public void LoadPlayMovieDialog()
 		{
 			new PlayMovie().ShowDialog();
 		}
 
-		public void RecordMovie()
+		public void LoadRecordMovieDialog()
 		{
 			// put any BEETA quality cores here
 			if (Global.Emulator is Emulation.Consoles.Nintendo.GBA.GBA ||
 				Global.Emulator is Emulation.Consoles.Sega.Genesis ||
 				Global.Emulator is Emulation.Consoles.Sega.Saturn.Yabause ||
-                Global.Emulator is Emulation.Consoles.Sony.PSP.PSP)
+				Global.Emulator is Emulation.Consoles.Sony.PSP.PSP)
 			{
 				var result = MessageBox.Show
 					(this, "Thanks for using Bizhawk!  The emulation core you have selected " +
 					"is currently BETA-status.  We appreciate your help in testing Bizhawk. " +
 					"You can record a movie on this core if you'd like to, but expect to " +
 					"encounter bugs and sync problems.  Continue?", "BizHawk", MessageBoxButtons.YesNo);
-				if (result != DialogResult.Yes)
-					return;
+				if (result != DialogResult.Yes) return;
 			}
 			new RecordMovie().ShowDialog();
 		}
 
-		public void PlayMovieFromBeginning()
+		public void RestartMovie()
 		{
 			if (Global.MovieSession.Movie.IsActive)
 			{
@@ -128,430 +125,16 @@ namespace BizHawk.MultiClient
 
 		public void StopMovie(bool abortchanges = false)
 		{
-			string message = "Movie ";
-			if (Global.MovieSession.Movie.IsRecording)
-			{
-				message += "recording ";
-			}
-			else if (Global.MovieSession.Movie.IsPlaying)
-			{
-				message += "playback ";
-			}
-
-			message += "stopped.";
-
-			if (Global.MovieSession.Movie.IsActive)
-			{
-				Global.MovieSession.Movie.Stop(abortchanges);
-				if (!abortchanges)
-				{
-					GlobalWinF.OSD.AddMessage(Path.GetFileName(Global.MovieSession.Movie.Filename) + " written to disk.");
-				}
-				GlobalWinF.OSD.AddMessage(message);
-				Global.ReadOnly = true;
-				SetMainformMovieInfo();
-			}
-		}
-
-		private void ShowError(string error)
-		{
-			if (!String.IsNullOrWhiteSpace(error))
-			{
-				MessageBox.Show(error, "Loadstate Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
-			}
+			Global.MovieSession.StopMovie();
+			SetMainformMovieInfo();
 		}
 
 		private bool HandleMovieLoadState(string path)
 		{
 			using (var sr = new StreamReader(path))
 			{
-				return HandleMovieLoadState(sr);
-			}
-		}
-
-		//OMG this needs to be refactored!
-		private bool HandleMovieLoadState(StreamReader reader)
-		{
-			string ErrorMSG = String.Empty;
-			//Note, some of the situations in these IF's may be identical and could be combined but I intentionally separated it out for clarity
-			if (!Global.MovieSession.Movie.IsActive)
-			{
-				return true;
-			}
-
-			else if (Global.MovieSession.Movie.IsRecording)
-			{
-				if (Global.ReadOnly)
-				{
-					var result = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: false, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
-					if (result == Movie.LoadStateResult.Pass)
-					{
-						Global.MovieSession.Movie.WriteMovie();
-						Global.MovieSession.Movie.SwitchToPlay();
-						SetMainformMovieInfo();
-						return true;
-					}
-					else
-					{
-						if (result == Movie.LoadStateResult.GuidMismatch)
-						{
-							var dresult = MessageBox.Show("The savestate GUID does not match the current movie.  Proceed anyway?",
-								"GUID Mismatch error",
-								MessageBoxButtons.YesNo, MessageBoxIcon.Question);
-							if (dresult == DialogResult.Yes)
-							{
-								var newresult = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: false, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
-								if (newresult == Movie.LoadStateResult.Pass)
-								{
-									Global.MovieSession.Movie.WriteMovie();
-									Global.MovieSession.Movie.SwitchToPlay();
-									SetMainformMovieInfo();
-									return true;
-								}
-								else
-								{
-									ShowError(ErrorMSG);
-									return false;
-								}
-							}
-							else
-							{
-								return false;
-							}
-						}
-						else
-						{
-							ShowError(ErrorMSG);
-							return false;
-						}
-					}
-				}
-				else
-				{
-					var result = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: true, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
-					if (result == Movie.LoadStateResult.Pass)
-					{
-						reader.BaseStream.Position = 0;
-						reader.DiscardBufferedData();
-						Global.MovieSession.Movie.LoadLogFromSavestateText(reader, Global.MovieSession.MultiTrack.IsActive);
-					}
-					else
-					{
-						if (result == Movie.LoadStateResult.GuidMismatch)
-						{
-							var dresult = MessageBox.Show("The savestate GUID does not match the current movie.  Proceed anyway?",
-								"GUID Mismatch error",
-								MessageBoxButtons.YesNo, MessageBoxIcon.Question);
-							if (dresult == DialogResult.Yes)
-							{
-								var newresult = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: false, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
-								if (newresult == Movie.LoadStateResult.Pass)
-								{
-									reader.BaseStream.Position = 0;
-									reader.DiscardBufferedData();
-									Global.MovieSession.Movie.LoadLogFromSavestateText(reader, Global.MovieSession.MultiTrack.IsActive);
-									return true;
-								}
-								else
-								{
-									ShowError(ErrorMSG);
-									return false;
-								}
-							}
-							else
-							{
-								return false;
-							}
-						}
-						else
-						{
-							ShowError(ErrorMSG);
-							return false;
-						}
-					}
-				}
-			}
-
-			else if (Global.MovieSession.Movie.IsPlaying && !Global.MovieSession.Movie.IsFinished)
-			{
-				if (Global.ReadOnly)
-				{
-					var result = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
-					if (result == Movie.LoadStateResult.Pass)
-					{
-						//Frame loop automatically handles the rewinding effect based on Global.Emulator.Frame so nothing else is needed here
-						return true;
-					}
-					else
-					{
-						if (result == Movie.LoadStateResult.GuidMismatch)
-						{
-							var dresult = MessageBox.Show("The savestate GUID does not match the current movie.  Proceed anyway?",
-								"GUID Mismatch error",
-								MessageBoxButtons.YesNo, MessageBoxIcon.Question);
-							if (dresult == DialogResult.Yes)
-							{
-								var newresult = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
-								if (newresult == Movie.LoadStateResult.Pass)
-								{
-									return true;
-								}
-								else
-								{
-									ShowError(ErrorMSG);
-									return false;
-								}
-							}
-							else
-							{
-								return false;
-							}
-						}
-						else
-						{
-							ShowError(ErrorMSG);
-							return false;
-						}
-					}
-				}
-				else
-				{
-					var result = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
-					if (result == Movie.LoadStateResult.Pass)
-					{
-						Global.MovieSession.Movie.SwitchToRecord();
-						SetMainformMovieInfo();
-						reader.BaseStream.Position = 0;
-						reader.DiscardBufferedData();
-						Global.MovieSession.Movie.LoadLogFromSavestateText(reader, Global.MovieSession.MultiTrack.IsActive);
-						return true;
-					}
-					else
-					{
-						if (result == Movie.LoadStateResult.GuidMismatch)
-						{
-							var dresult = MessageBox.Show("The savestate GUID does not match the current movie.  Proceed anyway?",
-								"GUID Mismatch error",
-								MessageBoxButtons.YesNo, MessageBoxIcon.Question);
-							if (dresult == DialogResult.Yes)
-							{
-								var newresult = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
-								if (newresult == Movie.LoadStateResult.Pass)
-								{
-									Global.MovieSession.Movie.SwitchToRecord();
-									SetMainformMovieInfo();
-									reader.BaseStream.Position = 0;
-									reader.DiscardBufferedData();
-									Global.MovieSession.Movie.LoadLogFromSavestateText(reader, Global.MovieSession.MultiTrack.IsActive);
-									return true;
-								}
-								else
-								{
-									ShowError(ErrorMSG);
-									return false;
-								}
-							}
-							else
-							{
-								return false;
-							}
-						}
-						else
-						{
-							ShowError(ErrorMSG);
-							return false;
-						}
-					}
-				}
-			}
-			else if (Global.MovieSession.Movie.IsFinished)
-			{
-				if (Global.ReadOnly)
-				{
-					var result = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
-					if (result != Movie.LoadStateResult.Pass)
-					{
-						if (result == Movie.LoadStateResult.GuidMismatch)
-						{
-							var dresult = MessageBox.Show("The savestate GUID does not match the current movie.  Proceed anyway?",
-								"GUID Mismatch error",
-								MessageBoxButtons.YesNo, MessageBoxIcon.Question);
-							if (dresult == DialogResult.Yes)
-							{
-								var newresult = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
-								if (newresult == Movie.LoadStateResult.Pass)
-								{
-									Global.MovieSession.Movie.SwitchToPlay();
-									SetMainformMovieInfo();
-									return true;
-								}
-								else
-								{
-									ShowError(ErrorMSG);
-									return false;
-								}
-							}
-							else
-							{
-								return false;
-							}
-						}
-						else
-						{
-							ShowError(ErrorMSG);
-							return false;
-						}
-					}
-					else if (Global.MovieSession.Movie.IsFinished) //TimeLine check can change a movie to finished, hence the check here (not a good design)
-					{
-						Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-					}
-					else
-					{
-						Global.MovieSession.Movie.SwitchToPlay();
-						SetMainformMovieInfo();
-					}
-				}
-				else
-				{
-					var result = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: false, ErrorMessage: out ErrorMSG);
-					if (result == Movie.LoadStateResult.Pass)
-					{
-						GlobalWinF.MainForm.ClearSaveRAM();
-						Global.MovieSession.Movie.StartRecording();
-						SetMainformMovieInfo();
-						reader.BaseStream.Position = 0;
-						reader.DiscardBufferedData();
-						Global.MovieSession.Movie.LoadLogFromSavestateText(reader, Global.MovieSession.MultiTrack.IsActive);
-						return true;
-					}
-					else
-					{
-						if (result == Movie.LoadStateResult.GuidMismatch)
-						{
-							var dresult = MessageBox.Show("The savestate GUID does not match the current movie.  Proceed anyway?",
-								"GUID Mismatch error",
-								MessageBoxButtons.YesNo, MessageBoxIcon.Question);
-							if (dresult == DialogResult.Yes)
-							{
-								var newresult = Global.MovieSession.Movie.CheckTimeLines(reader, OnlyGUID: !Global.ReadOnly, IgnoreGuidMismatch: true, ErrorMessage: out ErrorMSG);
-								if (newresult == Movie.LoadStateResult.Pass)
-								{
-									GlobalWinF.MainForm.ClearSaveRAM();
-									Global.MovieSession.Movie.StartRecording();
-									SetMainformMovieInfo();
-									reader.BaseStream.Position = 0;
-									reader.DiscardBufferedData();
-									Global.MovieSession.Movie.LoadLogFromSavestateText(reader, Global.MovieSession.MultiTrack.IsActive);
-									return true;
-								}
-								else
-								{
-									ShowError(ErrorMSG);
-									return false;
-								}
-							}
-							else
-							{
-								return false;
-							}
-						}
-						else
-						{
-							ShowError(ErrorMSG);
-							return false;
-						}
-					}
-				}
-			}
-
-			return true;
-		}
-
-		private void HandleMovieSaveState(StreamWriter writer)
-		{
-			if (Global.MovieSession.Movie.IsActive)
-			{
-				Global.MovieSession.Movie.DumpLogIntoSavestateText(writer);
-			}
-		}
-
-		private void HandleMovieOnFrameLoop()
-		{
-			if (!Global.MovieSession.Movie.IsActive)
-			{
-				Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-			}
-
-			else if (Global.MovieSession.Movie.IsFinished)
-			{
-				if (Global.Emulator.Frame < Global.MovieSession.Movie.Frames) //This scenario can happen from rewinding (suddenly we are back in the movie, so hook back up to the movie
-				{
-					Global.MovieSession.Movie.SwitchToPlay();
-					Global.MovieSession.LatchInputFromLog();
-				}
-				else
-				{
-					Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-				}
-			}
-
-			else if (Global.MovieSession.Movie.IsPlaying)
-			{
-				if (Global.Emulator.Frame >= Global.MovieSession.Movie.Frames)
-				{
-					if (TAStudio1.IsHandleCreated && !TAStudio1.IsDisposed)
-					{
-						Global.MovieSession.Movie.CaptureState();
-						Global.MovieSession.LatchInputFromLog();
-						Global.MovieSession.Movie.CommitFrame(Global.Emulator.Frame, Global.MovieOutputHardpoint);
-					}
-					else
-					{
-						Global.MovieSession.Movie.Finish();
-					}
-				}
-				else
-				{
-					Global.MovieSession.Movie.CaptureState();
-					Global.MovieSession.LatchInputFromLog();
-					if (GlobalWinF.ClientControls["ClearFrame"])
-					{
-						Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-						ClearFrame();
-					}
-					else if (TAStudio1.IsHandleCreated && !TAStudio1.IsDisposed || Global.Config.MoviePlaybackPokeMode)
-					{
-						Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-						MnemonicsGenerator mg = new MnemonicsGenerator();
-						mg.SetSource( Global.MovieOutputHardpoint);
-						if (!mg.IsEmpty)
-						{
-							Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-							Global.MovieSession.Movie.PokeFrame(Global.Emulator.Frame, mg.GetControllersAsMnemonic());
-						}
-						else
-						{
-							Global.MovieSession.LatchInputFromLog();
-						}
-					}
-				}
-			}
-
-			else if (Global.MovieSession.Movie.IsRecording)
-			{
-				Global.MovieSession.Movie.CaptureState();
-				if (Global.MovieSession.MultiTrack.IsActive)
-				{
-					Global.MovieSession.LatchMultitrackPlayerInput(Global.MovieInputSourceAdapter, Global.MultitrackRewiringControllerAdapter);
-				}
-				else
-				{
-					Global.MovieSession.LatchInputFromPlayer(Global.MovieInputSourceAdapter);
-				}
-				//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
-				Global.MovieSession.Movie.CommitFrame(Global.Emulator.Frame, Global.MovieOutputHardpoint);
+				SetMainformMovieInfo();
+				return Global.MovieSession.HandleMovieLoadState(sr);
 			}
 		}
 
diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs
index b5cdd94a3e..389e6f0272 100644
--- a/BizHawk.MultiClient/MainForm.cs
+++ b/BizHawk.MultiClient/MainForm.cs
@@ -181,7 +181,13 @@ namespace BizHawk.MultiClient
 		public MainForm(string[] args)
 		{
 			GlobalWinF.MainForm = this;
-			Global.MovieSession = new MovieSession { Movie = new Movie(GlobalWinF.MainForm.GetEmuVersion()) };
+			Global.MovieSession = new MovieSession
+			{
+				Movie = new Movie(GlobalWinF.MainForm.GetEmuVersion()),
+				ClearSRAMCallback = ClearSaveRAM,
+				MessageCallback = GlobalWinF.OSD.AddMessage,
+				AskYesNoCallback = StateErrorAskUser
+			};
 			MainWait = new AutoResetEvent(false);
 			Icon = Properties.Resources.logo;
 			InitializeComponent();
@@ -189,7 +195,6 @@ namespace BizHawk.MultiClient
 			if (Global.Config.ShowLogWindow)
 			{
 				ShowConsole();
-				//PsxApi.StdioFixes();
 				DisplayLogWindowMenuItem.Checked = true;
 			}
 
@@ -2042,10 +2047,10 @@ namespace BizHawk.MultiClient
 
 
 				case "Toggle read-only": ToggleReadOnly(); break;
-				case "Play Movie": PlayMovie(); break;
-				case "Record Movie": RecordMovie(); break;
+				case "Play Movie": LoadPlayMovieDialog(); break;
+				case "Record Movie": LoadRecordMovieDialog(); break;
 				case "Stop Movie": StopMovie(); break;
-				case "Play from beginning": PlayMovieFromBeginning(); break;
+				case "Play from beginning": RestartMovie(); break;
 				case "Save Movie": SaveMovie(); break;
 				case "Toggle MultiTrack":
 					if (Global.MovieSession.Movie.IsActive)
@@ -2289,7 +2294,7 @@ namespace BizHawk.MultiClient
 				else if (!Global.Config.MuteFrameAdvance)
 					genSound = true;
 
-				HandleMovieOnFrameLoop();
+				Global.MovieSession.HandleMovieOnFrameLoop(GlobalWinF.ClientControls["ClearFrame"]);
 
 				coreskipaudio = GlobalWinF.ClientControls["Turbo"] && CurrAviWriter == null;
 				//=======================================
@@ -2503,7 +2508,7 @@ namespace BizHawk.MultiClient
 				// text mode savestates
 				var writer = new StreamWriter(filename);
 				Global.Emulator.SaveStateText(writer);
-				HandleMovieSaveState(writer);
+				Global.MovieSession.HandleMovieSaveState(writer);
 				if (Global.Config.SaveScreenshotWithStates)
 				{
 					writer.Write("Framebuffer ");
@@ -2545,7 +2550,7 @@ namespace BizHawk.MultiClient
 								StreamWriter sw = new StreamWriter(s);
 								// this never should have been a core's responsibility
 								sw.WriteLine("Frame {0}", Global.Emulator.Frame);
-								HandleMovieSaveState(sw);
+								Global.MovieSession.HandleMovieSaveState(sw);
 								sw.Flush();
 							});
 					}
@@ -2597,7 +2602,8 @@ namespace BizHawk.MultiClient
 							delegate(Stream s)
 							{
 								StreamReader sr = new StreamReader(s);
-								succeed = HandleMovieLoadState(sr);
+								SetMainformMovieInfo();
+								succeed = Global.MovieSession.HandleMovieLoadState(sr);
 							});
 						if (!succeed)
 							goto cleanup;
@@ -3395,7 +3401,7 @@ namespace BizHawk.MultiClient
 			CloseForm(PCEBGViewer1);
 			CloseForm(_cheats);
 			CloseForm(TI83KeyPad1);
-			CloseForm(TAStudio1);
+			CloseForm(TAStudio1); Global.MovieSession.EditorMode = false;
 			CloseForm(TraceLogger1);
 			CloseForm(VirtualPadForm1);
 #if WINDOWS
@@ -3476,10 +3482,13 @@ namespace BizHawk.MultiClient
 			if (!TAStudio1.IsHandleCreated || TAStudio1.IsDisposed)
 			{
 				TAStudio1 = new TAStudio();
+				Global.MovieSession.EditorMode = true;
 				TAStudio1.Show();
 			}
 			else
+			{
 				TAStudio1.Focus();
+			}
 		}
 
 		public void LoadVirtualPads()
@@ -4570,5 +4579,16 @@ namespace BizHawk.MultiClient
 		{
 			GlobalWinF.DisplayManager.NeedsToPaint = true;
 		}
+
+		public bool StateErrorAskUser(string title, string message)
+		{
+			var result = MessageBox.Show(message,
+				title,
+				MessageBoxButtons.YesNo,
+				MessageBoxIcon.Question
+			);
+
+			return result == DialogResult.Yes;
+		}
 	}
 }
diff --git a/BizHawk.MultiClient/tools/TAStudio.cs b/BizHawk.MultiClient/tools/TAStudio.cs
index 4025279a56..9638c845cf 100644
--- a/BizHawk.MultiClient/tools/TAStudio.cs
+++ b/BizHawk.MultiClient/tools/TAStudio.cs
@@ -356,12 +356,12 @@ namespace BizHawk.MultiClient
 
 		private void newProjectToolStripMenuItem_Click(object sender, EventArgs e)
 		{
-			GlobalWinF.MainForm.RecordMovie();
+			GlobalWinF.MainForm.LoadRecordMovieDialog();
 		}
 
 		private void openProjectToolStripMenuItem_Click(object sender, EventArgs e)
 		{
-			GlobalWinF.MainForm.PlayMovie();
+			GlobalWinF.MainForm.LoadPlayMovieDialog();
 		}
 
 		private void saveProjectToolStripMenuItem_Click(object sender, EventArgs e)