using System; using System.Collections.Generic; using System.IO; using BizHawk.Common; using BizHawk.Client.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Nintendo.GBA; namespace BizHawk.Client.EmuHawk { public class BatchRunner { public class ProgressEventArgs { public int Completed { get; } public int Total { get; } public bool ShouldCancel { get; set; } public ProgressEventArgs(int completed, int total) { Completed = completed; Total = total; } } public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); public event ProgressEventHandler OnProgress; private readonly List _files; private readonly List _results = new List(); private readonly RomLoader _ldr; private readonly CoreComm _comm; private readonly int _numFrames; private int _multiIndex; private bool _multiHasNext; private Result _current; public class Result { public string Filename { get; set; } // name of file public string Fullname { get; set; } // filename + subfilename public GameInfo Game { get; set; } public Type CoreType { get; set; } // actual type of the core that was returned public enum EStatus { ExceptOnLoad, // exception thrown on load ErrorOnLoad, // error method thrown on load FalseOnLoad, // RomLoader returned false with no other information ExceptOnAdv, // exception thrown on frame advance Success, // load fully complete } public EStatus Status { get; set; } // what happened public List Messages { get; set; } = new List(); public int Frames { get; set; } // number of frames successfully run public int LaggedFrames { get; set; } // number of those that were lagged public string BoardName { get; set; } // IEmulator's board name return (could be null!) public void DumpTo(TextWriter tw) { tw.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}", Filename, Fullname, CoreType, Status, Frames, LaggedFrames, Game.Hash, BoardName); } } public BatchRunner(IEnumerable files, int numFrames) { _files = new List(files); _numFrames = numFrames; _ldr = new RomLoader(); _ldr.OnLoadError += OnLoadError; _ldr.ChooseArchive = ChooseArchive; _comm = new CoreComm(CommMessage, CommMessage); CoreFileProvider.SyncCoreCommInputSignals(_comm); } private void OnLoadError(object sender, RomLoader.RomErrorArgs e) { _current.Status = Result.EStatus.ErrorOnLoad; _current.Messages.Add($"{nameof(OnLoadError)}: {e.AttemptedCoreLoad}, {e.Message}, {e.Type}"); } private void CommMessage(string msg) { _current.Messages.Add($"{nameof(CommMessage)}: {msg}"); } private int? ChooseArchive(HawkFile hf) { int ret = _multiIndex; _multiIndex++; _multiHasNext = _multiIndex < hf.ArchiveItems.Count; return ret; } public List Run() { _results.Clear(); _current = null; RunInternal(); return new List(_results); } private void RunInternal() { for (int i = 0; i < _files.Count; i++) { string f = _files[i]; _multiHasNext = false; _multiIndex = 0; do { LoadOne(f); } while (_multiHasNext); if (OnProgress != null) { var e = new ProgressEventArgs(i + 1, _files.Count); OnProgress(this, e); if (e.ShouldCancel) { return; } } } } private void LoadOne(string f) { _current = new Result { Filename = f }; bool result; try { result = _ldr.LoadRom(f, _comm); } catch (Exception e) { _current.Status = Result.EStatus.ExceptOnLoad; _current.Messages.Add(e.ToString()); _results.Add(_current); _current = null; return; } _current.Fullname = _ldr.CanonicalFullPath; if (_current.Status == Result.EStatus.ErrorOnLoad) { _results.Add(_current); _current = null; return; } if (result == false) { _current.Status = Result.EStatus.FalseOnLoad; _results.Add(_current); _current = null; return; } using (IEmulator emu = _ldr.LoadedEmulator) { _current.Game = _ldr.Game; _current.CoreType = emu.GetType(); var controller = new Controller(emu.ControllerDefinition); _current.BoardName = emu.HasBoardInfo() ? emu.AsBoardInfo().BoardName : null; // hack if (emu is VBANext vba) { _current.BoardName = vba.GameCode; } _current.Frames = 0; _current.LaggedFrames = 0; for (int i = 0; i < _numFrames; i++) { try { emu.FrameAdvance(controller, true); // some cores really really really like it if you drain their audio every frame if (emu.HasSoundProvider()) { emu.AsSoundProvider().GetSamplesSync(out _, out _); } _current.Frames++; if (emu.CanPollInput() && emu.AsInputPollable().IsLagFrame) _current.LaggedFrames++; } catch (Exception e) { _current.Messages.Add(e.ToString()); _current.Status = Result.EStatus.ExceptOnAdv; _results.Add(_current); _current = null; return; } } } _current.Status = Result.EStatus.Success; _results.Add(_current); _current = null; } } }