using System; using System.Collections.Generic; using System.IO; using System.Linq; using Newtonsoft.Json; using BizHawk.Bizware.BizwareGL; namespace BizHawk.Client.Common { public class TasBranch { public int Frame { get; set; } public byte[] CoreData { get; set; } public IStringLog InputLog { get; set; } public BitmapBuffer CoreFrameBuffer { get; set; } public BitmapBuffer OSDFrameBuffer { get; set; } public TasMovieChangeLog ChangeLog { get; set; } public DateTime TimeStamp { get; set; } public TasMovieMarkerList Markers { get; set; } public Guid Uuid { get; set; } public string UserText { get; set; } public TasBranch Clone() => (TasBranch)MemberwiseClone(); } public interface ITasBranchCollection : IList { int Current { get; set; } string NewBranchText { get; set; } void Swap(int b1, int b2); void Replace(TasBranch old, TasBranch newBranch); void Save(BinaryStateSaver bs); void Load(BinaryStateLoader bl, ITasMovie movie); } public class TasBranchCollection : List, ITasBranchCollection { private readonly ITasMovie _movie; public TasBranchCollection(ITasMovie movie) { _movie = movie; } public int Current { get; set; } = -1; public string NewBranchText { get; set; } = ""; public void Swap(int b1, int b2) { var branch = this[b1]; if (b2 >= Count) { b2 = Count - 1; } Remove(branch); Insert(b2, branch); _movie.FlagChanges(); } public void Replace(TasBranch old, TasBranch newBranch) { int index = IndexOf(old); newBranch.Uuid = old.Uuid; if (newBranch.UserText == "") { newBranch.UserText = old.UserText; } this[index] = newBranch; _movie.FlagChanges(); } public new TasBranch this[int index] { get => index >= Count || index < 0 ? null : base [index]; set => base[index] = value; } public new void Add(TasBranch item) { if (item == null) { throw new ArgumentNullException($"{nameof(item)} cannot be null"); } if (item.Uuid == Guid.Empty) { item.Uuid = Guid.NewGuid(); } base.Add(item); _movie.FlagChanges(); } public new bool Remove(TasBranch item) { var result = base.Remove(item); if (result) { _movie.FlagChanges(); } return result; } public void Save(BinaryStateSaver bs) { var nheader = new IndexedStateLump(BinaryStateLump.BranchHeader); var ncore = new IndexedStateLump(BinaryStateLump.BranchCoreData); var ninput = new IndexedStateLump(BinaryStateLump.BranchInputLog); var nframebuffer = new IndexedStateLump(BinaryStateLump.BranchFrameBuffer); var ncoreframebuffer = new IndexedStateLump(BinaryStateLump.BranchCoreFrameBuffer); var nmarkers = new IndexedStateLump(BinaryStateLump.BranchMarkers); var nusertext = new IndexedStateLump(BinaryStateLump.BranchUserText); foreach (var b in this) { bs.PutLump(nheader, delegate(TextWriter tw) { // if this header needs more stuff in it, handle it sensibly tw.WriteLine(JsonConvert.SerializeObject(new { b.Frame, b.TimeStamp, UniqueIdentifier = b.Uuid })); }); bs.PutLump(ncore, delegate(Stream s) { s.Write(b.CoreData, 0, b.CoreData.Length); }); bs.PutLump(ninput, delegate(TextWriter tw) { int todo = b.InputLog.Count; for (int i = 0; i < todo; i++) { tw.WriteLine(b.InputLog[i]); } }); bs.PutLump(nframebuffer, delegate(Stream s) { var vp = new BitmapBufferVideoProvider(b.OSDFrameBuffer); QuickBmpFile.Save(vp, s, b.OSDFrameBuffer.Width, b.OSDFrameBuffer.Height); }); bs.PutLump(ncoreframebuffer, delegate(Stream s) { var vp = new BitmapBufferVideoProvider(b.CoreFrameBuffer); QuickBmpFile.Save(vp, s, b.CoreFrameBuffer.Width, b.CoreFrameBuffer.Height); }); bs.PutLump(nmarkers, delegate(TextWriter tw) { tw.WriteLine(b.Markers.ToString()); }); bs.PutLump(nusertext, delegate(TextWriter tw) { tw.WriteLine(b.UserText); }); nheader.Increment(); ncore.Increment(); ninput.Increment(); nframebuffer.Increment(); ncoreframebuffer.Increment(); nmarkers.Increment(); nusertext.Increment(); } } public void Load(BinaryStateLoader bl, ITasMovie movie) { var nheader = new IndexedStateLump(BinaryStateLump.BranchHeader); var ncore = new IndexedStateLump(BinaryStateLump.BranchCoreData); var ninput = new IndexedStateLump(BinaryStateLump.BranchInputLog); var nframebuffer = new IndexedStateLump(BinaryStateLump.BranchFrameBuffer); var ncoreframebuffer = new IndexedStateLump(BinaryStateLump.BranchCoreFrameBuffer); var nmarkers = new IndexedStateLump(BinaryStateLump.BranchMarkers); var nusertext = new IndexedStateLump(BinaryStateLump.BranchUserText); Clear(); while (true) { var b = new TasBranch(); if (!bl.GetLump(nheader, false, delegate(TextReader tr) { var header = (dynamic)JsonConvert.DeserializeObject(tr.ReadLine()); b.Frame = (int)header.Frame; var timestamp = header.TimeStamp; if (timestamp != null) { b.TimeStamp = (DateTime)timestamp; } else { b.TimeStamp = DateTime.Now; } var identifier = header.UniqueIdentifier; if (identifier != null) { b.Uuid = (Guid)identifier; } else { b.Uuid = Guid.NewGuid(); } })) { return; } bl.GetLump(ncore, true, delegate(Stream s, long length) { b.CoreData = new byte[length]; s.Read(b.CoreData, 0, b.CoreData.Length); }); bl.GetLump(ninput, true, delegate(TextReader tr) { b.InputLog = StringLogUtil.MakeStringLog(); string line; while ((line = tr.ReadLine()) != null) { b.InputLog.Add(line); } }); bl.GetLump(nframebuffer, true, delegate(Stream s, long length) { var vp = new QuickBmpFile.LoadedBMP(); QuickBmpFile.Load(vp, s); b.OSDFrameBuffer = new BitmapBuffer(vp.BufferWidth, vp.BufferHeight, vp.VideoBuffer); }); bl.GetLump(ncoreframebuffer, false, delegate(Stream s, long length) { var vp = new QuickBmpFile.LoadedBMP(); QuickBmpFile.Load(vp, s); b.CoreFrameBuffer = new BitmapBuffer(vp.BufferWidth, vp.BufferHeight, vp.VideoBuffer); }); b.Markers = new TasMovieMarkerList(movie); bl.GetLump(nmarkers, false, delegate(TextReader tr) { string line; while ((line = tr.ReadLine()) != null) { if (!string.IsNullOrWhiteSpace(line)) { b.Markers.Add(new TasMovieMarker(line)); } } }); bl.GetLump(nusertext, false, delegate(TextReader tr) { string line; if ((line = tr.ReadLine()) != null) { if (!string.IsNullOrWhiteSpace(line)) { b.UserText = line; } } }); Add(b); nheader.Increment(); ncore.Increment(); ninput.Increment(); nframebuffer.Increment(); ncoreframebuffer.Increment(); nmarkers.Increment(); nusertext.Increment(); } } } public static class TasBranchExtensions { public static int IndexOfFrame(this IList list, int frame) { var branch = list .Where(b => b.Frame == frame) .OrderByDescending(b => b.TimeStamp) .FirstOrDefault(); return branch == null ? -1 : list.IndexOf(branch); } // TODO: stop relying on the index value of a branch public static int IndexOfHash(this IList list, Guid uuid) { var branch = list.SingleOrDefault(b => b.Uuid == uuid); return branch == null ? -1 : list.IndexOf(branch); } } }