diff --git a/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/BizHawk.Client.Common/BizHawk.Client.Common.csproj
index f8eb645303..64ed20c78d 100644
--- a/BizHawk.Client.Common/BizHawk.Client.Common.csproj
+++ b/BizHawk.Client.Common/BizHawk.Client.Common.csproj
@@ -136,6 +136,10 @@
+
+
+
+
diff --git a/BizHawk.Client.Common/movie/bkm/Movie.HeaderApi.cs b/BizHawk.Client.Common/movie/bkm/Movie.HeaderApi.cs
new file mode 100644
index 0000000000..718e725d44
--- /dev/null
+++ b/BizHawk.Client.Common/movie/bkm/Movie.HeaderApi.cs
@@ -0,0 +1,104 @@
+using System.Collections.Generic;
+
+namespace BizHawk.Client.Common
+{
+ public partial class Movie : IMovie
+ {
+ public IDictionary HeaderEntries
+ {
+ get
+ {
+ return Header;
+ }
+ }
+
+ public SubtitleList Subtitles
+ {
+ get { return Header.Subtitles; }
+ }
+
+ public IList Comments
+ {
+ get { return Header.Comments; }
+ }
+
+ public string SyncSettingsJson
+ {
+ get { return Header[HeaderKeys.SYNCSETTINGS]; }
+ set { Header[HeaderKeys.SYNCSETTINGS] = value; }
+ }
+
+ public string SavestateBinaryBase64Blob
+ {
+ get { return Header.SavestateBinaryBase64Blob; }
+ set { Header.SavestateBinaryBase64Blob = value; }
+ }
+
+ public ulong Rerecords
+ {
+ get { return Header.Rerecords; }
+ set { Header.Rerecords = value; }
+ }
+
+ public bool StartsFromSavestate
+ {
+ get { return Header.StartsFromSavestate; }
+ set { Header.StartsFromSavestate = value; }
+ }
+
+ public string GameName
+ {
+ get { return Header.GameName; }
+ set { Header.GameName = value; }
+ }
+
+ public string SystemID
+ {
+ get { return Header.SystemID; }
+ set { Header.SystemID = value; }
+ }
+
+ public string Hash
+ {
+ get { return Header[HeaderKeys.SHA1]; }
+ set { Header[HeaderKeys.SHA1] = value; }
+ }
+
+ public string Author
+ {
+ get { return Header[HeaderKeys.AUTHOR]; }
+ set { Header[HeaderKeys.AUTHOR] = value; }
+ }
+
+ public string Core
+ {
+ get { return Header[HeaderKeys.CORE]; }
+ set { Header[HeaderKeys.CORE] = value; }
+ }
+
+ public string Platform
+ {
+ get { return Header[HeaderKeys.PLATFORM]; }
+ set { Header[HeaderKeys.PLATFORM] = value; }
+ }
+
+ public string BoardName
+ {
+ get { return Header[HeaderKeys.BOARDNAME]; }
+ set { Header[HeaderKeys.BOARDNAME] = value; }
+ }
+
+ public string EmulatorVersion
+ {
+ get { return Header[HeaderKeys.EMULATIONVERSION]; }
+
+ set { Header[HeaderKeys.EMULATIONVERSION] = value; }
+ }
+
+ public string FirmwareHash
+ {
+ get { return Header[HeaderKeys.FIRMWARESHA1]; }
+ set { Header[HeaderKeys.FIRMWARESHA1] = value; }
+ }
+ }
+}
diff --git a/BizHawk.Client.Common/movie/bkm/Movie.IO.cs b/BizHawk.Client.Common/movie/bkm/Movie.IO.cs
new file mode 100644
index 0000000000..98de5d620c
--- /dev/null
+++ b/BizHawk.Client.Common/movie/bkm/Movie.IO.cs
@@ -0,0 +1,252 @@
+using System;
+using System.IO;
+using System.Text;
+
+using BizHawk.Common;
+
+namespace BizHawk.Client.Common
+{
+ public partial class Movie : IMovie
+ {
+ private int _preloadFramecount; // Not a a reliable number, used for preloading (when no log has yet been loaded), this is only for quick stat compilation for dialogs such as play movie
+
+ public void SaveAs(string path)
+ {
+ Filename = path;
+ if (!Loaded)
+ {
+ return;
+ }
+
+ var directory_info = new FileInfo(Filename).Directory;
+ if (directory_info != null)
+ {
+ Directory.CreateDirectory(directory_info.FullName);
+ }
+
+ Write(Filename);
+ }
+
+ public void Save()
+ {
+ if (!Loaded || string.IsNullOrWhiteSpace(Filename))
+ {
+ return;
+ }
+
+ SaveAs(Filename);
+ _changes = false;
+ }
+
+ public void SaveBackup()
+ {
+ if (!Loaded || string.IsNullOrWhiteSpace(Filename))
+ {
+ return;
+ }
+
+ var backupName = Filename;
+ backupName = backupName.Insert(Filename.LastIndexOf("."), string.Format(".{0:yyyy-MM-dd HH.mm.ss}", DateTime.Now));
+ backupName = Path.Combine(Global.Config.PathEntries["Global", "Movie backups"].Path, Path.GetFileName(backupName));
+
+ var directory_info = new FileInfo(backupName).Directory;
+ if (directory_info != null)
+ {
+ Directory.CreateDirectory(directory_info.FullName);
+ }
+
+ Write(backupName);
+ }
+
+ public bool Load()
+ {
+ var file = new FileInfo(Filename);
+
+ if (file.Exists == false)
+ {
+ Loaded = false;
+ return false;
+ }
+
+ Header.Clear();
+ _log.Clear();
+
+ using (var sr = file.OpenText())
+ {
+ string line;
+
+ while ((line = sr.ReadLine()) != null)
+ {
+ if (line == string.Empty)
+ {
+ continue;
+ }
+
+ if (line.Contains("LoopOffset"))
+ {
+ try
+ {
+ _loopOffset = int.Parse(line.Split(new[] { ' ' }, 2)[1]);
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ else if (Header.ParseLineFromFile(line))
+ {
+ continue;
+ }
+ else if (line.StartsWith("|"))
+ {
+ _log.AppendFrame(line);
+ }
+ else
+ {
+ Header.Comments.Add(line);
+ }
+ }
+ }
+
+ Loaded = true;
+ return true;
+ }
+
+ ///
+ /// Load Header information only for displaying file information in dialogs such as play movie
+ /// TODO - consider not loading the SavestateBinaryBase64Blob key?
+ ///
+ public bool PreLoadText(HawkFile hawkFile)
+ {
+ Loaded = false;
+ var file = new FileInfo(hawkFile.CanonicalFullPath);
+
+ if (file.Exists == false)
+ {
+ return false;
+ }
+
+ Header.Clear();
+ _log.Clear();
+
+ var origStreamPosn = hawkFile.GetStream().Position;
+ hawkFile.GetStream().Position = 0; // Reset to start
+
+ // No using block because we're sharing the stream and need to give it back undisposed.
+ var sr = new StreamReader(hawkFile.GetStream());
+
+ for (; ; )
+ {
+ //read to first space (key/value delimeter), or pipe, or EOF
+ int first = sr.Read();
+
+ if (first == -1) break; //EOF
+ else if (first == '|') //pipe: begin input log
+ {
+ //NOTE - this code is a bit convoluted due to its predating the basic outline of the parser which was upgraded in may 2014
+ string line = '|' + sr.ReadLine();
+
+ //how many bytes are left, total?
+ long remain = sr.BaseStream.Length - sr.BaseStream.Position;
+
+ //try to find out whether we use \r\n or \n
+ //but only look for 1K characters.
+ bool usesR = false;
+ for (int i = 0; i < 1024; i++)
+ {
+ int c = sr.Read();
+ if (c == -1)
+ break;
+ if (c == '\r')
+ {
+ usesR = true;
+ break;
+ }
+ if (c == '\n')
+ break;
+ }
+
+ int lineLen = line.Length + 1; //account for \n
+ if (usesR) lineLen++; //account for \r
+
+ _preloadFramecount = (int)(remain / lineLen); //length is remaining bytes / length per line
+ _preloadFramecount++; //account for the current line
+ break;
+ }
+ else
+ {
+ //a header line. finish reading key token, to make sure it isn't one of the FORBIDDEN keys
+ StringBuilder sbLine = new StringBuilder();
+ sbLine.Append((char)first);
+ for (; ; )
+ {
+ int c = sr.Read();
+ if (c == -1) break;
+ if (c == '\n') break;
+ if (c == ' ') break;
+ sbLine.Append((char)c);
+ }
+
+ var line = sbLine.ToString();
+
+ //ignore these suckers, theyre way too big for preloading. seriously, we will get out of memory errors.
+ var skip = line == HeaderKeys.SAVESTATEBINARYBASE64BLOB;
+
+ if (skip)
+ {
+ //skip remainder of the line
+ sr.DiscardBufferedData();
+ var stream = sr.BaseStream;
+ for (; ; )
+ {
+ int c = stream.ReadByte();
+ if (c == -1) break;
+ if (c == '\n') break;
+ }
+ //proceed to next line
+ continue;
+ }
+
+
+ string remainder = sr.ReadLine();
+ sbLine.Append(' ');
+ sbLine.Append(remainder);
+ line = sbLine.ToString();
+
+ if (string.IsNullOrWhiteSpace(line) || Header.ParseLineFromFile(line))
+ {
+ continue;
+ }
+
+ Header.Comments.Add(line);
+ }
+ }
+
+ hawkFile.GetStream().Position = origStreamPosn;
+
+ return true;
+ }
+
+ private void Write(string fn)
+ {
+ using (var fs = new FileStream(fn, FileMode.Create, FileAccess.Write, FileShare.Read))
+ {
+ using (var sw = new StreamWriter(fs))
+ {
+ sw.Write(Header.ToString());
+
+ // TODO: clean this up
+ if (_loopOffset.HasValue)
+ {
+ sw.WriteLine("LoopOffset " + _loopOffset);
+ }
+
+ foreach (var input in _log)
+ {
+ sw.WriteLine(input);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/BizHawk.Client.Common/movie/bkm/Movie.InputLog.cs b/BizHawk.Client.Common/movie/bkm/Movie.InputLog.cs
new file mode 100644
index 0000000000..c31e11fda0
--- /dev/null
+++ b/BizHawk.Client.Common/movie/bkm/Movie.InputLog.cs
@@ -0,0 +1,291 @@
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+namespace BizHawk.Client.Common
+{
+ public partial class Movie : IMovie
+ {
+ private readonly MovieLog _log = new MovieLog();
+
+ public string GetInputLog()
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("[Input]");
+
+ foreach (var record in _log)
+ {
+ sb.AppendLine(record);
+ }
+
+ sb.AppendLine("[/Input]");
+
+ return sb.ToString();
+ }
+
+ public bool ExtractInputLog(TextReader reader, out string errorMessage)
+ {
+ errorMessage = string.Empty;
+ int? stateFrame = null;
+
+ // We are in record mode so replace the movie log with the one from the savestate
+ if (!Global.MovieSession.MultiTrack.IsActive)
+ {
+ if (Global.Config.EnableBackupMovies && _makeBackup && _log.Length > 0)
+ {
+ SaveBackup();
+ _makeBackup = false;
+ }
+
+ _log.Clear();
+ while (true)
+ {
+ var line = reader.ReadLine();
+ if (line == null)
+ {
+ break;
+ }
+
+ if (line.Trim() == string.Empty || line == "[Input]")
+ {
+ continue;
+ }
+
+ if (line == "[/Input]")
+ {
+ break;
+ }
+
+ if (line.Contains("Frame 0x")) // NES stores frame count in hex, yay
+ {
+ var strs = line.Split('x');
+ try
+ {
+ stateFrame = int.Parse(strs[1], NumberStyles.HexNumber);
+ }
+ catch
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ return false;
+ }
+ }
+ else if (line.Contains("Frame "))
+ {
+ var strs = line.Split(' ');
+ try
+ {
+ stateFrame = int.Parse(strs[1]);
+ }
+ catch
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ return false;
+ }
+ }
+ else if (line[0] == '|')
+ {
+ _log.AppendFrame(line);
+ }
+ }
+ }
+ else
+ {
+ var i = 0;
+ while (true)
+ {
+ var line = reader.ReadLine();
+ if (line == null)
+ {
+ break;
+ }
+
+ if (line.Trim() == string.Empty || line == "[Input]")
+ {
+ continue;
+ }
+
+ if (line == "[/Input]")
+ {
+ break;
+ }
+
+ if (line.Contains("Frame 0x")) // NES stores frame count in hex, yay
+ {
+ var strs = line.Split('x');
+ try
+ {
+ stateFrame = int.Parse(strs[1], NumberStyles.HexNumber);
+ }
+ catch
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ return false;
+ }
+ }
+ else if (line.Contains("Frame "))
+ {
+ var strs = line.Split(' ');
+ try
+ {
+ stateFrame = int.Parse(strs[1]);
+ }
+ catch
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ return false;
+ }
+ }
+ else if (line.StartsWith("|"))
+ {
+ _log.SetFrameAt(i, line);
+ i++;
+ }
+ }
+ }
+
+ if (!stateFrame.HasValue)
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ }
+
+ var stateFramei = stateFrame ?? 0;
+
+ if (stateFramei > 0 && stateFramei < _log.Length)
+ {
+ if (!Global.Config.VBAStyleMovieLoadState)
+ {
+ _log.TruncateStates(stateFramei);
+ _log.TruncateMovie(stateFramei);
+ }
+ }
+ else if (stateFramei > _log.Length) // Post movie savestate
+ {
+ if (!Global.Config.VBAStyleMovieLoadState)
+ {
+ _log.TruncateStates(_log.Length);
+ _log.TruncateMovie(_log.Length);
+ }
+
+ _mode = Moviemode.Finished;
+ }
+
+ if (IsCountingRerecords)
+ {
+ Rerecords++;
+ }
+
+ return true;
+ }
+
+ public bool CheckTimeLines(TextReader reader, 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();
+ var stateFrame = 0;
+ while (true)
+ {
+ var line = reader.ReadLine();
+ if (line == null)
+ {
+ return false;
+ }
+
+ if (line.Trim() == string.Empty)
+ {
+ continue;
+ }
+
+ if (line.Contains("Frame 0x")) // NES stores frame count in hex, yay
+ {
+ var strs = line.Split('x');
+ try
+ {
+ stateFrame = int.Parse(strs[1], NumberStyles.HexNumber);
+ }
+ catch
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ return false;
+ }
+ }
+ else if (line.Contains("Frame "))
+ {
+ var strs = line.Split(' ');
+ try
+ {
+ stateFrame = int.Parse(strs[1]);
+ }
+ catch
+ {
+ errorMessage = "Savestate Frame number failed to parse";
+ return false;
+ }
+ }
+ else if (line == "[Input]")
+ {
+ continue;
+ }
+ else if (line == "[/Input]")
+ {
+ break;
+ }
+ else if (line[0] == '|')
+ {
+ log.AppendFrame(line);
+ }
+ }
+
+ if (stateFrame == 0)
+ {
+ stateFrame = log.Length; // In case the frame count failed to parse, revert to using the entire state input log
+ }
+
+ if (_log.Length < stateFrame)
+ {
+ if (IsFinished)
+ {
+ return true;
+ }
+
+ errorMessage = "The savestate is from frame "
+ + log.Length
+ + " which is greater than the current movie length of "
+ + _log.Length;
+
+ return false;
+ }
+
+ for (var i = 0; i < stateFrame; i++)
+ {
+ if (_log[i] != log[i])
+ {
+ errorMessage = "The savestate input does not match the movie input at frame "
+ + (i + 1)
+ + ".";
+
+ return false;
+ }
+ }
+
+ if (stateFrame > log.Length) // stateFrame is greater than state input log, so movie finished mode
+ {
+ if (_mode == Moviemode.Play || _mode == Moviemode.Finished)
+ {
+ _mode = Moviemode.Finished;
+ return true;
+ }
+
+ return false;
+ }
+
+ if (_mode == Moviemode.Finished)
+ {
+ _mode = Moviemode.Play;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/BizHawk.Client.Common/movie/bkm/Movie.ModeApi.cs b/BizHawk.Client.Common/movie/bkm/Movie.ModeApi.cs
new file mode 100644
index 0000000000..f77fd0802a
--- /dev/null
+++ b/BizHawk.Client.Common/movie/bkm/Movie.ModeApi.cs
@@ -0,0 +1,97 @@
+namespace BizHawk.Client.Common
+{
+ public partial class Movie : IMovie
+ {
+ private enum Moviemode { Inactive, Play, Record, Finished }
+
+ private Moviemode _mode = Moviemode.Inactive;
+
+ public bool IsPlaying
+ {
+ get { return _mode == Moviemode.Play || _mode == Moviemode.Finished; }
+ }
+
+ public bool IsRecording
+ {
+ get { return _mode == Moviemode.Record; }
+ }
+
+ public bool IsActive
+ {
+ get { return _mode != Moviemode.Inactive; }
+ }
+
+ public bool IsFinished
+ {
+ get { return _mode == Moviemode.Finished; }
+ }
+
+ public void StartNewRecording()
+ {
+ // adelikat: ClearSaveRam shouldn't be here at all most likely, especially considering this is an implementation detail
+ // If Starting a new recording requires clearing sram it shoudl be done at a higher layer and not rely on all IMovies doing this
+ // Haven't removed it yet because I coudln't guarantee that power-on movies coudl live without it
+ // And the immediate fire is that Savestate movies are breaking
+ if (!StartsFromSavestate)
+ {
+ Global.Emulator.ClearSaveRam();
+ }
+
+ _mode = Moviemode.Record;
+ if (Global.Config.EnableBackupMovies && _makeBackup && _log.Length > 0)
+ {
+ SaveBackup();
+ _makeBackup = false;
+ }
+
+ _log.Clear();
+ }
+
+ public void StartNewPlayback()
+ {
+ // See StartNewRecording for details as to why this savestate check is here
+ if (!StartsFromSavestate)
+ {
+ Global.Emulator.ClearSaveRam();
+ }
+
+ _mode = Moviemode.Play;
+ }
+
+ public void SwitchToRecord()
+ {
+ _mode = Moviemode.Record;
+ }
+
+ public void SwitchToPlay()
+ {
+ _mode = Moviemode.Play;
+ Save();
+ }
+
+ public void Stop(bool saveChanges = true)
+ {
+ if (saveChanges)
+ {
+ if (_mode == Moviemode.Record || _changes)
+ {
+ Save();
+ }
+ }
+
+ _changes = false;
+ _mode = Moviemode.Inactive;
+ }
+
+ ///
+ /// If a movie is in playback mode, this will set it to movie finished
+ ///
+ private void Finish()
+ {
+ if (_mode == Moviemode.Play)
+ {
+ _mode = Moviemode.Finished;
+ }
+ }
+ }
+}
diff --git a/BizHawk.Client.Common/movie/bkm/Movie.cs b/BizHawk.Client.Common/movie/bkm/Movie.cs
index be7da8ed74..d10f9a8fc9 100644
--- a/BizHawk.Client.Common/movie/bkm/Movie.cs
+++ b/BizHawk.Client.Common/movie/bkm/Movie.cs
@@ -1,23 +1,12 @@
using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Text;
-
-using BizHawk.Common;
using BizHawk.Emulation.Common;
-
namespace BizHawk.Client.Common
{
- public class Movie : IMovie
+ public partial class Movie : IMovie
{
- private readonly MovieLog _log = new MovieLog();
private readonly PlatformFrameRates _frameRates = new PlatformFrameRates();
private bool _makeBackup = true;
-
- private Moviemode _mode = Moviemode.Inactive;
- private int _preloadFramecount; // Not a a reliable number, used for preloading (when no log has yet been loaded), this is only for quick stat compilation for dialogs such as play movie
private bool _changes;
private int? _loopOffset;
@@ -36,212 +25,20 @@ namespace BizHawk.Client.Common
Filename = string.Empty;
_preloadFramecount = 0;
StartsFromSavestate = startsFromSavestate;
-
+
IsCountingRerecords = true;
_mode = Moviemode.Inactive;
_makeBackup = true;
}
- private enum Moviemode { Inactive, Play, Record, Finished }
-
#region Properties
- public SubtitleList Subtitles
- {
- get { return Header.Subtitles; }
- }
-
- public IList Comments
- {
- get { return Header.Comments; }
- }
-
- public string SyncSettingsJson
- {
- get
- {
- return Header[HeaderKeys.SYNCSETTINGS];
- }
-
- set
- {
- Header[HeaderKeys.SYNCSETTINGS] = value;
- }
- }
-
- public string SavestateBinaryBase64Blob
- {
- get
- {
- return Header.SavestateBinaryBase64Blob;
- }
-
- set
- {
- Header.SavestateBinaryBase64Blob = value;
- }
- }
-
- public ulong Rerecords
- {
- get
- {
- return Header.Rerecords;
- }
-
- set
- {
- Header.Rerecords = value;
- }
- }
-
- public bool StartsFromSavestate
- {
- get
- {
- return Header.StartsFromSavestate;
- }
-
- set
- {
- Header.StartsFromSavestate = value;
- }
- }
-
- public string GameName
- {
- get
- {
- return Header.GameName;
- }
-
- set
- {
- Header.GameName = value;
- }
- }
-
- public string SystemID
- {
- get
- {
- return Header.SystemID;
- }
-
- set
- {
- Header.SystemID = value;
- }
- }
-
- public string Hash
- {
- get
- {
- return Header[HeaderKeys.SHA1];
- }
-
- set
- {
- Header[HeaderKeys.SHA1] = value;
- }
- }
-
- public string Author
- {
- get
- {
- return Header[HeaderKeys.AUTHOR];
- }
-
- set
- {
- Header[HeaderKeys.AUTHOR] = value;
- }
- }
-
- public string Core
- {
- get
- {
- return Header[HeaderKeys.CORE];
- }
-
- set
- {
- Header[HeaderKeys.CORE] = value;
- }
- }
-
- public string Platform
- {
- get
- {
- return Header[HeaderKeys.PLATFORM];
- }
-
- set
- {
- Header[HeaderKeys.PLATFORM] = value;
- }
- }
-
- public string BoardName
- {
- get
- {
- return Header[HeaderKeys.BOARDNAME];
- }
-
- set
- {
- Header[HeaderKeys.BOARDNAME] = value;
- }
- }
-
- public string EmulatorVersion
- {
- get
- {
- return Header[HeaderKeys.EMULATIONVERSION];
- }
-
- set
- {
- Header[HeaderKeys.EMULATIONVERSION] = value;
- }
- }
-
- public string FirmwareHash
- {
- get
- {
- return Header[HeaderKeys.FIRMWARESHA1];
- }
-
- set
- {
- Header[HeaderKeys.FIRMWARESHA1] = value;
- }
- }
-
- public IDictionary HeaderEntries
- {
- get
- {
- return Header;
- }
- }
-
public string PreferredExtension { get { return "bkm"; } }
-
public MovieHeader Header { get; private set; }
-
public string Filename { get; set; }
public bool IsCountingRerecords { get; set; }
-
public bool Loaded { get; private set; }
-
+
public int InputLogLength
{
get { return _log.Length; }
@@ -265,325 +62,11 @@ namespace BizHawk.Client.Common
}
}
- #endregion
-
- #region Mode API
-
- public bool IsPlaying
- {
- get { return _mode == Moviemode.Play || _mode == Moviemode.Finished; }
- }
-
- public bool IsRecording
- {
- get { return _mode == Moviemode.Record; }
- }
-
- public bool IsActive
- {
- get { return _mode != Moviemode.Inactive; }
- }
-
- public bool IsFinished
- {
- get { return _mode == Moviemode.Finished; }
- }
-
public bool Changes
{
get { return _changes; }
}
- public void StartNewRecording()
- {
- // adelikat: ClearSaveRam shouldn't be here at all most likely, especially considering this is an implementation detail
- // If Starting a new recording requires clearing sram it shoudl be done at a higher layer and not rely on all IMovies doing this
- // Haven't removed it yet because I coudln't guarantee that power-on movies coudl live without it
- // And the immediate fire is that Savestate movies are breaking
- if (!StartsFromSavestate)
- {
- Global.Emulator.ClearSaveRam();
- }
-
- _mode = Moviemode.Record;
- if (Global.Config.EnableBackupMovies && _makeBackup && _log.Length > 0)
- {
- SaveBackup();
- _makeBackup = false;
- }
-
- _log.Clear();
- }
-
- public void StartNewPlayback()
- {
- // See StartNewRecording for details as to why this savestate check is here
- if (!StartsFromSavestate)
- {
- Global.Emulator.ClearSaveRam();
- }
-
- _mode = Moviemode.Play;
- }
-
- public void SwitchToRecord()
- {
- _mode = Moviemode.Record;
- }
-
- public void SwitchToPlay()
- {
- _mode = Moviemode.Play;
- Save();
- }
-
- public void Stop(bool saveChanges = true)
- {
- if (saveChanges)
- {
- if (_mode == Moviemode.Record || _changes)
- {
- Save();
- }
- }
-
- _changes = false;
- _mode = Moviemode.Inactive;
- }
-
- ///
- /// If a movie is in playback mode, this will set it to movie finished
- ///
- private void Finish()
- {
- if (_mode == Moviemode.Play)
- {
- _mode = Moviemode.Finished;
- }
- }
-
- #endregion
-
- #region Public File Handling
-
- public void SaveAs(string path)
- {
- Filename = path;
- if (!Loaded)
- {
- return;
- }
-
- var directory_info = new FileInfo(Filename).Directory;
- if (directory_info != null)
- {
- Directory.CreateDirectory(directory_info.FullName);
- }
-
- Write(Filename);
- }
-
- public void Save()
- {
- if (!Loaded || string.IsNullOrWhiteSpace(Filename))
- {
- return;
- }
-
- SaveAs(Filename);
- _changes = false;
- }
-
- public void SaveBackup()
- {
- if (!Loaded || string.IsNullOrWhiteSpace(Filename))
- {
- return;
- }
-
- var backupName = Filename;
- backupName = backupName.Insert(Filename.LastIndexOf("."), string.Format(".{0:yyyy-MM-dd HH.mm.ss}", DateTime.Now));
- backupName = Path.Combine(Global.Config.PathEntries["Global", "Movie backups"].Path, Path.GetFileName(backupName) ?? string.Empty);
-
- var directory_info = new FileInfo(backupName).Directory;
- if (directory_info != null)
- {
- Directory.CreateDirectory(directory_info.FullName);
- }
-
- Write(backupName);
- }
-
- ///
- /// Load Header information only for displaying file information in dialogs such as play movie
- /// TODO - consider not loading the SavestateBinaryBase64Blob key?
- ///
- public bool PreLoadText(HawkFile hawkFile)
- {
- Loaded = false;
- var file = new FileInfo(hawkFile.CanonicalFullPath);
-
- if (file.Exists == false)
- {
- return false;
- }
-
- Header.Clear();
- _log.Clear();
-
- var origStreamPosn = hawkFile.GetStream().Position;
- hawkFile.GetStream().Position = 0; // Reset to start
-
- // No using block because we're sharing the stream and need to give it back undisposed.
- var sr = new StreamReader(hawkFile.GetStream());
-
- for(;;)
- {
- //read to first space (key/value delimeter), or pipe, or EOF
- int first = sr.Read();
-
- if (first == -1) break; //EOF
- else if(first == '|') //pipe: begin input log
- {
- //NOTE - this code is a bit convoluted due to its predating the basic outline of the parser which was upgraded in may 2014
- string line = '|' + sr.ReadLine();
-
- //how many bytes are left, total?
- long remain = sr.BaseStream.Length - sr.BaseStream.Position;
-
- //try to find out whether we use \r\n or \n
- //but only look for 1K characters.
- bool usesR = false;
- for (int i = 0; i < 1024; i++)
- {
- int c = sr.Read();
- if (c == -1)
- break;
- if (c == '\r')
- {
- usesR = true;
- break;
- }
- if (c == '\n')
- break;
- }
-
- int lineLen = line.Length + 1; //account for \n
- if (usesR) lineLen++; //account for \r
-
- _preloadFramecount = (int)(remain / lineLen); //length is remaining bytes / length per line
- _preloadFramecount++; //account for the current line
- break;
- }
- else
- {
- //a header line. finish reading key token, to make sure it isn't one of the FORBIDDEN keys
- StringBuilder sbLine = new StringBuilder();
- sbLine.Append((char)first);
- for (; ; )
- {
- int c = sr.Read();
- if (c == -1) break;
- if (c == '\n') break;
- if (c == ' ') break;
- sbLine.Append((char)c);
- }
-
- string line = sbLine.ToString();
-
- //ignore these suckers, theyre way too big for preloading. seriously, we will get out of memory errors.
- bool skip = false;
- if (line == HeaderKeys.SAVESTATEBINARYBASE64BLOB) skip = true;
-
- if (skip)
- {
- //skip remainder of the line
- sr.DiscardBufferedData();
- var stream = sr.BaseStream;
- for (; ; )
- {
- int c = stream.ReadByte();
- if (c == -1) break;
- if (c == '\n') break;
- }
- //proceed to next line
- continue;
- }
-
-
- string remainder = sr.ReadLine();
- sbLine.Append(' ');
- sbLine.Append(remainder);
- line = sbLine.ToString();
-
- if (string.IsNullOrWhiteSpace(line) || Header.ParseLineFromFile(line))
- {
- continue;
- }
-
- Header.Comments.Add(line);
- }
- }
-
- hawkFile.GetStream().Position = origStreamPosn;
-
- return true;
- }
-
- public bool Load()
- {
- var file = new FileInfo(Filename);
-
- if (file.Exists == false)
- {
- Loaded = false;
- return false;
- }
-
- Header.Clear();
- _log.Clear();
-
- using (var sr = file.OpenText())
- {
- string line;
-
- while ((line = sr.ReadLine()) != null)
- {
- if (line == string.Empty)
- {
- continue;
- }
-
- if (line.Contains("LoopOffset"))
- {
- try
- {
- _loopOffset = int.Parse(line.Split(new[] { ' ' }, 2)[1]);
- }
- catch (Exception)
- {
- continue;
- }
- }
- else if (Header.ParseLineFromFile(line))
- {
- continue;
- }
- else if (line.StartsWith("|"))
- {
- _log.AppendFrame(line);
- }
- else
- {
- Header.Comments.Add(line);
- }
- }
- }
-
- Loaded = true;
- return true;
- }
-
#endregion
#region Public Log Editing
@@ -643,10 +126,6 @@ namespace BizHawk.Client.Common
_changes = true;
}
- #endregion
-
- #region Public Misc Methods
-
public void PokeFrame(int frame, IController source)
{
var mg = new MnemonicsGenerator();
@@ -676,176 +155,6 @@ namespace BizHawk.Client.Common
_log.SetFrameAt(frame, mg.GetControllersAsMnemonic());
}
- public string GetInputLog()
- {
- var sb = new StringBuilder();
-
- sb.AppendLine("[Input]");
-
- foreach (var record in _log)
- {
- sb.AppendLine(record);
- }
-
- sb.AppendLine("[/Input]");
-
- return sb.ToString();
- }
-
- public bool ExtractInputLog(TextReader reader, out string errorMessage)
- {
- errorMessage = string.Empty;
- int? stateFrame = null;
-
- // We are in record mode so replace the movie log with the one from the savestate
- if (!Global.MovieSession.MultiTrack.IsActive)
- {
- if (Global.Config.EnableBackupMovies && _makeBackup && _log.Length > 0)
- {
- SaveBackup();
- _makeBackup = false;
- }
-
- _log.Clear();
- while (true)
- {
- var line = reader.ReadLine();
- if (line == null)
- {
- break;
- }
-
- if (line.Trim() == string.Empty || line == "[Input]")
- {
- continue;
- }
-
- if (line == "[/Input]")
- {
- break;
- }
-
- if (line.Contains("Frame 0x")) // NES stores frame count in hex, yay
- {
- var strs = line.Split('x');
- try
- {
- stateFrame = int.Parse(strs[1], NumberStyles.HexNumber);
- }
- catch
- {
- errorMessage = "Savestate Frame number failed to parse";
- return false;
- }
- }
- else if (line.Contains("Frame "))
- {
- var strs = line.Split(' ');
- try
- {
- stateFrame = int.Parse(strs[1]);
- }
- catch
- {
- errorMessage = "Savestate Frame number failed to parse";
- return false;
- }
- }
- else if (line[0] == '|')
- {
- _log.AppendFrame(line);
- }
- }
- }
- else
- {
- var i = 0;
- while (true)
- {
- var line = reader.ReadLine();
- if (line == null)
- {
- break;
- }
-
- if (line.Trim() == string.Empty || line == "[Input]")
- {
- continue;
- }
-
- if (line == "[/Input]")
- {
- break;
- }
-
- if (line.Contains("Frame 0x")) // NES stores frame count in hex, yay
- {
- var strs = line.Split('x');
- try
- {
- stateFrame = int.Parse(strs[1], NumberStyles.HexNumber);
- }
- catch
- {
- errorMessage = "Savestate Frame number failed to parse";
- return false;
- }
- }
- else if (line.Contains("Frame "))
- {
- var strs = line.Split(' ');
- try
- {
- stateFrame = int.Parse(strs[1]);
- }
- catch
- {
- errorMessage = "Savestate Frame number failed to parse";
- return false;
- }
- }
- else if (line.StartsWith("|"))
- {
- _log.SetFrameAt(i, line);
- i++;
- }
- }
- }
-
- if (!stateFrame.HasValue)
- {
- errorMessage = "Savestate Frame number failed to parse";
- }
-
- var stateFramei = stateFrame ?? 0;
-
- if (stateFramei > 0 && stateFramei < _log.Length)
- {
- if (!Global.Config.VBAStyleMovieLoadState)
- {
- _log.TruncateStates(stateFramei);
- _log.TruncateMovie(stateFramei);
- }
- }
- else if (stateFramei > _log.Length) // Post movie savestate
- {
- if (!Global.Config.VBAStyleMovieLoadState)
- {
- _log.TruncateStates(_log.Length);
- _log.TruncateMovie(_log.Length);
- }
-
- _mode = Moviemode.Finished;
- }
-
- if (IsCountingRerecords)
- {
- Rerecords++;
- }
-
- return true;
- }
-
public TimeSpan Time
{
get
@@ -860,142 +169,8 @@ namespace BizHawk.Client.Common
}
}
- public bool CheckTimeLines(TextReader reader, 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();
- var stateFrame = 0;
- while (true)
- {
- var line = reader.ReadLine();
- if (line == null)
- {
- return false;
- }
-
- if (line.Trim() == string.Empty)
- {
- continue;
- }
-
- if (line.Contains("Frame 0x")) // NES stores frame count in hex, yay
- {
- var strs = line.Split('x');
- try
- {
- stateFrame = int.Parse(strs[1], NumberStyles.HexNumber);
- }
- catch
- {
- errorMessage = "Savestate Frame number failed to parse";
- return false;
- }
- }
- else if (line.Contains("Frame "))
- {
- var strs = line.Split(' ');
- try
- {
- stateFrame = int.Parse(strs[1]);
- }
- catch
- {
- errorMessage = "Savestate Frame number failed to parse";
- return false;
- }
- }
- else if (line == "[Input]")
- {
- continue;
- }
- else if (line == "[/Input]")
- {
- break;
- }
- else if (line[0] == '|')
- {
- log.AppendFrame(line);
- }
- }
-
- if (stateFrame == 0)
- {
- stateFrame = log.Length; // In case the frame count failed to parse, revert to using the entire state input log
- }
-
- if (_log.Length < stateFrame)
- {
- if (IsFinished)
- {
- return true;
- }
-
- errorMessage = "The savestate is from frame "
- + log.Length
- + " which is greater than the current movie length of "
- + _log.Length;
-
- return false;
- }
-
- for (var i = 0; i < stateFrame; i++)
- {
- if (_log[i] != log[i])
- {
- errorMessage = "The savestate input does not match the movie input at frame "
- + (i + 1)
- + ".";
-
- return false;
- }
- }
-
- if (stateFrame > log.Length) // stateFrame is greater than state input log, so movie finished mode
- {
- if (_mode == Moviemode.Play || _mode == Moviemode.Finished)
- {
- _mode = Moviemode.Finished;
- return true;
- }
-
- return false;
- }
-
- if (_mode == Moviemode.Finished)
- {
- _mode = Moviemode.Play;
- }
-
- return true;
- }
-
#endregion
- #region Helpers
-
- private void Write(string fn)
- {
- using (var fs = new FileStream(fn, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- using (var sw = new StreamWriter(fs))
- {
- sw.Write(Header.ToString());
-
- // TODO: clean this up
- if (_loopOffset.HasValue)
- {
- sw.WriteLine("LoopOffset " + _loopOffset);
- }
-
- foreach (var input in _log)
- {
- sw.WriteLine(input);
- }
- }
- }
- }
-
private double GetSeconds(int frameCount)
{
double frames = frameCount;
@@ -1023,7 +198,5 @@ namespace BizHawk.Client.Common
return _frameRates[system, pal];
}
}
-
- #endregion
}
}
\ No newline at end of file