Implement IBasicMovieInfo interface for IMovie (#3634)

* Implement IBasicMovieInfo interface for IMovie

this allows parsing basic movie fields into an own class that is independent from Bk2Movie/TasMovie

This is mainly useful for the PlayMovie dialog which can now load movie information from files on disk without having to go through the entire Bk2Movie/TasMovie loading process

* don't potentially iterate input log twice

* Optimize LoadFramecount
This commit is contained in:
Moritz Bender 2023-04-20 10:43:02 +02:00 committed by GitHub
parent 9b278d3130
commit bc16a2cdaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 386 additions and 273 deletions

View File

@ -0,0 +1,260 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
public class BasicMovieInfo : IBasicMovieInfo
{
private string _filename;
private bool IsPal => Header[HeaderKeys.Pal] == "1";
protected readonly Bk2Header Header = new();
public BasicMovieInfo(string filename)
{
if (string.IsNullOrWhiteSpace(filename))
{
throw filename is null
? new ArgumentNullException(paramName: nameof(filename))
: new ArgumentException(message: "path cannot be blank", paramName: nameof(filename));
}
Filename = filename;
}
public string Name { get; private set; }
public string Filename
{
get => _filename;
set
{
_filename = value;
Name = Path.GetFileName(Filename);
}
}
public virtual int FrameCount { get; private set; }
public TimeSpan TimeLength
{
get
{
double dblSeconds;
if (Header.TryGetValue(HeaderKeys.CycleCount, out var numCyclesStr) && Header.TryGetValue(HeaderKeys.ClockRate, out var clockRateStr))
{
var numCycles = Convert.ToUInt64(numCyclesStr);
var clockRate = Convert.ToDouble(clockRateStr, CultureInfo.InvariantCulture);
dblSeconds = numCycles / clockRate;
}
else
{
var numFrames = (ulong)FrameCount;
dblSeconds = numFrames / FrameRate;
}
var seconds = (int)(dblSeconds % 60);
var days = seconds / 86400;
var hours = seconds / 3600;
var minutes = (seconds / 60) % 60;
var milliseconds = (int)((dblSeconds - seconds) * 1000);
return new TimeSpan(days, hours, minutes, seconds, milliseconds);
}
}
public double FrameRate
{
get
{
if (SystemID == VSystemID.Raw.Arcade && Header.TryGetValue(HeaderKeys.VsyncAttoseconds, out var vsyncAttoStr))
{
const decimal attosInSec = 1000000000000000000;
return (double)(attosInSec / Convert.ToUInt64(vsyncAttoStr));
}
else
{
return PlatformFrameRates.GetFrameRate(SystemID, IsPal);
}
}
}
public SubtitleList Subtitles { get; } = new();
public IList<string> Comments { get; } = new List<string>();
public virtual string GameName
{
get => Header[HeaderKeys.GameName];
set => Header[HeaderKeys.GameName] = value;
}
public virtual string SystemID
{
get => Header[HeaderKeys.Platform];
set => Header[HeaderKeys.Platform] = value;
}
public virtual ulong Rerecords
{
get => ulong.Parse(Header[HeaderKeys.Rerecords]);
set => Header[HeaderKeys.Rerecords] = value.ToString();
}
public virtual string Hash
{
get => Header[HeaderKeys.Sha1];
set => Header[HeaderKeys.Sha1] = value;
}
public virtual string Author
{
get => Header[HeaderKeys.Author];
set => Header[HeaderKeys.Author] = value;
}
public virtual string Core
{
get => Header[HeaderKeys.Core];
set => Header[HeaderKeys.Core] = value;
}
public virtual string BoardName
{
get => Header[HeaderKeys.BoardName];
set => Header[HeaderKeys.BoardName] = value;
}
public virtual string EmulatorVersion
{
get => Header[HeaderKeys.EmulatorVersion];
set => Header[HeaderKeys.EmulatorVersion] = value;
}
public virtual string OriginalEmulatorVersion
{
get => Header[HeaderKeys.OriginalEmulatorVersion];
set => Header[HeaderKeys.OriginalEmulatorVersion] = value;
}
public virtual string FirmwareHash
{
get => Header[HeaderKeys.FirmwareSha1];
set => Header[HeaderKeys.FirmwareSha1] = value;
}
public IDictionary<string, string> HeaderEntries => Header;
public bool Load()
{
var file = new FileInfo(Filename);
if (!file.Exists)
{
return false;
}
try
{
using var bl = ZipStateLoader.LoadAndDetect(Filename, true);
if (bl is null) return false;
ClearBeforeLoad();
LoadFields(bl);
if (FrameCount == 0)
{
// only iterate the input log if it hasn't been loaded already
LoadFramecount(bl);
}
return true;
}
catch (InvalidDataException e) when (e.StackTrace.Contains("ZipArchive.ReadEndOfCentralDirectory"))
{
throw new Exception("Archive appears to be corrupt. Make a backup, then try to repair it with e.g. 7-Zip.", e);
}
}
protected virtual void ClearBeforeLoad()
{
Header.Clear();
Subtitles.Clear();
Comments.Clear();
}
protected virtual void LoadFields(ZipStateLoader bl)
{
bl.GetLump(BinaryStateLump.Movieheader, abort: true, tr =>
{
while (tr.ReadLine() is string line)
{
if (!string.IsNullOrWhiteSpace(line))
{
var pair = line.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (pair.Length > 1)
{
if (!Header.ContainsKey(pair[0]))
{
Header.Add(pair[0], pair[1]);
}
}
}
}
});
bl.GetLump(BinaryStateLump.Comments, abort: false, tr =>
{
while (tr.ReadLine() is string line)
{
if (!string.IsNullOrWhiteSpace(line))
{
Comments.Add(line);
}
}
});
bl.GetLump(BinaryStateLump.Subtitles, abort: false, tr =>
{
while (tr.ReadLine() is string line)
{
if (!string.IsNullOrWhiteSpace(line))
{
Subtitles.AddFromString(line);
}
}
Subtitles.Sort();
});
bl.GetLump(BinaryStateLump.Subtitles, abort: false, tr =>
{
while (tr.ReadLine() is string line)
{
if (!string.IsNullOrWhiteSpace(line))
{
Subtitles.AddFromString(line);
}
}
Subtitles.Sort();
});
}
private void LoadFramecount(ZipStateLoader bl)
{
bl.GetLump(BinaryStateLump.Input, abort: true, tr =>
{
// just skim through the input log and count input lines
// FIXME: this is potentially expensive and shouldn't be necessary for something as simple as frame count
while (tr.ReadLine() is string line)
{
if (line.Length > 0 && line[0] == '|')
{
FrameCount++;
}
}
});
}
}
}

View File

@ -188,7 +188,7 @@ namespace BizHawk.Client.Common
return true; return true;
} }
/// <exception cref="MoviePlatformMismatchException"><paramref name="record"/> is <see langword="false"/> and <paramref name="movie"/>.<see cref="IMovie.SystemID"/> does not match <paramref name="systemId"/>.<see cref="IEmulator.SystemId"/></exception> /// <exception cref="MoviePlatformMismatchException"><paramref name="record"/> is <see langword="false"/> and <paramref name="movie"/>.<see cref="IBasicMovieInfo.SystemID"/> does not match <paramref name="systemId"/>.<see cref="IEmulator.SystemId"/></exception>
public void QueueNewMovie(IMovie movie, bool record, string systemId, IDictionary<string, string> preferredCores) public void QueueNewMovie(IMovie movie, bool record, string systemId, IDictionary<string, string> preferredCores)
{ {
if (movie.IsActive() && movie.Changes) if (movie.IsActive() && movie.Changes)
@ -198,7 +198,7 @@ namespace BizHawk.Client.Common
if (!record) // The semantics of record is that we are starting a new movie, and even wiping a pre-existing movie with the same path, but non-record means we are loading an existing movie into playback mode if (!record) // The semantics of record is that we are starting a new movie, and even wiping a pre-existing movie with the same path, but non-record means we are loading an existing movie into playback mode
{ {
movie.Load(false); movie.Load();
if (movie.SystemID != systemId) if (movie.SystemID != systemId)
{ {
@ -400,4 +400,4 @@ namespace BizHawk.Client.Common
Movie.RecordFrame(Movie.Emulator.Frame, MovieController); Movie.RecordFrame(Movie.Emulator.Frame, MovieController);
} }
} }
} }

View File

@ -1,18 +1,11 @@
using System.Collections.Generic; using System.Text;
using System.Text;
namespace BizHawk.Client.Common namespace BizHawk.Client.Common
{ {
public partial class Bk2Movie public partial class Bk2Movie
{ {
protected readonly Bk2Header Header = new Bk2Header();
private string _syncSettingsJson = ""; private string _syncSettingsJson = "";
public IDictionary<string, string> HeaderEntries => Header;
public SubtitleList Subtitles { get; } = new SubtitleList();
public IList<string> Comments { get; } = new List<string>();
public string SyncSettingsJson public string SyncSettingsJson
{ {
get => _syncSettingsJson; get => _syncSettingsJson;
@ -26,11 +19,8 @@ namespace BizHawk.Client.Common
} }
} }
public ulong Rerecords public override ulong Rerecords
{ {
get => Header.TryGetValue(HeaderKeys.Rerecords, out var s)
? ulong.Parse(s)
: 0UL; // Modifying the header itself can cause a race condition between loading a movie and rendering the rerecord count, causing a movie's rerecord count to be overwritten with 0 during loading.
set set
{ {
if (Header[HeaderKeys.Rerecords] != value.ToString()) if (Header[HeaderKeys.Rerecords] != value.ToString())
@ -80,9 +70,8 @@ namespace BizHawk.Client.Common
} }
} }
public string GameName public override string GameName
{ {
get => Header.TryGetValue(HeaderKeys.GameName, out var s) ? s : string.Empty;
set set
{ {
if (Header[HeaderKeys.GameName] != value) if (Header[HeaderKeys.GameName] != value)
@ -93,9 +82,8 @@ namespace BizHawk.Client.Common
} }
} }
public string SystemID public override string SystemID
{ {
get => Header.TryGetValue(HeaderKeys.Platform, out var s) ? s : string.Empty;
set set
{ {
if (Header[HeaderKeys.Platform] != value) if (Header[HeaderKeys.Platform] != value)
@ -106,9 +94,8 @@ namespace BizHawk.Client.Common
} }
} }
public string Hash public override string Hash
{ {
get => Header[HeaderKeys.Sha1];
set set
{ {
if (Header[HeaderKeys.Sha1] != value) if (Header[HeaderKeys.Sha1] != value)
@ -119,9 +106,8 @@ namespace BizHawk.Client.Common
} }
} }
public string Author public override string Author
{ {
get => Header[HeaderKeys.Author];
set set
{ {
if (Header[HeaderKeys.Author] != value) if (Header[HeaderKeys.Author] != value)
@ -132,9 +118,8 @@ namespace BizHawk.Client.Common
} }
} }
public string Core public override string Core
{ {
get => Header[HeaderKeys.Core];
set set
{ {
if (Header[HeaderKeys.Core] != value) if (Header[HeaderKeys.Core] != value)
@ -145,9 +130,8 @@ namespace BizHawk.Client.Common
} }
} }
public string BoardName public override string BoardName
{ {
get => Header[HeaderKeys.BoardName];
set set
{ {
if (Header[HeaderKeys.BoardName] != value) if (Header[HeaderKeys.BoardName] != value)
@ -158,9 +142,8 @@ namespace BizHawk.Client.Common
} }
} }
public string EmulatorVersion public override string EmulatorVersion
{ {
get => Header[HeaderKeys.EmulatorVersion];
set set
{ {
if (Header[HeaderKeys.EmulatorVersion] != value) if (Header[HeaderKeys.EmulatorVersion] != value)
@ -171,9 +154,8 @@ namespace BizHawk.Client.Common
} }
} }
public string OriginalEmulatorVersion public override string OriginalEmulatorVersion
{ {
get => Header[HeaderKeys.OriginalEmulatorVersion];
set set
{ {
if (Header[HeaderKeys.OriginalEmulatorVersion] != value) if (Header[HeaderKeys.OriginalEmulatorVersion] != value)
@ -184,9 +166,8 @@ namespace BizHawk.Client.Common
} }
} }
public string FirmwareHash public override string FirmwareHash
{ {
get => Header[HeaderKeys.FirmwareSha1];
set set
{ {
if (Header[HeaderKeys.FirmwareSha1] != value) if (Header[HeaderKeys.FirmwareSha1] != value)
@ -209,10 +190,6 @@ namespace BizHawk.Client.Common
return sb.ToString(); return sb.ToString();
} }
// ReSharper disable SimplifyConditionalTernaryExpression
public bool IsPal => Header.TryGetValue(HeaderKeys.Pal, out var s) ? s == "1" : false;
// ReSharper restore SimplifyConditionalTernaryExpression
public string TextSavestate { get; set; } public string TextSavestate { get; set; }
public byte[] BinarySavestate { get; set; } public byte[] BinarySavestate { get; set; }
public int[] SavestateFramebuffer { get; set; } public int[] SavestateFramebuffer { get; set; }

View File

@ -29,31 +29,6 @@ namespace BizHawk.Client.Common
Write(backupName, isBackup: true); Write(backupName, isBackup: true);
} }
public virtual bool Load(bool preload)
{
var file = new FileInfo(Filename);
if (!file.Exists)
{
return false;
}
try
{
using var bl = ZipStateLoader.LoadAndDetect(Filename, true);
if (bl is null) return false;
ClearBeforeLoad();
LoadFields(bl, preload);
Changes = false;
return true;
}
catch (InvalidDataException e) when (e.StackTrace.Contains("ZipArchive.ReadEndOfCentralDirectory"))
{
throw new Exception("Archive appears to be corrupt. Make a backup, then try to repair it with e.g. 7-Zip.", e);
}
}
public bool PreLoadHeaderAndLength() => Load(true);
protected virtual void Write(string fn, bool isBackup = false) protected virtual void Write(string fn, bool isBackup = false)
{ {
SetCycleValues(); SetCycleValues();
@ -136,49 +111,28 @@ namespace BizHawk.Client.Common
} }
} }
protected virtual void ClearBeforeLoad() protected override void ClearBeforeLoad()
{ {
base.ClearBeforeLoad();
ClearBk2Fields(); ClearBk2Fields();
} }
protected void ClearBk2Fields() private void ClearBk2Fields()
{ {
Header.Clear();
Log.Clear(); Log.Clear();
Subtitles.Clear();
Comments.Clear();
_syncSettingsJson = ""; _syncSettingsJson = "";
TextSavestate = null; TextSavestate = null;
BinarySavestate = null; BinarySavestate = null;
} }
protected virtual void LoadFields(ZipStateLoader bl, bool preload) protected override void LoadFields(ZipStateLoader bl)
{ {
LoadBk2Fields(bl, preload); base.LoadFields(bl);
LoadBk2Fields(bl);
} }
protected void LoadBk2Fields(ZipStateLoader bl, bool preload) private void LoadBk2Fields(ZipStateLoader bl)
{ {
bl.GetLump(BinaryStateLump.Movieheader, abort: true, tr =>
{
string line;
while ((line = tr.ReadLine()) != null)
{
if (!string.IsNullOrWhiteSpace(line))
{
var pair = line.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (pair.Length > 1)
{
if (!Header.ContainsKey(pair[0]))
{
Header.Add(pair[0], pair[1]);
}
}
}
}
});
bl.GetLump(BinaryStateLump.Input, abort: true, tr => bl.GetLump(BinaryStateLump.Input, abort: true, tr =>
{ {
IsCountingRerecords = false; IsCountingRerecords = false;
@ -186,37 +140,6 @@ namespace BizHawk.Client.Common
IsCountingRerecords = true; IsCountingRerecords = true;
}); });
if (preload)
{
return;
}
bl.GetLump(BinaryStateLump.Comments, abort: false, tr =>
{
string line;
while ((line = tr.ReadLine()) != null)
{
if (!string.IsNullOrWhiteSpace(line))
{
Comments.Add(line);
}
}
});
bl.GetLump(BinaryStateLump.Subtitles, abort: false, tr =>
{
string line;
while ((line = tr.ReadLine()) != null)
{
if (!string.IsNullOrWhiteSpace(line))
{
Subtitles.AddFromString(line);
}
}
Subtitles.Sort();
});
bl.GetLump(BinaryStateLump.SyncSettings, abort: false, tr => bl.GetLump(BinaryStateLump.SyncSettings, abort: false, tr =>
{ {
string line; string line;

View File

@ -1,26 +1,15 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common namespace BizHawk.Client.Common
{ {
public partial class Bk2Movie : IMovie public partial class Bk2Movie : BasicMovieInfo, IMovie
{ {
private Bk2Controller _adapter; private Bk2Controller _adapter;
public Bk2Movie(IMovieSession session, string filename) public Bk2Movie(IMovieSession session, string filename) : base(filename)
{ {
if (string.IsNullOrWhiteSpace(filename))
{
throw filename is null
? new ArgumentNullException(paramName: nameof(filename))
: new ArgumentException(message: "path cannot be blank", paramName: nameof(filename));
}
Session = session; Session = session;
Filename = filename;
Header[HeaderKeys.MovieVersion] = "BizHawk v2.0.0"; Header[HeaderKeys.MovieVersion] = "BizHawk v2.0.0";
} }
@ -36,20 +25,6 @@ namespace BizHawk.Client.Common
protected bool MakeBackup { get; set; } = true; protected bool MakeBackup { get; set; } = true;
private string _filename;
public string Filename
{
get => _filename;
set
{
_filename = value;
Name = Path.GetFileName(Filename);
}
}
public string Name { get; private set; }
public virtual string PreferredExtension => Extension; public virtual string PreferredExtension => Extension;
public const string Extension = "bk2"; public const string Extension = "bk2";
@ -62,52 +37,9 @@ namespace BizHawk.Client.Common
return new Bk2LogEntryGenerator(Emulator?.SystemId ?? SystemID, source); return new Bk2LogEntryGenerator(Emulator?.SystemId ?? SystemID, source);
} }
public int FrameCount => Log.Count; public override int FrameCount => Log.Count;
public int InputLogLength => Log.Count; public int InputLogLength => Log.Count;
public TimeSpan TimeLength
{
get
{
double dblSeconds;
if (Header.TryGetValue(HeaderKeys.CycleCount, out var numCyclesStr) && Header.TryGetValue(HeaderKeys.ClockRate, out var clockRateStr))
{
var numCycles = Convert.ToUInt64(numCyclesStr);
var clockRate = Convert.ToDouble(clockRateStr, CultureInfo.InvariantCulture);
dblSeconds = numCycles / clockRate;
}
else
{
var numFrames = (ulong)FrameCount;
dblSeconds = numFrames / FrameRate;
}
var seconds = (int)(dblSeconds % 60);
var days = seconds / 86400;
var hours = seconds / 3600;
var minutes = (seconds / 60) % 60;
var milliseconds = (int)((dblSeconds - seconds) * 1000);
return new TimeSpan(days, hours, minutes, seconds, milliseconds);
}
}
public double FrameRate
{
get
{
if (SystemID == VSystemID.Raw.Arcade && Header.TryGetValue(HeaderKeys.VsyncAttoseconds, out var vsyncAttoStr))
{
const decimal attosInSec = 1000000000000000000;
return (double)(attosInSec / Convert.ToUInt64(vsyncAttoStr));
}
else
{
return PlatformFrameRates.GetFrameRate(SystemID, IsPal);
}
}
}
public IStringLog GetLogEntries() => Log; public IStringLog GetLogEntries() => Log;
public void CopyLog(IEnumerable<string> log) public void CopyLog(IEnumerable<string> log)

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
namespace BizHawk.Client.Common
{
public interface IBasicMovieInfo
{
// Filename of the movie, settable by the client
string Filename { get; set; }
string Name { get; }
string GameName { get; set; }
/// <summary>
/// Gets the total number of frames that count towards the completion time of the movie
/// </summary>
int FrameCount { get; }
/// <summary>
/// Gets the actual length of time a movie lasts for. For subframe cores, this will be different then the above two options
/// </summary>
TimeSpan TimeLength { get; }
/// <summary>
/// Gets the frame rate in frames per second for the movie's system.
/// </summary>
double FrameRate { get; }
SubtitleList Subtitles { get; }
IList<string> Comments { get; }
string SystemID { get; set; }
ulong Rerecords { get; set; }
/// <value>either CRC32, MD5, or SHA1, hex-encoded, unprefixed</value>
string Hash { get; set; }
string Author { get; set; }
string Core { get; set; }
string EmulatorVersion { get; set; }
string OriginalEmulatorVersion { get; set; }
string FirmwareHash { get; set; }
string BoardName { get; set; }
/// <summary>
/// Gets the header key value pairs stored in the movie file
/// </summary>
IDictionary<string, string> HeaderEntries { get; }
/// <summary>
/// Tells the movie to load the contents of Filename
/// </summary>
/// <returns>Return whether or not the file was successfully loaded</returns>
bool Load();
}
}

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -30,7 +29,7 @@ namespace BizHawk.Client.Common
// TODO: message callback / event handler // TODO: message callback / event handler
// TODO: consider other event handlers, switching modes? // TODO: consider other event handlers, switching modes?
public interface IMovie public interface IMovie : IBasicMovieInfo
{ {
/// <summary> /// <summary>
/// Gets the current movie mode /// Gets the current movie mode
@ -41,29 +40,12 @@ namespace BizHawk.Client.Common
bool Changes { get; } bool Changes { get; }
string Name { get; }
/// <summary>
/// Gets the total number of frames that count towards the completion time of the movie
/// </summary>
int FrameCount { get; }
/// <summary> /// <summary>
/// Gets the actual length of the input log, should only be used by code that needs the input log length /// Gets the actual length of the input log, should only be used by code that needs the input log length
/// specifically, not the frame count /// specifically, not the frame count
/// </summary> /// </summary>
int InputLogLength { get; } int InputLogLength { get; }
/// <summary>
/// Gets the actual length of time a movie lasts for. For subframe cores, this will be different then the above two options
/// </summary>
TimeSpan TimeLength { get; }
/// <summary>
/// Gets the frame rate in frames per second for the movie's system.
/// </summary>
double FrameRate { get; }
/// <summary> /// <summary>
/// Gets the file extension for the current <see cref="IMovie"/> implementation /// Gets the file extension for the current <see cref="IMovie"/> implementation
/// </summary> /// </summary>
@ -74,9 +56,6 @@ namespace BizHawk.Client.Common
/// </summary> /// </summary>
string SyncSettingsJson { get; set; } string SyncSettingsJson { get; set; }
SubtitleList Subtitles { get; }
IList<string> Comments { get; }
// savestate anchor. // savestate anchor.
string TextSavestate { get; set; } string TextSavestate { get; set; }
byte[] BinarySavestate { get; set; } byte[] BinarySavestate { get; set; }
@ -85,34 +64,11 @@ namespace BizHawk.Client.Common
// saveram anchor // saveram anchor
byte[] SaveRam { get; set; } byte[] SaveRam { get; set; }
ulong Rerecords { get; set; }
bool StartsFromSavestate { get; set; } bool StartsFromSavestate { get; set; }
bool StartsFromSaveRam { get; set; } bool StartsFromSaveRam { get; set; }
string GameName { get; set; }
string SystemID { get; set; }
/// <value>either CRC32, MD5, or SHA1, hex-encoded, unprefixed</value>
string Hash { get; set; }
string Author { get; set; }
string Core { get; set; }
string EmulatorVersion { get; set; }
string OriginalEmulatorVersion { get; set; }
string FirmwareHash { get; set; }
string BoardName { get; set; }
string LogKey { get; set; } string LogKey { get; set; }
/// <summary>
/// Loads from the HawkFile the minimal amount of information needed to determine Header info and Movie length.
/// This method is intended to be more performant than a full load
/// </summary>
bool PreLoadHeaderAndLength();
/// <summary>
/// Gets the header key value pairs stored in the movie file
/// </summary>
IDictionary<string, string> HeaderEntries { get; }
/// <summary> /// <summary>
/// Forces the creation of a backup file of the current movie state /// Forces the creation of a backup file of the current movie state
/// </summary> /// </summary>
@ -123,15 +79,6 @@ namespace BizHawk.Client.Common
/// </summary> /// </summary>
ILogEntryGenerator LogGeneratorInstance(IController source); ILogEntryGenerator LogGeneratorInstance(IController source);
// Filename of the movie, settable by the client
string Filename { get; set; }
/// <summary>
/// Tells the movie to load the contents of Filename
/// </summary>
/// <returns>Return whether or not the file was successfully loaded</returns>
bool Load(bool preload);
/// <summary> /// <summary>
/// Instructs the movie to save the current contents to Filename /// Instructs the movie to save the current contents to Filename
/// </summary> /// </summary>

View File

@ -49,7 +49,7 @@ namespace BizHawk.Client.Common
protected override void ClearBeforeLoad() protected override void ClearBeforeLoad()
{ {
ClearBk2Fields(); base.ClearBeforeLoad();
ClearTasprojExtras(); ClearTasprojExtras();
} }
@ -61,21 +61,18 @@ namespace BizHawk.Client.Common
ChangeLog.Clear(); ChangeLog.Clear();
} }
protected override void LoadFields(ZipStateLoader bl, bool preload) protected override void LoadFields(ZipStateLoader bl)
{ {
LoadBk2Fields(bl, preload); base.LoadFields(bl);
if (!preload) if (MovieService.IsCurrentTasVersion(Header[HeaderKeys.MovieVersion]))
{ {
if (MovieService.IsCurrentTasVersion(Header[HeaderKeys.MovieVersion])) LoadTasprojExtras(bl);
{ }
LoadTasprojExtras(bl); else
} {
else Session.PopupMessage("The current .tasproj is not compatible with this version of BizHawk! .tasproj features failed to load.");
{ Markers.Add(0, StartsFromSavestate ? "Savestate" : "Power on");
Session.PopupMessage("The current .tasproj is not compatible with this version of BizHawk! .tasproj features failed to load.");
Markers.Add(0, StartsFromSavestate ? "Savestate" : "Power on");
}
} }
} }

View File

@ -27,7 +27,7 @@ namespace BizHawk.Client.EmuHawk
private readonly IEmulator _emulator; private readonly IEmulator _emulator;
private readonly IMovieSession _movieSession; private readonly IMovieSession _movieSession;
private List<IMovie> _movieList = new List<IMovie>(); private List<IBasicMovieInfo> _movieList = new();
private bool _sortReverse; private bool _sortReverse;
private string _sortedCol; private string _sortedCol;
@ -66,8 +66,10 @@ namespace BizHawk.Client.EmuHawk
private void PlayMovie_Load(object sender, EventArgs e) private void PlayMovie_Load(object sender, EventArgs e)
{ {
_suppressCheckedChanged = true;
IncludeSubDirectories.Checked = _config.PlayMovieIncludeSubDir; IncludeSubDirectories.Checked = _config.PlayMovieIncludeSubDir;
MatchHashCheckBox.Checked = _config.PlayMovieMatchHash; MatchHashCheckBox.Checked = _config.PlayMovieMatchHash;
_suppressCheckedChanged = false;
ScanFiles(); ScanFiles();
PreHighlightMovie(); PreHighlightMovie();
TurboCheckbox.Checked = _config.TurboSeek; TurboCheckbox.Checked = _config.TurboSeek;
@ -87,7 +89,9 @@ namespace BizHawk.Client.EmuHawk
var indices = MovieView.SelectedIndices; var indices = MovieView.SelectedIndices;
if (indices.Count > 0) // Import file if necessary if (indices.Count > 0) // Import file if necessary
{ {
_mainForm.StartNewMovie(_movieList[MovieView.SelectedIndices[0]], false); var movie = _movieSession.Get(_movieList[MovieView.SelectedIndices[0]].Filename);
movie.Load();
_mainForm.StartNewMovie(movie, false);
} }
} }
@ -99,7 +103,7 @@ namespace BizHawk.Client.EmuHawk
return null; return null;
} }
var movie = PreLoadMovieFile(file, force); var movie = LoadMovieInfo(file, force);
if (movie == null) if (movie == null)
{ {
return null; return null;
@ -138,13 +142,13 @@ namespace BizHawk.Client.EmuHawk
return null; return null;
} }
private IMovie PreLoadMovieFile(HawkFile hf, bool force) private IBasicMovieInfo LoadMovieInfo(HawkFile hf, bool force)
{ {
var movie = _movieSession.Get(hf.CanonicalFullPath); IBasicMovieInfo movie = new BasicMovieInfo(hf.CanonicalFullPath);
try try
{ {
movie.PreLoadHeaderAndLength(); movie.Load();
// Don't do this from browse // Don't do this from browse
if (movie.Hash == _game.Hash if (movie.Hash == _game.Hash
@ -338,8 +342,8 @@ namespace BizHawk.Client.EmuHawk
Close(); Close();
} }
private static readonly RigidMultiPredicateSort<IMovie> ColumnSorts private static readonly RigidMultiPredicateSort<IBasicMovieInfo> ColumnSorts
= new RigidMultiPredicateSort<IMovie>(new Dictionary<string, Func<IMovie, IComparable>> = new(new Dictionary<string, Func<IBasicMovieInfo, IComparable>>
{ {
["File"] = x => Path.GetFileName(x.Filename), ["File"] = x => Path.GetFileName(x.Filename),
["SysID"] = x => x.SystemID, ["SysID"] = x => x.SystemID,
@ -499,7 +503,11 @@ namespace BizHawk.Client.EmuHawk
var indices = MovieView.SelectedIndices; var indices = MovieView.SelectedIndices;
if (indices.Count > 0) if (indices.Count > 0)
{ {
var form = new EditCommentsForm(_movieList[MovieView.SelectedIndices[0]], _movieSession.ReadOnly); // TODO this will allocate unnecessary memory when this movie is a TasMovie due to TasStateManager
var movie = _movieSession.Get(_movieList[MovieView.SelectedIndices[0]].Filename);
movie.Load();
// TODO movie should be disposed if movie is ITasMovie
var form = new EditCommentsForm(movie, _movieSession.ReadOnly);
form.Show(); form.Show();
} }
} }
@ -509,7 +517,11 @@ namespace BizHawk.Client.EmuHawk
var indices = MovieView.SelectedIndices; var indices = MovieView.SelectedIndices;
if (indices.Count > 0) if (indices.Count > 0)
{ {
using EditSubtitlesForm s = new(DialogController, _movieList[MovieView.SelectedIndices[0]], _config.PathEntries, readOnly: true); // TODO this will allocate unnecessary memory when this movie is a TasMovie due to TasStateManager
var movie = _movieSession.Get(_movieList[MovieView.SelectedIndices[0]].Filename);
movie.Load();
// TODO movie should be disposed if movie is ITasMovie
using EditSubtitlesForm s = new(DialogController, movie, _config.PathEntries, readOnly: true);
s.Show(); s.Show();
} }
} }
@ -543,6 +555,8 @@ namespace BizHawk.Client.EmuHawk
private void IncludeSubDirectories_CheckedChanged(object sender, EventArgs e) private void IncludeSubDirectories_CheckedChanged(object sender, EventArgs e)
{ {
if (_suppressCheckedChanged) return;
_config.PlayMovieIncludeSubDir = IncludeSubDirectories.Checked; _config.PlayMovieIncludeSubDir = IncludeSubDirectories.Checked;
ScanFiles(); ScanFiles();
PreHighlightMovie(); PreHighlightMovie();
@ -550,6 +564,8 @@ namespace BizHawk.Client.EmuHawk
private void MatchHashCheckBox_CheckedChanged(object sender, EventArgs e) private void MatchHashCheckBox_CheckedChanged(object sender, EventArgs e)
{ {
if (_suppressCheckedChanged) return;
_config.PlayMovieMatchHash = MatchHashCheckBox.Checked; _config.PlayMovieMatchHash = MatchHashCheckBox.Checked;
ScanFiles(); ScanFiles();
PreHighlightMovie(); PreHighlightMovie();
@ -578,6 +594,8 @@ namespace BizHawk.Client.EmuHawk
} }
private bool _programmaticallyChangingStopFrameCheckbox; private bool _programmaticallyChangingStopFrameCheckbox;
private bool _suppressCheckedChanged;
private void StopOnFrameCheckbox_CheckedChanged(object sender, EventArgs e) private void StopOnFrameCheckbox_CheckedChanged(object sender, EventArgs e)
{ {
if (!_programmaticallyChangingStopFrameCheckbox) if (!_programmaticallyChangingStopFrameCheckbox)

View File

@ -7,10 +7,10 @@ namespace BizHawk.Client.EmuHawk
{ {
public partial class MovieHeaderEditor : Form public partial class MovieHeaderEditor : Form
{ {
private readonly IMovie _movie; private readonly IBasicMovieInfo _movie;
private readonly Config _config; private readonly Config _config;
public MovieHeaderEditor(IMovie movie, Config config) public MovieHeaderEditor(IBasicMovieInfo movie, Config config)
{ {
_movie = movie; _movie = movie;
_config = config; _config = config;