diff --git a/src/BizHawk.Client.Common/movie/MovieSession.cs b/src/BizHawk.Client.Common/movie/MovieSession.cs index 53796bb783..90e745a355 100644 --- a/src/BizHawk.Client.Common/movie/MovieSession.cs +++ b/src/BizHawk.Client.Common/movie/MovieSession.cs @@ -348,17 +348,51 @@ namespace BizHawk.Client.Common #endif if (Movie.IsAtEnd() && Movie.Emulator.HasCycleTiming()) { + const string WINDOW_TITLE_MISMATCH = "Cycle count mismatch"; + const string WINDOW_TITLE_MISSING = "Cycle count not yet saved"; + const string PFX_MISSING = "The cycle count (running time) hasn't been saved into this movie yet.\n"; + const string ERR_MSG_MISSING_READONLY = PFX_MISSING + "The movie was loaded in read-only mode. To add the cycle count, load it in read-write mode and play to the end again."; + const string ERR_MSG_MISSING_CONFIRM = PFX_MISSING + "Add it now?"; + const string PFX_MISMATCH = "The cycle count (running time) saved into this movie ({0}) doesn't match the measured count ({1}) here at the end.\n"; + const string ERR_FMT_STR_MISMATCH_READONLY = PFX_MISMATCH + "The movie was loaded in read-only mode. To correct the cycle count, load it in read-write mode and play to the end again."; + const string ERR_FMT_STR_MISMATCH_CONFIRM = PFX_MISMATCH + "Correct it now?"; var coreValue = Movie.Emulator.AsCycleTiming().CycleCount; if (!Movie.HeaderEntries.TryGetValue(HeaderKeys.CycleCount, out var movieValueStr) || !long.TryParse(movieValueStr, out var movieValue)) { - //TODO ideally this would just save indiscriminately, maybe skip if read-only, but I don't think we should be bothering the user in this case --yoshi - PopupMessage("The cycle count (running time) hasn't been saved into this movie yet.\nEnsure you've paused at the end, then save the movie again to add the cycle count."); + if (ReadOnly) + { + //TODO this would be annoying to encounter; detect the missing field when playback starts instead --yoshi + _dialogParent.ModalMessageBox( + caption: WINDOW_TITLE_MISSING, + text: ERR_MSG_MISSING_READONLY, + icon: EMsgBoxIcon.Info); + } + else if (_dialogParent.ModalMessageBox2( + caption: WINDOW_TITLE_MISSING, + text: ERR_MSG_MISSING_CONFIRM, + icon: EMsgBoxIcon.Question)) + { + Movie.SetCycleValues(); + } } else if (coreValue != movieValue) { - // TODO: Ideally, this would be a Yes/No MessageBox that saves when "Yes" is pressed. - PopupMessage($"The cycle count (running time) saved into this movie ({movieValue}) doesn't match the measured count ({coreValue}) here at the end.\nEnsure you've paused at the end, then save the movie again to update the cycle count."); + if (ReadOnly) + { + //TODO this would be annoying to encounter; detect the missing field when playback starts instead --yoshi + _dialogParent.ModalMessageBox( + caption: WINDOW_TITLE_MISMATCH, + text: string.Format(ERR_FMT_STR_MISMATCH_READONLY, movieValue, coreValue), + icon: EMsgBoxIcon.Warning); + } + else if (_dialogParent.ModalMessageBox2( + caption: WINDOW_TITLE_MISMATCH, + text: string.Format(ERR_FMT_STR_MISMATCH_CONFIRM, movieValue, coreValue), + icon: EMsgBoxIcon.Question)) + { + Movie.SetCycleValues(); + } } } switch (Settings.MovieEndAction) diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index 1b6343ec8f..d58099728a 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -49,7 +49,7 @@ namespace BizHawk.Client.Common } } - private void SetCycleValues() + public void SetCycleValues() //TODO IEmulator should not be an instance prop of movies, it should be passed in to every call (i.e. from MovieService) --yoshi { // The saved cycle value will only be valid if the end of the movie has been emulated. if (this.IsAtEnd()) diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs index 3ccefb589b..1f7d05388f 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs @@ -84,6 +84,9 @@ namespace BizHawk.Client.Common /// void Save(); + /// updates the and headers from the currently loaded core + void SetCycleValues(); + /// /// Writes the input log directly to the stream, bypassing the need to load it all into ram as a string ///