diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj
index 5ac70a84a5..8fb8f635fd 100644
--- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj
+++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj
@@ -442,6 +442,7 @@
LuaWriterColorConfig.cs
+
Form
diff --git a/BizHawk.MultiClient/movie/Movie.cs b/BizHawk.MultiClient/movie/Movie.cs
index 9d8143b8f3..de6b6204b2 100644
--- a/BizHawk.MultiClient/movie/Movie.cs
+++ b/BizHawk.MultiClient/movie/Movie.cs
@@ -531,6 +531,14 @@ namespace BizHawk.MultiClient
#region Public Misc Methods
+ public MovieLog LogDump
+ {
+ get
+ {
+ return Log;
+ }
+ }
+
public bool FrameLagged(int frame)
{
return Log.FrameLagged(frame);
diff --git a/BizHawk.MultiClient/tools/StateVisualizer.cs b/BizHawk.MultiClient/tools/StateVisualizer.cs
new file mode 100644
index 0000000000..1ef16c7227
--- /dev/null
+++ b/BizHawk.MultiClient/tools/StateVisualizer.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace BizHawk.MultiClient
+{
+ class StateVisualizer
+ {
+ public StateVisualizer()
+ {
+ TimeLine movietimeline = new TimeLine(Global.MovieSession.Movie.LogDump);
+
+ Timelines = new List();
+ Timelines.Add(movietimeline);
+
+ //Load all 10 saveslots and process
+ for (int i = 0; i < 10; i++)
+ {
+ string name = "QuickSave" + i.ToString();
+ string path = PathManager.SaveStatePrefix(Global.Game) + "." + name + ".State";
+ if (File.Exists(path))
+ {
+ Movie m = new Movie();
+ m.LoadLogFromSavestateText(path);
+ AddLog(m.LogDump, i);
+ }
+ }
+ }
+
+ private void AddLog(MovieLog log, int? slot)
+ {
+ sort();
+
+ bool added = false;
+ foreach (TimeLine timeline in Timelines)
+ {
+ if (timeline.TryAddToTimeline(log, slot))
+ {
+ added = true;
+ }
+ }
+
+ if (!added)
+ {
+ TimeLine t = new TimeLine(log);
+ Timelines.Add(t);
+ }
+ }
+
+ private void sort()
+ {
+ Timelines = Timelines.OrderByDescending(x => x.Length).ToList();
+ }
+
+ List Timelines; //MovieLogs of all savestates and the loaded movie
+
+ //Represents a single timeline that consists of at least 1 slot
+ public class TimeLine
+ {
+ public TimeLine(MovieLog log, int? slot_number = null)
+ {
+ timeline = new Event(log, slot_number);
+ subevents = new List();
+ }
+
+ private Event timeline;
+ private List subevents;
+
+ private class Event
+ {
+ public int? Slot;
+ public MovieLog Log;
+
+ public Event(MovieLog log, int? slot)
+ {
+ Slot = slot;
+ Log = log;
+ }
+
+ public Event()
+ {
+ Slot = null;
+ Log = new MovieLog();
+ }
+ }
+
+ public int Points
+ {
+ get
+ {
+ return subevents.Count + 1;
+ }
+ }
+
+ public int Length
+ {
+ get
+ {
+ return timeline.Log.Length;
+ }
+ }
+
+ public int? GetPoint(int position)
+ {
+ sort();
+ if (position < subevents.Count)
+ {
+ return subevents[position].Log.Length;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public bool TryAddToTimeline(MovieLog log, int? slot)
+ {
+ bool isdifferent = false;
+ if (log.Length < timeline.Log.Length)
+ {
+ for (int i = 0; i < log.Length; i++)
+ {
+ if (log.GetFrame(i) != timeline.Log.GetFrame(i))
+ {
+ isdifferent = true;
+ }
+ }
+
+ if (isdifferent)
+ {
+ return false;
+ }
+ else
+ {
+ subevents.Add(new Event(log, slot));
+ sort();
+ return true;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < timeline.Log.Length; i++)
+ {
+ if (log.GetFrame(i) != timeline.Log.GetFrame(i))
+ {
+ isdifferent = true;
+ }
+ }
+
+ if (isdifferent)
+ {
+ return false;
+ }
+ else
+ {
+ subevents.Add(timeline);
+ timeline = new Event(log, slot);
+ sort();
+ return true;
+ }
+ }
+ }
+
+ private void sort()
+ {
+ subevents = subevents.OrderByDescending(x => x.Log.Length).ToList();
+ }
+ }
+ }
+}