diff --git a/.global.editorconfig.ini b/.global.editorconfig.ini index d6f88a6f0e3..6d8d0f8bb38 100644 --- a/.global.editorconfig.ini +++ b/.global.editorconfig.ini @@ -156,15 +156,6 @@ dotnet_diagnostic.CA2229.severity = silent # Opt in to preview features before using them dotnet_diagnostic.CA2252.severity = silent # CSharpDetectPreviewFeatureAnalyzer very slow -## Nullable rules; generics are a bit wonky and I have no idea why we're allowed to configure these to be not errors or why they aren't errors by default. - -# Nullability of reference types in value doesn't match target type. -dotnet_diagnostic.CS8619.severity = error -# Make Foo an error for class Foo where T : class. Use `where T : class?` if Foo should be allowed. -dotnet_diagnostic.CS8634.severity = error -# Nullability of type argument doesn't match 'notnull' constraint. -dotnet_diagnostic.CS8714.severity = error - ## .NET DocumentationAnalyzers style rules # Place text in paragraphs diff --git a/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs b/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs index 152bdcedcf1..f0f4614f8d9 100644 --- a/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs @@ -88,7 +88,7 @@ private void CallStateSaved(object sender, StateSavedEventArgs args) public void CloseEmulator(int? exitCode = null) => _mainForm.CloseEmulator(exitCode); - public void CloseRom() => _mainForm.LoadNullRom(); + public void CloseRom() => _mainForm.CloseRom(); public void DisplayMessages(bool value) => _config.DisplayMessages = value; @@ -160,19 +160,9 @@ public bool OpenRom(string path) public void RebootCore() => _mainForm.RebootCore(); - // TODO: Change return type to FileWriteResult. public void SaveRam() => _mainForm.FlushSaveRAM(); - // TODO: Change return type to FileWriteResult. - // We may wish to change more than that, since we have a mostly-dupicate ISaveStateApi.Save, neither has documentation indicating what the differences are. - public void SaveState(string name) - { - FileWriteResult result = _mainForm.SaveState(Path.Combine(_config.PathEntries.SaveStateAbsolutePath(Game.System), $"{name}.State"), name); - if (result.Exception != null && result.Exception is not UnlessUsingApiException) - { - throw result.Exception; - } - } + public void SaveState(string name) => _mainForm.SaveState(Path.Combine(_config.PathEntries.SaveStateAbsolutePath(Game.System), $"{name}.State"), name, fromLua: false); public int ScreenHeight() => _displayManager.GetPanelNativeSize().Height; diff --git a/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs b/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs index 47e059e96c5..50672fb7965 100644 --- a/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs @@ -52,7 +52,6 @@ public string GetInputAsMnemonic(int frame) return Bk2LogEntryGenerator.GenerateLogEntry(_movieSession.Movie.GetInputState(frame)); } - // TODO: Change return type to FileWriteResult public void Save(string filename) { if (_movieSession.Movie.NotActive()) @@ -70,8 +69,7 @@ public void Save(string filename) } _movieSession.Movie.Filename = filename; } - FileWriteResult result = _movieSession.Movie.Save(); - if (result.Exception != null) throw result.Exception; + _movieSession.Movie.Save(); } public IReadOnlyDictionary GetHeader() diff --git a/src/BizHawk.Client.Common/Api/Classes/SaveStateApi.cs b/src/BizHawk.Client.Common/Api/Classes/SaveStateApi.cs index 271f036ed20..9bbe860e9c5 100644 --- a/src/BizHawk.Client.Common/Api/Classes/SaveStateApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/SaveStateApi.cs @@ -39,17 +39,8 @@ public bool LoadSlot(int slotNum, bool suppressOSD) return _mainForm.LoadQuickSave(slotNum, suppressOSD: suppressOSD); } - // TODO: Change return type FileWriteResult. - public void Save(string path, bool suppressOSD) - { - FileWriteResult result = _mainForm.SaveState(path, path, suppressOSD); - if (result.Exception != null && result.Exception is not UnlessUsingApiException) - { - throw result.Exception; - } - } + public void Save(string path, bool suppressOSD) => _mainForm.SaveState(path, path, true, suppressOSD); - // TODO: Change return type to FileWriteResult. public void SaveSlot(int slotNum, bool suppressOSD) { if (slotNum is < 0 or > 10) throw new ArgumentOutOfRangeException(paramName: nameof(slotNum), message: ERR_MSG_NOT_A_SLOT); @@ -58,11 +49,7 @@ public void SaveSlot(int slotNum, bool suppressOSD) LogCallback(ERR_MSG_USE_SLOT_10); slotNum = 10; } - FileWriteResult result = _mainForm.SaveQuickSave(slotNum, suppressOSD: suppressOSD); - if (result.Exception != null && result.Exception is not UnlessUsingApiException) - { - throw result.Exception; - } + _mainForm.SaveQuickSave(slotNum, suppressOSD: suppressOSD, fromLua: true); } } } diff --git a/src/BizHawk.Client.Common/DialogControllerExtensions.cs b/src/BizHawk.Client.Common/DialogControllerExtensions.cs index 6499754f190..3c90b47f097 100644 --- a/src/BizHawk.Client.Common/DialogControllerExtensions.cs +++ b/src/BizHawk.Client.Common/DialogControllerExtensions.cs @@ -1,18 +1,10 @@ #nullable enable using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace BizHawk.Client.Common { - public enum TryAgainResult - { - Saved, - IgnoredFailure, - Canceled, - } - public static class DialogControllerExtensions { public static void AddOnScreenMessage( @@ -72,48 +64,6 @@ public static bool ModalMessageBox2( EMsgBoxIcon? icon = null) => dialogParent.DialogController.ShowMessageBox3(owner: dialogParent, text: text, caption: caption, icon: icon); - public static void ErrorMessageBox( - this IDialogParent dialogParent, - FileWriteResult fileResult, - string? prefixMessage = null) - { - Debug.Assert(fileResult.IsError && fileResult.Exception != null, "Error box must have an error."); - - string prefix = prefixMessage ?? ""; - dialogParent.ModalMessageBox( - text: $"{prefix}\n{fileResult.UserFriendlyErrorMessage()}\n{fileResult.Exception!.Message}", - caption: "Error", - icon: EMsgBoxIcon.Error); - } - - /// - /// If the action fails, asks the user if they want to try again. - /// The user will be repeatedly asked if they want to try again until either success or the user says no. - /// - /// Returns true on success or if the user said no. Returns false if the user said cancel. - public static TryAgainResult DoWithTryAgainBox( - this IDialogParent dialogParent, - Func action, - string message) - { - FileWriteResult fileResult = action(); - while (fileResult.IsError) - { - bool? askResult = dialogParent.ModalMessageBox3( - caption: "Failed to write file", - text: $"{message} Do you want to try again?" - + $"\nError details: {fileResult.UserFriendlyErrorMessage()}" - + $"\n\nFull info for debugging:\n--------------------\n{fileResult.Exception}\n--------------------" - + "\n\n(Retry?)", - icon: EMsgBoxIcon.Warning); - if (askResult == null) return TryAgainResult.Canceled; - if (askResult == false) return TryAgainResult.IgnoredFailure; - if (askResult == true) fileResult = action(); - } - - return TryAgainResult.Saved; - } - /// Creates and shows a System.Windows.Forms.OpenFileDialog or equivalent with the receiver () as its parent /// OpenFileDialog.RestoreDirectory (isn't this useless when specifying ? keeping it for backcompat) /// OpenFileDialog.Filter diff --git a/src/BizHawk.Client.Common/FileWriteResult.cs b/src/BizHawk.Client.Common/FileWriteResult.cs deleted file mode 100644 index c11193e347c..00000000000 --- a/src/BizHawk.Client.Common/FileWriteResult.cs +++ /dev/null @@ -1,131 +0,0 @@ -#nullable enable - -using System.Diagnostics; - -namespace BizHawk.Client.Common -{ - public enum FileWriteEnum - { - Success, - // Failures during a FileWriter write. - FailedToOpen, - FailedDuringWrite, - FailedToDeleteOldBackup, - FailedToMakeBackup, - FailedToDeleteOldFile, - FailedToRename, - Aborted, - // Failures from other sources - FailedToDeleteGeneric, - FailedToMoveForSwap, - } - - /// - /// Provides information about the success or failure of an attempt to write to a file. - /// - public class FileWriteResult - { - public readonly FileWriteEnum Error = FileWriteEnum.Success; - public readonly Exception? Exception; - internal readonly FileWritePaths Paths; - - public bool IsError => Error != FileWriteEnum.Success; - - public FileWriteResult(FileWriteEnum error, FileWritePaths writer, Exception? exception) - { - Error = error; - Exception = exception; - Paths = writer; - } - - public FileWriteResult() : this(FileWriteEnum.Success, new("", ""), null) { } - - /// - /// Converts this instance to a different generic type. - /// The new instance will take the value given only if this instance has no error. - /// - /// The value of the new instance. Ignored if this instance has an error. - public FileWriteResult Convert(T value) where T : class - { - if (Error == FileWriteEnum.Success) return new(value, Paths); - else return new(this); - } - - public FileWriteResult(FileWriteResult other) : this(other.Error, other.Paths, other.Exception) { } - - public string UserFriendlyErrorMessage() - { - Debug.Assert(!IsError || (Exception != null), "FileWriteResult with an error should have an exception."); - - switch (Error) - { - // We include the full path since the user may not have explicitly given a directory and may not know what it is. - case FileWriteEnum.Success: - return $"The file \"{Paths.Final}\" was written successfully."; - case FileWriteEnum.FailedToOpen: - if (Paths.Final != Paths.Temp) - { - return $"The temporary file \"{Paths.Temp}\" could not be opened."; - } - return $"The file \"{Paths.Final}\" could not be created."; - case FileWriteEnum.FailedDuringWrite: - return $"An error occurred while writing the file."; // No file name here; it should be deleted. - case FileWriteEnum.Aborted: - return "The operation was aborted."; - - case FileWriteEnum.FailedToDeleteGeneric: - return $"The file \"{Paths.Final}\" could not be deleted."; - //case FileWriteEnum.FailedToDeleteForSwap: - // return $"Failed to swap files. Unable to write to \"{Paths.Final}\""; - case FileWriteEnum.FailedToMoveForSwap: - return $"Failed to swap files. Unable to rename \"{Paths.Temp}\" to \"{Paths.Final}\""; - } - - string success = $"The file was created successfully at \"{Paths.Temp}\" but could not be moved"; - switch (Error) - { - case FileWriteEnum.FailedToDeleteOldBackup: - return $"{success}. Unable to remove old backup file \"{Paths.Backup}\"."; - case FileWriteEnum.FailedToMakeBackup: - return $"{success}. Unable to create backup. Failed to move \"{Paths.Final}\" to \"{Paths.Backup}\"."; - case FileWriteEnum.FailedToDeleteOldFile: - return $"{success}. Unable to remove the old file \"{Paths.Final}\"."; - case FileWriteEnum.FailedToRename: - return $"{success} to \"{Paths.Final}\"."; - default: - return "unreachable"; - } - } - } - - /// - /// Provides information about the success or failure of an attempt to write to a file. - /// If successful, also provides a related object instance. - /// - public class FileWriteResult : FileWriteResult where T : class // Note: "class" also means "notnull". - { - /// - /// Value will be null if is true. - /// Otherwise, Value will not be null. - /// - public readonly T? Value = default; - - internal FileWriteResult(FileWriteEnum error, FileWritePaths paths, Exception? exception) : base(error, paths, exception) { } - - internal FileWriteResult(T value, FileWritePaths paths) : base(FileWriteEnum.Success, paths, null) - { - Debug.Assert(value != null, "Should not give a null value on success. Use the non-generic type if there is no value."); - Value = value; - } - - public FileWriteResult(FileWriteResult other) : base(other.Error, other.Paths, other.Exception) { } - } - - /// - /// This only exists as a way to avoid changing the API behavior. - /// - public class UnlessUsingApiException : Exception - { - public UnlessUsingApiException(string message) : base(message) { } - } -} diff --git a/src/BizHawk.Client.Common/FileWriter.cs b/src/BizHawk.Client.Common/FileWriter.cs deleted file mode 100644 index fdbdda72ab2..00000000000 --- a/src/BizHawk.Client.Common/FileWriter.cs +++ /dev/null @@ -1,242 +0,0 @@ -#nullable enable - -using System.Diagnostics; -using System.IO; -using System.Threading; - -using BizHawk.Common.StringExtensions; - -namespace BizHawk.Client.Common -{ - public class FileWritePaths(string final, string temp) - { - public readonly string Final = final; - public readonly string Temp = temp; - public string? Backup; - } - - /// - /// Provides a mechanism for safely overwriting files, by using a temporary file that only replaces the original after writing has been completed. - /// Optionally makes a backup of the original file. - /// - public class FileWriter : IDisposable - { - - private FileStream? _stream; // is never null until this.Dispose() - public FileStream Stream - { - get => _stream ?? throw new ObjectDisposedException("Cannot access a disposed FileStream."); - } - public FileWritePaths Paths; - - public bool UsingTempFile => Paths.Temp != Paths.Final; - - private bool _finished = false; - - private FileWriter(FileWritePaths paths, FileStream stream) - { - Paths = paths; - _stream = stream; - } - - public static FileWriteResult Write(string path, byte[] bytes, string? backupPath = null) - { - FileWriteResult createResult = Create(path); - if (createResult.IsError) return createResult; - - try - { - createResult.Value!.Stream.Write(bytes); - } - catch (Exception ex) - { - return new(FileWriteEnum.FailedDuringWrite, createResult.Value!.Paths, ex); - } - - return createResult.Value.CloseAndDispose(backupPath); - } - - public static FileWriteResult Write(string path, Action writeCallback, string? backupPath = null) - { - FileWriteResult createResult = Create(path); - if (createResult.IsError) return createResult; - - try - { - writeCallback(createResult.Value!.Stream); - } - catch (Exception ex) - { - return new(FileWriteEnum.FailedDuringWrite, createResult.Value!.Paths, ex); - } - - return createResult.Value.CloseAndDispose(backupPath); - } - - /// - /// Create a FileWriter instance, or return an error if unable to access the file. - /// - public static FileWriteResult Create(string path) - { - string writePath = path; - // If the file already exists, we will write to a temporary location first and preserve the old one until we're done. - if (File.Exists(path)) - { - writePath = path.InsertBeforeLast('.', ".saving", out bool inserted); - if (!inserted) writePath = $"{path}.saving"; - - // The file might already exist, if a prior file write failed. - // Maybe the user should have dealt with this on the previously failed save. - // But we want to support plain old "try again", so let's ignore that. - } - FileWritePaths paths = new(path, writePath); - try - { - var parentDir = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(parentDir)) Directory.CreateDirectory(parentDir); - FileStream fs = new(writePath, FileMode.Create, FileAccess.Write); - return new(new FileWriter(paths, fs), paths); - } - catch (Exception ex) // There are many exception types that file operations might raise. - { - return new(FileWriteEnum.FailedToOpen, paths, ex); - } - } - - /// - /// This method must be called after writing has finished and must not be called twice. - /// Dispose will be called regardless of the result. - /// - /// If not null, renames the original file to this path. - /// If called twice. - public FileWriteResult CloseAndDispose(string? backupPath = null) - { - // In theory it might make sense to allow the user to try again if we fail inside this method. - // If we implement that, it is probably best to make a static method that takes a FileWriteResult. - // So even then, this method should not ever be called twice. - if (_finished) throw new InvalidOperationException("Cannot close twice."); - - _finished = true; - Dispose(); - - Paths.Backup = backupPath; - if (!UsingTempFile) - { - // The chosen file did not already exist, so there is nothing to back up and nothing to rename. - return new(FileWriteEnum.Success, Paths, null); - } - - try - { - // When everything goes right, this is all we need. - File.Replace(Paths.Temp, Paths.Final, backupPath); - return new(FileWriteEnum.Success, Paths, null); - } - catch - { - // When things go wrong, we have to do a lot of work in order to - // figure out what went wrong and tell the user. - return FindTheError(); - } - } - - private FileWriteResult FindTheError() - { - // It is an unfortunate reality that .NET provides horrible exception messages - // when using File.Replace(source, destination, backup). They are not only - // unhelpful by not telling which file operation failed, but can also be a lie. - // File.Move isn't great either. - // So, we will split this into multiple parts and subparts. - - // 1) Handle backup file, if necessary - // a) Delete the old backup, if it exists. We check existence here to avoid DirectoryNotFound errors. - // If this fails, return that failure. - // If it succeeded but the file somehow still exists, report that error. - // b) Ensure the target directory exists. - // Rename the original file, and similarly report any errors. - // 2) Handle renaming of temp file, the same way renaming of original for backup was done. - - if (Paths.Backup != null) - { - try { DeleteIfExists(Paths.Backup); } - catch (Exception ex) { return new(FileWriteEnum.FailedToDeleteOldBackup, Paths, ex); } - if (!TryWaitForFileToVanish(Paths.Backup)) return new(FileWriteEnum.FailedToDeleteOldBackup, Paths, new Exception("The file was supposedly deleted but is still there.")); - - try { MoveFile(Paths.Final, Paths.Backup); } - catch (Exception ex) { return new(FileWriteEnum.FailedToMakeBackup, Paths, ex); } - if (!TryWaitForFileToVanish(Paths.Final)) return new(FileWriteEnum.FailedToMakeBackup, Paths, new Exception("The file was supposedly moved but is still in the orignal location.")); - } - - try { DeleteIfExists(Paths.Final); } - catch (Exception ex) { return new(FileWriteEnum.FailedToDeleteOldFile, Paths, ex); } - if (!TryWaitForFileToVanish(Paths.Final)) return new(FileWriteEnum.FailedToDeleteOldFile, Paths, new Exception("The file was supposedly deleted but is still there.")); - - try { MoveFile(Paths.Temp, Paths.Final); } - catch (Exception ex) { return new(FileWriteEnum.FailedToRename, Paths, ex); } - if (!TryWaitForFileToVanish(Paths.Temp)) return new(FileWriteEnum.FailedToRename, Paths, new Exception("The file was supposedly moved but is still in the orignal location.")); - - return new(FileWriteEnum.Success, Paths, null); - } - - /// - /// Closes and deletes the file. Use if there was an error while writing. - /// Do not call after this. - /// - public void Abort() - { - if (_dispoed) throw new ObjectDisposedException("Cannot use a disposed file stream."); - _finished = true; - Dispose(); - - try - { - // Delete because the file is almost certainly useless and just clutter. - File.Delete(Paths.Temp); - } - catch { /* eat? this is probably not very important */ } - } - - private bool _dispoed; - public void Dispose() - { - if (_dispoed) return; - _dispoed = true; - - _stream!.Dispose(); - _stream = null; - - // The caller should call CloseAndDispose and handle potential failure. - Debug.Assert(_finished, $"{nameof(FileWriteResult)} should not be disposed before calling {nameof(CloseAndDispose)}"); - } - - - private static void DeleteIfExists(string path) - { - if (File.Exists(path)) - { - File.Delete(path); - } - } - - private static void MoveFile(string source, string destination) - { - FileInfo file = new(destination); - file.Directory.Create(); - File.Move(source, destination); - } - - /// - /// Supposedly it is possible for File.Delete to return before the file has actually been deleted. - /// And File.Move too, I guess. - /// - private static bool TryWaitForFileToVanish(string path) - { - for (var i = 25; i != 0; i--) - { - if (!File.Exists(path)) return true; - Thread.Sleep(10); - } - return false; - } - } -} diff --git a/src/BizHawk.Client.Common/FrameworkZipWriter.cs b/src/BizHawk.Client.Common/FrameworkZipWriter.cs index a8c9fcd6521..2f99e0d60f0 100644 --- a/src/BizHawk.Client.Common/FrameworkZipWriter.cs +++ b/src/BizHawk.Client.Common/FrameworkZipWriter.cs @@ -11,17 +11,16 @@ public class FrameworkZipWriter : IZipWriter { private ZipArchive? _archive; - private FileWriter? _fs; + private FileStream? _fs; private Zstd? _zstd; private readonly CompressionLevel _level; private readonly int _zstdCompressionLevel; - private Exception? _writeException = null; - private bool _disposed; - - private FrameworkZipWriter(int compressionLevel) + public FrameworkZipWriter(string path, int compressionLevel) { + _fs = new(path, FileMode.Create, FileAccess.Write); + _archive = new(_fs, ZipArchiveMode.Create, leaveOpen: true); if (compressionLevel == 0) _level = CompressionLevel.NoCompression; else if (compressionLevel < 5) @@ -35,95 +34,32 @@ private FrameworkZipWriter(int compressionLevel) _zstdCompressionLevel = compressionLevel * 2 + 1; } - public static FileWriteResult Create(string path, int compressionLevel) - { - FileWriteResult fs = FileWriter.Create(path); - if (fs.IsError) return new(fs); - - FrameworkZipWriter ret = new(compressionLevel); - ret._fs = fs.Value!; - ret._archive = new(ret._fs.Stream, ZipArchiveMode.Create, leaveOpen: true); - - return fs.Convert(ret); - } - - public FileWriteResult CloseAndDispose(string? backupPath = null) - { - if (_archive == null || _fs == null) throw new ObjectDisposedException("Cannot use disposed ZipWriter."); - - // We actually have to do this here since it has to be done before the file stream is closed. - _archive.Dispose(); - _archive = null; - - FileWriteResult ret; - if (_writeException == null) - { - ret = _fs.CloseAndDispose(backupPath); - } - else - { - ret = new(FileWriteEnum.FailedDuringWrite, _fs.Paths, _writeException); - _fs.Abort(); - } - - // And since we have to close stuff, there's really no point in not disposing here. - Dispose(); - return ret; - } - - public void Abort() - { - if (_archive == null || _fs == null) throw new ObjectDisposedException("Cannot use disposed ZipWriter."); - - _archive.Dispose(); - _archive = null; - - _fs.Abort(); - - Dispose(); - } - public void WriteItem(string name, Action callback, bool zstdCompress) { - if (_archive == null || _zstd == null) throw new ObjectDisposedException("Cannot use disposed ZipWriter."); - if (_writeException != null) return; + // don't compress with deflate if we're already compressing with zstd + // this won't produce meaningful compression, and would just be a timesink + using var stream = _archive!.CreateEntry(name, zstdCompress ? CompressionLevel.NoCompression : _level).Open(); - try + if (zstdCompress) { - // don't compress with deflate if we're already compressing with zstd - // this won't produce meaningful compression, and would just be a timesink - using var stream = _archive.CreateEntry(name, zstdCompress ? CompressionLevel.NoCompression : _level).Open(); - - if (zstdCompress) - { - using var z = _zstd.CreateZstdCompressionStream(stream, _zstdCompressionLevel); - callback(z); - } - else - { - callback(stream); - } + using var z = _zstd!.CreateZstdCompressionStream(stream, _zstdCompressionLevel); + callback(z); } - catch (Exception ex) + else { - _writeException = ex; - // We aren't returning the failure until closing. Should we? I don't want to refactor that much calling code without a good reason. + callback(stream); } } public void Dispose() { - if (_disposed) return; - _disposed = true; - - // _archive should already be disposed by CloseAndDispose, but just in case _archive?.Dispose(); _archive = null; - _zstd!.Dispose(); - _zstd = null; - - _fs!.Dispose(); + _fs?.Flush(flushToDisk: true); + _fs?.Dispose(); _fs = null; + _zstd?.Dispose(); + _zstd = null; } } } diff --git a/src/BizHawk.Client.Common/IMainFormForApi.cs b/src/BizHawk.Client.Common/IMainFormForApi.cs index 7f14768cc4b..63105920310 100644 --- a/src/BizHawk.Client.Common/IMainFormForApi.cs +++ b/src/BizHawk.Client.Common/IMainFormForApi.cs @@ -47,7 +47,7 @@ public interface IMainFormForApi void CloseEmulator(int? exitCode = null); /// only referenced from - void LoadNullRom(bool clearSram = false); + void CloseRom(bool clearSram = false); /// only referenced from IDecodeResult DecodeCheatForAPI(string code, out MemoryDomain/*?*/ domain); @@ -56,7 +56,7 @@ public interface IMainFormForApi void EnableRewind(bool enabled); /// only referenced from - FileWriteResult FlushSaveRAM(bool autosave = false); + bool FlushSaveRAM(bool autosave = false); /// only referenced from void FrameAdvance(bool discardApiHawkSurfaces = true); @@ -100,17 +100,11 @@ public interface IMainFormForApi /// only referenced from bool RestartMovie(); - FileWriteResult SaveQuickSave(int slot, bool suppressOSD = false); + /// only referenced from + void SaveQuickSave(int slot, bool suppressOSD = false, bool fromLua = false); - /// - /// Creates a savestate and writes it to a file. - /// - /// The path of the file to write. - /// The name to use for the state on the client's on-screen display. - /// If true, the client will not show a success message. - /// Returns a value indicating if there was an error and (if there was) why. /// referenced from and - FileWriteResult SaveState(string path, string userFriendlyStateName, bool suppressOSD = false); + void SaveState(string path, string userFriendlyStateName, bool fromLua = false, bool suppressOSD = false); /// only referenced from void StepRunLoop_Throttle(); diff --git a/src/BizHawk.Client.Common/IZipWriter.cs b/src/BizHawk.Client.Common/IZipWriter.cs index 98a3762daf3..aba28cc0690 100644 --- a/src/BizHawk.Client.Common/IZipWriter.cs +++ b/src/BizHawk.Client.Common/IZipWriter.cs @@ -7,18 +7,5 @@ namespace BizHawk.Client.Common public interface IZipWriter : IDisposable { void WriteItem(string name, Action callback, bool zstdCompress); - - /// - /// This method must be called after writing has finished and must not be called twice. - /// Dispose will be called regardless of the result. - /// - /// If not null, renames the original file to this path. - FileWriteResult CloseAndDispose(string? backupPath = null); - - /// - /// Closes and deletes the file. Use if there was an error while writing. - /// Do not call after this. - /// - void Abort(); } } diff --git a/src/BizHawk.Client.Common/SaveSlotManager.cs b/src/BizHawk.Client.Common/SaveSlotManager.cs index 774e9821034..ea95e684f86 100644 --- a/src/BizHawk.Client.Common/SaveSlotManager.cs +++ b/src/BizHawk.Client.Common/SaveSlotManager.cs @@ -69,65 +69,31 @@ public void ToggleRedo(IMovie movie, int slot) public bool IsRedo(IMovie movie, int slot) => slot is >= 1 and <= 10 && movie is not ITasMovie && _redo[slot - 1]; - /// - /// Takes the .state and .bak files and swaps them - /// - public FileWriteResult SwapBackupSavestate(IMovie movie, string path, int currentSlot) + public void SwapBackupSavestate(IMovie movie, string path, int currentSlot) { - string backupPath = $"{path}.bak"; - string tempPath = $"{path}.bak.tmp"; - + // Takes the .state and .bak files and swaps them var state = new FileInfo(path); - var backup = new FileInfo(backupPath); + var backup = new FileInfo($"{path}.bak"); + var temp = new FileInfo($"{path}.bak.tmp"); if (!state.Exists || !backup.Exists) { - return new(); + return; } - // Delete old temp file if it exists. - try - { - if (File.Exists(tempPath)) File.Delete(tempPath); - } - catch (Exception ex) + if (temp.Exists) { - return new(FileWriteEnum.FailedToDeleteGeneric, new(tempPath, ""), ex); + temp.Delete(); } - // Move backup to temp. - try - { - backup.MoveTo(tempPath); - } - catch (Exception ex) - { - return new(FileWriteEnum.FailedToMoveForSwap, new(tempPath, backupPath), ex); - } - // Move current to backup. - try - { - state.MoveTo(backupPath); - } - catch (Exception ex) - { - // Attempt to restore the backup - try { backup.MoveTo(backupPath); } catch { /* eat? unlikely to fail here */ } - return new(FileWriteEnum.FailedToMoveForSwap, new(backupPath, path), ex); - } - // Move backup to current. - try - { - backup.MoveTo(path); - } - catch (Exception ex) - { - // Should we attempt to restore? Unlikely to fail here since we've already touched all files. - return new(FileWriteEnum.FailedToMoveForSwap, new(path, tempPath), ex); - } + backup.CopyTo($"{path}.bak.tmp"); + backup.Delete(); + state.CopyTo($"{path}.bak"); + state.Delete(); + temp.CopyTo(path); + temp.Delete(); ToggleRedo(movie, currentSlot); - return new(); } } } diff --git a/src/BizHawk.Client.Common/config/ConfigService.cs b/src/BizHawk.Client.Common/config/ConfigService.cs index 357d02ecc49..70286ddedb4 100644 --- a/src/BizHawk.Client.Common/config/ConfigService.cs +++ b/src/BizHawk.Client.Common/config/ConfigService.cs @@ -104,14 +104,19 @@ public static bool IsFromSameVersion(string filepath, out string msg) return config ?? new T(); } - public static FileWriteResult Save(string filepath, object config) + public static void Save(string filepath, object config) { - return FileWriter.Write(filepath, (fs) => + var file = new FileInfo(filepath); + try { - using var writer = new StreamWriter(fs); + using var writer = file.CreateText(); var w = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; Serializer.Serialize(w, config); - }); + } + catch + { + /* Eat it */ + } } // movie 1.0 header stuff diff --git a/src/BizHawk.Client.Common/lua/LuaFileList.cs b/src/BizHawk.Client.Common/lua/LuaFileList.cs index fceb0620c44..5cc579ffd86 100644 --- a/src/BizHawk.Client.Common/lua/LuaFileList.cs +++ b/src/BizHawk.Client.Common/lua/LuaFileList.cs @@ -102,8 +102,9 @@ public bool Load(string path, bool disableOnLoad, Action onFunctionListChange) return true; } - public FileWriteResult Save(string path) + public void Save(string path) { + using var sw = new StreamWriter(path); var sb = new StringBuilder(); var saveDirectory = Path.GetDirectoryName(Path.GetFullPath(path)); foreach (var file in this) @@ -122,19 +123,10 @@ public FileWriteResult Save(string path) } } - FileWriteResult result = FileWriter.Write(path, (fs) => - { - using var sw = new StreamWriter(fs); - sw.Write(sb.ToString()); - }); - - if (!result.IsError) - { - Filename = path; - Changes = false; - } + sw.Write(sb.ToString()); - return result; + Filename = path; + Changes = false; } } } diff --git a/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs b/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs index aa4c913c243..945a216c7a8 100644 --- a/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs +++ b/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs @@ -71,7 +71,7 @@ public static IMovie ToBk2(this IMovie old) return bk2; } - public static FileWriteResult ConvertToSavestateAnchoredMovie(this ITasMovie old, int frame, byte[] savestate) + public static ITasMovie ConvertToSavestateAnchoredMovie(this ITasMovie old, int frame, byte[] savestate) { string newFilename = ConvertFileNameToTasMovie(old.Filename); @@ -115,11 +115,11 @@ public static FileWriteResult ConvertToSavestateAnchoredMovie(this IT } } - FileWriteResult saveResult = tas.Save(); - return saveResult.Convert(tas); + tas.Save(); + return tas; } - public static FileWriteResult ConvertToSaveRamAnchoredMovie(this ITasMovie old, byte[] saveRam) + public static ITasMovie ConvertToSaveRamAnchoredMovie(this ITasMovie old, byte[] saveRam) { string newFilename = ConvertFileNameToTasMovie(old.Filename); @@ -146,8 +146,8 @@ public static FileWriteResult ConvertToSaveRamAnchoredMovie(this ITas tas.Subtitles.Add(sub); } - FileWriteResult saveResult = tas.Save(); - return saveResult.Convert(tas); + tas.Save(); + return tas; } #pragma warning disable RCS1224 // private but for unit test diff --git a/src/BizHawk.Client.Common/movie/MovieSession.cs b/src/BizHawk.Client.Common/movie/MovieSession.cs index 52055e4ce9b..eaaeef1fdc5 100644 --- a/src/BizHawk.Client.Common/movie/MovieSession.cs +++ b/src/BizHawk.Client.Common/movie/MovieSession.cs @@ -244,9 +244,8 @@ public void RunQueuedMovie(bool recordMode, IEmulator emulator) public void AbortQueuedMovie() => _queuedMovie = null; - public FileWriteResult StopMovie(bool saveChanges = true) + public void StopMovie(bool saveChanges = true) { - FileWriteResult/*?*/ result = null; if (Movie.IsActive()) { var message = "Movie "; @@ -263,17 +262,8 @@ public FileWriteResult StopMovie(bool saveChanges = true) if (saveChanges && Movie.Changes) { - result = Movie.Save(); - if (result.IsError) - { - Output($"Failed to write {Path.GetFileName(Movie.Filename)} to disk."); - Output(result.UserFriendlyErrorMessage()); - return result; - } - else - { - Output($"{Path.GetFileName(Movie.Filename)} written to disk."); - } + Movie.Save(); + Output($"{Path.GetFileName(Movie.Filename)} written to disk."); } Movie.Stop(); @@ -289,8 +279,6 @@ public FileWriteResult StopMovie(bool saveChanges = true) } Movie = null; - - return result ?? new(); } public IMovie Get(string path, bool loadMovie) @@ -385,8 +373,6 @@ private void HandlePlaybackEnd() switch (Settings.MovieEndAction) { case MovieEndAction.Stop: - // Technically this can save the movie, but it'd be weird to be in that situation. - // Do we want that? StopMovie(); break; case MovieEndAction.Record: diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index 4b53ec7cd0a..6e8ca063afc 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Globalization; using System.IO; using System.Runtime.InteropServices; @@ -14,25 +12,25 @@ namespace BizHawk.Client.Common { public partial class Bk2Movie { - public FileWriteResult Save() + public void Save() { - return Write(Filename); + Write(Filename); } - public FileWriteResult SaveBackup() + public void SaveBackup() { if (string.IsNullOrWhiteSpace(Filename)) { - return new(); + return; } - string backupName = Filename.InsertBeforeLast('.', insert: $".{DateTime.Now:yyyy-MM-dd HH.mm.ss}", out _); + var backupName = Filename.InsertBeforeLast('.', insert: $".{DateTime.Now:yyyy-MM-dd HH.mm.ss}", out _); backupName = Path.Combine(Session.BackupDirectory, Path.GetFileName(backupName)); - return Write(backupName, isBackup: true); + Write(backupName, isBackup: true); } - protected virtual FileWriteResult Write(string fn, bool isBackup = false) + protected virtual void Write(string fn, bool isBackup = false) { SetCycleValues(); // EmulatorVersion used to store the unchanging original emulator version. @@ -43,27 +41,13 @@ protected virtual FileWriteResult Write(string fn, bool isBackup = false) Header[HeaderKeys.EmulatorVersion] = VersionInfo.GetEmuVersion(); Directory.CreateDirectory(Path.GetDirectoryName(fn)!); - var createResult = ZipStateSaver.Create(fn, Session.Settings.MovieCompressionLevel); - if (createResult.IsError) return createResult; + using var bs = new ZipStateSaver(fn, Session.Settings.MovieCompressionLevel); + AddLumps(bs, isBackup); - ZipStateSaver saver = createResult.Value!; - try - { - AddLumps(saver, isBackup); - } - catch (Exception ex) - { - saver.Abort(); - return new(FileWriteEnum.FailedDuringWrite, createResult.Paths, ex); - } - - FileWriteResult result = saver.CloseAndDispose(); - if (!isBackup && !result.IsError) + if (!isBackup) { Changes = false; } - - return result; } 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 @@ -150,7 +134,7 @@ private void LoadBk2Fields(ZipStateLoader bl) bl.GetLump(BinaryStateLump.SyncSettings, abort: false, tr => { - string? line; + string line; while ((line = tr.ReadLine()) != null) { if (!string.IsNullOrWhiteSpace(line)) diff --git a/src/BizHawk.Client.Common/movie/import/IMovieImport.cs b/src/BizHawk.Client.Common/movie/import/IMovieImport.cs index c183d980ec9..91a30f734fa 100644 --- a/src/BizHawk.Client.Common/movie/import/IMovieImport.cs +++ b/src/BizHawk.Client.Common/movie/import/IMovieImport.cs @@ -67,11 +67,7 @@ public ImportResult Import( Result.Movie.Hash = hash; } - if (Result.Movie.Save().IsError) - { - Result.Errors.Add($"Could not write the file {newFileName}"); - return Result; - } + Result.Movie.Save(); } return Result; diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs index 3455f2e0ced..0670e548a44 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs @@ -81,12 +81,12 @@ public interface IMovie : IBasicMovieInfo /// /// Forces the creation of a backup file of the current movie state /// - FileWriteResult SaveBackup(); + void SaveBackup(); /// /// Instructs the movie to save the current contents to Filename /// - FileWriteResult Save(); + void Save(); /// updates the and headers from the currently loaded core void SetCycleValues(); diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs index 3ab9a23158e..908f3b75ed9 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovieSession.cs @@ -83,7 +83,7 @@ void QueueNewMovie( /// clears the queued movie void AbortQueuedMovie(); - FileWriteResult StopMovie(bool saveChanges = true); + void StopMovie(bool saveChanges = true); /// /// Create a new (Tas)Movie with the given path as filename. If is true, diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index fd72d9fed43..1d4bae7529b 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -216,9 +216,7 @@ public override bool ExtractInputLog(TextReader reader, out string errorMessage) // We are in record mode so replace the movie log with the one from the savestate if (Session.Settings.EnableBackupMovies && MakeBackup && Log.Count != 0) { - // TODO: This isn't ideal, but making it ideal would mean a big refactor. - FileWriteResult saveResult = SaveBackup(); - if (saveResult.Exception != null) throw saveResult.Exception; + SaveBackup(); MakeBackup = false; } diff --git a/src/BizHawk.Client.Common/savestates/SavestateFile.cs b/src/BizHawk.Client.Common/savestates/SavestateFile.cs index 966ca04c234..d2558a9b044 100644 --- a/src/BizHawk.Client.Common/savestates/SavestateFile.cs +++ b/src/BizHawk.Client.Common/savestates/SavestateFile.cs @@ -58,16 +58,14 @@ public SavestateFile( _userBag = userBag; } - public FileWriteResult Create(string filename, SaveStateConfig config, bool makeBackup) + public void Create(string filename, SaveStateConfig config) { - FileWriteResult createResult = ZipStateSaver.Create(filename, config.CompressionLevelNormal); - if (createResult.IsError) return createResult; - var bs = createResult.Value!; + // the old method of text savestate save is now gone. + // a text savestate is just like a binary savestate, but with a different core lump + using var bs = new ZipStateSaver(filename, config.CompressionLevelNormal); using (new SimpleTime("Save Core")) { - // the old method of text savestate save is now gone. - // a text savestate is just like a binary savestate, but with a different core lump if (config.Type == SaveStateType.Text) { bs.PutLump(BinaryStateLump.CorestateText, tw => _statable.SaveStateText(tw)); @@ -131,9 +129,6 @@ public FileWriteResult Create(string filename, SaveStateConfig config, bool make { bs.PutLump(BinaryStateLump.LagLog, tw => tasMovie.LagLog.Save(tw), zstdCompress: true); } - - makeBackup = makeBackup && config.MakeBackups; - return bs.CloseAndDispose(makeBackup ? $"{filename}.bak" : null); } public bool Load(string path, IDialogParent dialogParent) diff --git a/src/BizHawk.Client.Common/savestates/ZipStateSaver.cs b/src/BizHawk.Client.Common/savestates/ZipStateSaver.cs index 68dcd337a69..6a01f6c60b9 100644 --- a/src/BizHawk.Client.Common/savestates/ZipStateSaver.cs +++ b/src/BizHawk.Client.Common/savestates/ZipStateSaver.cs @@ -27,9 +27,9 @@ private static void WriteEmuVersion(Stream s) sw.WriteLine(VersionInfo.GetEmuVersion()); } - private ZipStateSaver(FrameworkZipWriter zip) + public ZipStateSaver(string path, int compressionLevel) { - _zip = zip; + _zip = new FrameworkZipWriter(path, compressionLevel); // we put these in every zip, so we know where they came from // a bit redundant for movie files given their headers, but w/e @@ -37,35 +37,6 @@ private ZipStateSaver(FrameworkZipWriter zip) PutLump(BinaryStateLump.BizVersion, WriteEmuVersion, false); } - public static FileWriteResult Create(string path, int compressionLevel) - { - FileWriteResult result = FrameworkZipWriter.Create(path, compressionLevel); - if (result.IsError) return new(result); - else return result.Convert(new ZipStateSaver(result.Value!)); - } - - /// - /// This method must be called after writing has finished and must not be called twice. - /// Dispose will be called regardless of the result. - /// - /// If not null, renames the original file to this path. - public FileWriteResult CloseAndDispose(string? backupPath = null) - { - FileWriteResult result = _zip.CloseAndDispose(backupPath); - Dispose(); - return result; - } - - /// - /// Closes and deletes the file. Use if there was an error while writing. - /// Do not call after this. - /// - public void Abort() - { - _zip.Abort(); - Dispose(); - } - public void PutLump(BinaryStateLump lump, Action callback, bool zstdCompress = true) { var filePath = AsTarbomb ? lump.FileName : $"{TOP_LEVEL_DIR_NAME}/{lump.FileName}"; diff --git a/src/BizHawk.Client.Common/tools/CheatList.cs b/src/BizHawk.Client.Common/tools/CheatList.cs index c00013e1f0c..a9cca688a9a 100644 --- a/src/BizHawk.Client.Common/tools/CheatList.cs +++ b/src/BizHawk.Client.Common/tools/CheatList.cs @@ -88,10 +88,20 @@ public bool AttemptToLoadCheatFile(IMemoryDomains domains) return file.Exists && Load(domains, file.FullName, false); } - public void NewList(string defaultFileName) + public void NewList(string defaultFileName, bool autosave = false) { _defaultFileName = defaultFileName; + if (autosave && _changes && _cheatList.Count is not 0) + { + if (string.IsNullOrEmpty(CurrentFileName)) + { + CurrentFileName = _defaultFileName; + } + + Save(); + } + _cheatList.Clear(); CurrentFileName = ""; Changes = false; @@ -210,7 +220,7 @@ public void DisableAll() public bool IsActive(MemoryDomain domain, long address) => _cheatList.Exists(cheat => !cheat.IsSeparator && cheat.Enabled && cheat.Domain == domain && cheat.Contains(address)); - public FileWriteResult SaveOnClose() + public void SaveOnClose() { if (_config.AutoSaveOnClose) { @@ -221,27 +231,17 @@ public FileWriteResult SaveOnClose() CurrentFileName = _defaultFileName; } - return SaveFile(CurrentFileName); + SaveFile(CurrentFileName); } else if (_cheatList.Count is 0 && !string.IsNullOrWhiteSpace(CurrentFileName)) { - try - { - File.Delete(CurrentFileName); - } - catch (Exception ex) - { - return new(FileWriteEnum.FailedToDeleteGeneric, new(CurrentFileName, ""), ex); - } + File.Delete(CurrentFileName); _config.Recent.Remove(CurrentFileName); - return new(); } } - - return new(); } - public FileWriteResult Save() + public bool Save() { if (string.IsNullOrWhiteSpace(CurrentFileName)) { @@ -251,52 +251,54 @@ public FileWriteResult Save() return SaveFile(CurrentFileName); } - public FileWriteResult SaveFile(string path) + public bool SaveFile(string path) { - var sb = new StringBuilder(); - - foreach (var cheat in _cheatList) + try { - if (cheat.IsSeparator) - { - sb.AppendLine("----"); - } - else + new FileInfo(path).Directory?.Create(); + var sb = new StringBuilder(); + + foreach (var cheat in _cheatList) { - // Set to hex for saving - var tempCheatType = cheat.Type; - - cheat.SetType(WatchDisplayType.Hex); - - sb - .Append(cheat.AddressStr).Append('\t') - .Append(cheat.ValueStr).Append('\t') - .Append(cheat.Compare is null ? "N" : cheat.CompareStr).Append('\t') - .Append(cheat.Domain != null ? cheat.Domain.Name : "").Append('\t') - .Append(cheat.Enabled ? '1' : '0').Append('\t') - .Append(cheat.Name).Append('\t') - .Append(cheat.SizeAsChar).Append('\t') - .Append(cheat.TypeAsChar).Append('\t') - .Append(cheat.BigEndian is true ? '1' : '0').Append('\t') - .Append(cheat.ComparisonType).Append('\t') - .AppendLine(); - - cheat.SetType(tempCheatType); + if (cheat.IsSeparator) + { + sb.AppendLine("----"); + } + else + { + // Set to hex for saving + var tempCheatType = cheat.Type; + + cheat.SetType(WatchDisplayType.Hex); + + sb + .Append(cheat.AddressStr).Append('\t') + .Append(cheat.ValueStr).Append('\t') + .Append(cheat.Compare is null ? "N" : cheat.CompareStr).Append('\t') + .Append(cheat.Domain != null ? cheat.Domain.Name : "").Append('\t') + .Append(cheat.Enabled ? '1' : '0').Append('\t') + .Append(cheat.Name).Append('\t') + .Append(cheat.SizeAsChar).Append('\t') + .Append(cheat.TypeAsChar).Append('\t') + .Append(cheat.BigEndian is true ? '1' : '0').Append('\t') + .Append(cheat.ComparisonType).Append('\t') + .AppendLine(); + + cheat.SetType(tempCheatType); + } } - } - FileWriteResult result = FileWriter.Write(path, (fs) => - { - StreamWriter sw = new(fs); - sw.Write(sb.ToString()); - sw.Flush(); - }); - if (!result.IsError) - { + + File.WriteAllText(path, sb.ToString()); + CurrentFileName = path; _config.Recent.Add(CurrentFileName); Changes = false; + return true; + } + catch + { + return false; } - return result; } public bool Load(IMemoryDomains domains, string path, bool append) diff --git a/src/BizHawk.Client.Common/tools/TAStudio/MovieZone.cs b/src/BizHawk.Client.Common/tools/TAStudio/MovieZone.cs index 93faff43110..adbd1b42d29 100644 --- a/src/BizHawk.Client.Common/tools/TAStudio/MovieZone.cs +++ b/src/BizHawk.Client.Common/tools/TAStudio/MovieZone.cs @@ -156,25 +156,19 @@ private void PlaceMacroInternal(IMovie movie, int start) } } - public FileWriteResult Save(string fileName) + public void Save(string fileName) { // Save the controller definition/LogKey // Save the controller name and controller groups count. (Only for the user) // Save whether or not the macro should use overlay input, and/or replace - - return FileWriter.Write(fileName, (fs) => - { - using var writer = new StreamWriter(fs); - writer.WriteLine(InputKey); - writer.WriteLine(_movieDefinition.Name); - writer.WriteLine(_movieDefinition.ControlsOrdered.Count.ToString()); - writer.WriteLine($"{Overlay},{Replace}"); - - foreach (string line in _log) - { - writer.WriteLine(line); - } - }); + string[] header = new string[4]; + header[0] = InputKey; + header[1] = _movieDefinition.Name; + header[2] = _movieDefinition.ControlsOrdered.Count.ToString(); + header[3] = $"{Overlay},{Replace}"; + + File.WriteAllLines(fileName, header); + File.AppendAllLines(fileName, _log); } public static MovieZone/*?*/ Load(string fileName, IDialogController dialogController, IMovie movie) diff --git a/src/BizHawk.Client.Common/tools/Watch/WatchList/WatchList.cs b/src/BizHawk.Client.Common/tools/Watch/WatchList/WatchList.cs index eb5afeb1ded..0a3e12d913c 100644 --- a/src/BizHawk.Client.Common/tools/Watch/WatchList/WatchList.cs +++ b/src/BizHawk.Client.Common/tools/Watch/WatchList/WatchList.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -339,37 +338,39 @@ public void Reload() } } - public FileWriteResult Save() + public bool Save() { if (string.IsNullOrWhiteSpace(CurrentFileName)) { - return new(); + return false; } - var sb = new StringBuilder(); - sb.Append("SystemID ").AppendLine(_systemId); - - foreach (var watch in _watchList) + using (var sw = new StreamWriter(CurrentFileName)) { - sb.AppendLine(watch.ToString()); - } + var sb = new StringBuilder(); + sb.Append("SystemID ").AppendLine(_systemId); + + foreach (var watch in _watchList) + { + sb.AppendLine(watch.ToString()); + } - FileWriteResult result = FileWriter.Write(CurrentFileName, (fs) => - { - using var sw = new StreamWriter(fs); sw.WriteLine(sb.ToString()); - }); + } - if (!result.IsError) Changes = false; - return result; + Changes = false; + return true; } - public FileWriteResult SaveAs(FileInfo file) + public bool SaveAs(FileInfo file) { - Debug.Assert(file != null, "Cannot save as without a file name."); + if (file != null) + { + CurrentFileName = file.FullName; + return Save(); + } - CurrentFileName = file.FullName; - return Save(); + return false; } private bool LoadFile(string path, bool append) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs index 944fabe23fd..a2eef27097d 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -264,12 +264,12 @@ private void OpenAdvancedMenuItem_Click(object sender, EventArgs e) private void CloseRomMenuItem_Click(object sender, EventArgs e) { Console.WriteLine($"Closing rom clicked Frame: {Emulator.Frame} Emulator: {Emulator.GetType().Name}"); - LoadNullRom(); + CloseRom(); Console.WriteLine($"Closing rom clicked DONE Frame: {Emulator.Frame} Emulator: {Emulator.GetType().Name}"); } private void QuickSavestateMenuItem_Click(object sender, EventArgs e) - => SaveQuickSaveAndShowError(int.Parse(((ToolStripMenuItem) sender).Text)); + => SaveQuickSave(int.Parse(((ToolStripMenuItem) sender).Text)); private void SaveNamedStateMenuItem_Click(object sender, EventArgs e) => SaveStateAs(); @@ -305,7 +305,7 @@ private void SaveToCurrentSlotMenuItem_Click(object sender, EventArgs e) => SavestateCurrentSlot(); private void SavestateCurrentSlot() - => SaveQuickSaveAndShowError(Config.SaveSlot); + => SaveQuickSave(Config.SaveSlot); private void LoadCurrentSlotMenuItem_Click(object sender, EventArgs e) => LoadstateCurrentSlot(); @@ -315,7 +315,7 @@ private bool LoadstateCurrentSlot() private void FlushSaveRAMMenuItem_Click(object sender, EventArgs e) { - ShowMessageIfError(() => FlushSaveRAM(), "Failed to flush saveram!"); + FlushSaveRAM(); } private void ReadonlyMenuItem_Click(object sender, EventArgs e) @@ -987,15 +987,8 @@ private void HkOverInputMenuItem_Click(object sender, EventArgs e) private void SaveConfigMenuItem_Click(object sender, EventArgs e) { - FileWriteResult result = SaveConfig(); - if (result.IsError) - { - this.ErrorMessageBox(result); - } - else - { - AddOnScreenMessage("Saved settings"); - } + SaveConfig(); + AddOnScreenMessage("Saved settings"); } private void SaveConfigAsMenuItem_Click(object sender, EventArgs e) @@ -1007,15 +1000,8 @@ private void SaveConfigAsMenuItem_Click(object sender, EventArgs e) initFileName: file); if (result is not null) { - FileWriteResult saveResult = SaveConfig(result); - if (saveResult.IsError) - { - this.ErrorMessageBox(saveResult); - } - else - { - AddOnScreenMessage("Copied settings"); - } + SaveConfig(result); + AddOnScreenMessage("Copied settings"); } } @@ -1401,20 +1387,13 @@ private void ViewCommentsContextMenuItem_Click(object sender, EventArgs e) private void UndoSavestateContextMenuItem_Click(object sender, EventArgs e) { var slot = Config.SaveSlot; - FileWriteResult swapResult = _stateSlots.SwapBackupSavestate(MovieSession.Movie, $"{SaveStatePrefix()}.QuickSave{slot % 10}.State", slot); - if (swapResult.IsError) - { - this.ErrorMessageBox(swapResult, "Failed to swap state files."); - } - else - { - AddOnScreenMessage($"Save slot {slot} restored."); - } + _stateSlots.SwapBackupSavestate(MovieSession.Movie, $"{SaveStatePrefix()}.QuickSave{slot % 10}.State", slot); + AddOnScreenMessage($"Save slot {slot} restored."); } private void ClearSramContextMenuItem_Click(object sender, EventArgs e) { - LoadNullRom(clearSram: true); + CloseRom(clearSram: true); } private void ShowMenuContextMenuItem_Click(object sender, EventArgs e) @@ -1479,7 +1458,7 @@ private void SlotStatusButtons_MouseUp(object sender, MouseEventArgs e) if (sender == Slot9StatusButton) slot = 9; if (sender == Slot0StatusButton) slot = 10; - if (e.Button is MouseButtons.Right) SaveQuickSaveAndShowError(slot); + if (e.Button is MouseButtons.Right) SaveQuickSave(slot); else if (e.Button is MouseButtons.Left && HasSlot(slot)) _ = LoadQuickSave(slot); } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs index 6d28e5da85b..da6fcf4245f 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs @@ -10,7 +10,7 @@ private bool CheckHotkey(string trigger) { void SelectAndSaveToSlot(int slot) { - SaveQuickSaveAndShowError(slot); + SaveQuickSave(slot); Config.SaveSlot = slot; UpdateStatusSlots(); } @@ -120,13 +120,13 @@ void SelectAndLoadFromSlot(int slot) OpenRom(); break; case "Close ROM": - LoadNullRom(); + CloseRom(); break; case "Load Last ROM": LoadMostRecentROM(); break; case "Flush SaveRAM": - FlushSaveRAMMenuItem_Click(null, EventArgs.Empty); + FlushSaveRAM(); break; case "Display FPS": ToggleFps(); diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs b/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs index b9e7f04f946..f97519c3651 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs @@ -125,14 +125,7 @@ public void StopMovie(bool saveChanges = true) } else { - FileWriteResult saveResult = MovieSession.StopMovie(saveChanges); - if (saveResult.IsError) - { - this.ShowMessageBox( - $"Failed to save movie.\n{saveResult.UserFriendlyErrorMessage()}\n{saveResult.Exception.Message}", - "Error", - EMsgBoxIcon.Error); - } + MovieSession.StopMovie(saveChanges); SetMainformMovieInfo(); } } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 1b9e8555252..d2dd87aaeef 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -879,23 +879,20 @@ private void CheckMayCloseAndCleanup(object/*?*/ closingSender, CancelEventArgs closingArgs.Cancel = true; return; } - // StopAv would be handled in CloseGame, but since we've asked the user about it, best to handle it now. StopAv(); } - TryAgainResult configSaveResult = this.DoWithTryAgainBox(() => SaveConfig(), "Failed to save config file."); - if (configSaveResult == TryAgainResult.Canceled) + if (!Tools.AskSave()) { closingArgs.Cancel = true; return; } - if (!CloseGame()) - { - closingArgs.Cancel = true; - return; - } Tools.Close(); + MovieSession.StopMovie(); + // zero 03-nov-2015 - close game after other steps. tools might need to unhook themselves from a core. + CloseGame(); + SaveConfig(); } private readonly bool _suppressSyncSettingsWarning; @@ -1740,7 +1737,6 @@ public bool RunLibretroCoreChooser() // countdown for saveram autoflushing public int AutoFlushSaveRamIn { get; set; } - private bool AutoFlushSaveRamFailed; private void SetStatusBar() { @@ -1943,7 +1939,7 @@ private void LoadSaveRam() } } - public FileWriteResult FlushSaveRAM(bool autosave = false) + public bool FlushSaveRAM(bool autosave = false) { if (Emulator.HasSaveRam()) { @@ -1957,13 +1953,53 @@ public FileWriteResult FlushSaveRAM(bool autosave = false) path = Config.PathEntries.SaveRamAbsolutePath(Game, MovieSession.Movie); } + var file = new FileInfo(path); + var newPath = $"{path}.new"; + var newFile = new FileInfo(newPath); + var backupPath = $"{path}.bak"; + var backupFile = new FileInfo(backupPath); + var saveram = Emulator.AsSaveRam().CloneSaveRam(); if (saveram == null) - return new(); - return FileWriter.Write(path, saveram, $"{path}.bak"); + return true; + + try + { + Directory.CreateDirectory(file.DirectoryName!); + using (var fs = File.Create(newPath)) + { + fs.Write(saveram, 0, saveram.Length); + fs.Flush(flushToDisk: true); + } + + if (file.Exists) + { + if (Config.BackupSaveram) + { + if (backupFile.Exists) + { + backupFile.Delete(); + } + + file.MoveTo(backupPath); + } + else + { + file.Delete(); + } + } + + newFile.MoveTo(path); + } + catch (IOException e) + { + AddOnScreenMessage("Failed to flush saveram!"); + Console.Error.WriteLine(e); + return false; + } } - return new(); + return true; } private void RewireSound() @@ -2077,7 +2113,7 @@ private void LoadRomFromRecent(string rom) if (!LoadRom(romPath, new LoadRomArgs(ioa), out var failureIsFromAskSave)) { - if (failureIsFromAskSave) AddOnScreenMessage("ROM loading cancelled due to unsaved changes"); + if (failureIsFromAskSave) AddOnScreenMessage("ROM loading cancelled; a tool had unsaved changes"); else if (ioa is OpenAdvanced_LibretroNoGame || File.Exists(romPath)) AddOnScreenMessage("ROM loading failed"); else Config.RecentRoms.HandleLoadError(this, romPath, rom); } @@ -2394,7 +2430,7 @@ public ISettingsAdapter GetSettingsAdapterForLoadedCore() public SettingsAdapter GetSettingsAdapterForLoadedCoreUntyped() => new(Emulator, static () => true, HandlePutCoreSettings, MayPutCoreSyncSettings, HandlePutCoreSyncSettings); - private FileWriteResult SaveConfig(string path = "") + private void SaveConfig(string path = "") { if (Config.SaveWindowPosition) { @@ -2421,7 +2457,7 @@ private FileWriteResult SaveConfig(string path = "") } CommitCoreSettingsToConfig(); - return ConfigService.Save(path, Config); + ConfigService.Save(path, Config); } private void ToggleFps() @@ -2642,16 +2678,8 @@ private void SaveMovie() { if (MovieSession.Movie.IsActive()) { - FileWriteResult result = MovieSession.Movie.Save(); - if (result.IsError) - { - AddOnScreenMessage($"Failed to save {MovieSession.Movie.Filename}."); - AddOnScreenMessage(result.UserFriendlyErrorMessage()); - } - else - { - AddOnScreenMessage($"{MovieSession.Movie.Filename} saved."); - } + MovieSession.Movie.Save(); + AddOnScreenMessage($"{MovieSession.Movie.Filename} saved."); } } @@ -2988,22 +3016,8 @@ private void StepRunLoop_Core(bool force = false) AutoFlushSaveRamIn--; if (AutoFlushSaveRamIn <= 0) { - FileWriteResult result = FlushSaveRAM(true); - if (result.IsError) - { - // For autosave, allow one failure before bothering the user. - if (AutoFlushSaveRamFailed) - { - this.ErrorMessageBox(result, "Failed to flush saveram!"); - } - AutoFlushSaveRamFailed = true; - AutoFlushSaveRamIn = Math.Min(600, Config.FlushSaveRamFrames); - } - else - { - AutoFlushSaveRamFailed = false; - AutoFlushSaveRamIn = Config.FlushSaveRamFrames; - } + FlushSaveRAM(true); + AutoFlushSaveRamIn = Config.FlushSaveRamFrames; } } // why not skip audio if the user doesn't want sound @@ -3584,12 +3598,6 @@ public bool LoadRom(string path, LoadRomArgs args, out bool failureIsFromAskSave private bool LoadRomInternal(string path, LoadRomArgs args, out bool failureIsFromAskSave) { failureIsFromAskSave = false; - if (!CloseGame()) - { - failureIsFromAskSave = true; - return false; - } - if (path == null) throw new ArgumentNullException(nameof(path)); if (args == null) @@ -3617,6 +3625,12 @@ private bool LoadRomInternal(string path, LoadRomArgs args, out bool failureIsFr // it is then up to the core itself to override its own local DeterministicEmulation setting bool deterministic = args.Deterministic ?? MovieSession.NewMovieQueued; + if (!Tools.AskSave()) + { + failureIsFromAskSave = true; + return false; + } + var loader = new RomLoader(Config, this) { ChooseArchive = LoadArchiveChooser, @@ -3630,6 +3644,12 @@ private bool LoadRomInternal(string path, LoadRomArgs args, out bool failureIsFr loader.OnLoadSettings += CoreSettings; loader.OnLoadSyncSettings += CoreSyncSettings; + // this also happens in CloseGame(). But it needs to happen here since if we're restarting with the same core, + // any settings changes that we made need to make it back to config before we try to instantiate that core with + // the new settings objects + CommitCoreSettingsToConfig(); // adelikat: I Think by reordering things, this isn't necessary anymore + CloseGame(); + var nextComm = CreateCoreComm(); IOpenAdvanced ioa = args.OpenAdvanced; @@ -3799,7 +3819,7 @@ private bool LoadRomInternal(string path, LoadRomArgs args, out bool failureIsFr if (previousRom != CurrentlyOpenRom) { - CheatList.NewList(Tools.GenerateDefaultCheatFilename()); + CheatList.NewList(Tools.GenerateDefaultCheatFilename(), autosave: true); if (Config.Cheats.LoadFileByGame && Emulator.HasMemoryDomains()) { if (CheatList.AttemptToLoadCheatFile(Emulator.AsMemoryDomains())) @@ -3816,7 +3836,7 @@ private bool LoadRomInternal(string path, LoadRomArgs args, out bool failureIsFr } else { - CheatList.NewList(Tools.GenerateDefaultCheatFilename()); + CheatList.NewList(Tools.GenerateDefaultCheatFilename(), autosave: true); } } @@ -3859,7 +3879,7 @@ private bool LoadRomInternal(string path, LoadRomArgs args, out bool failureIsFr DisplayManager.UpdateGlobals(Config, Emulator); DisplayManager.Blank(); ExtToolManager.BuildToolStrip(); - CheatList.NewList(""); + CheatList.NewList("", autosave: true); OnRomChanged(); return false; } @@ -3912,100 +3932,74 @@ private void CommitCoreSettingsToConfig() } } - /// - /// This closes the game but does not set things up for using the client with the new null emulator. - /// This method should only be called (outside of ) if the caller is about to load a new game with no user interaction between close and load. - /// - /// True if the game was closed. False if the user cancelled due to unsaved changes. - private bool CloseGame(bool clearSram = false) + // whats the difference between these two methods?? + // its very tricky. rename to be more clear or combine them. + // This gets called whenever a core related thing is changed. + // Like reboot core. + private void CloseGame(bool clearSram = false) { - CommitCoreSettingsToConfig(); // Must happen before stopping the movie, since it checks for active movie. - - if (!Tools.AskSave()) - { - return false; - } - // There is a cheats tool, but cheats can be active while the "cheats tool" is not. And have auto-save option. - TryAgainResult cheatSaveResult = this.DoWithTryAgainBox(CheatList.SaveOnClose, "Failed to save cheats."); - if (cheatSaveResult == TryAgainResult.Canceled) return false; - - // If TAStudio is open, we already asked about saving the movie. - if (!Tools.IsLoaded()) - { - TryAgainResult saveMovieResult = this.DoWithTryAgainBox(() => MovieSession.StopMovie(), "Failed to save movie."); - if (saveMovieResult == TryAgainResult.Canceled) return false; - } - + GameIsClosing = true; if (clearSram) { var path = Config.PathEntries.SaveRamAbsolutePath(Game, MovieSession.Movie); if (File.Exists(path)) { - TryAgainResult clearResult = this.DoWithTryAgainBox(() => { - try - { - File.Delete(path); - AddOnScreenMessage("SRAM cleared."); - return new(); - } - catch (Exception ex) - { - return new(FileWriteEnum.FailedToDeleteGeneric, new(path, ""), ex); - } - }, "Failed to clear SRAM."); - if (clearResult == TryAgainResult.Canceled) - { - return false; - } + File.Delete(path); + AddOnScreenMessage("SRAM cleared."); } } else if (Emulator.HasSaveRam()) { - TryAgainResult flushResult = this.DoWithTryAgainBox( - () => FlushSaveRAM(), - "Failed flushing the game's Save RAM to your disk."); - if (flushResult == TryAgainResult.Canceled) return false; - } + while (true) + { + if (FlushSaveRAM()) break; - TryAgainResult stateSaveResult = this.DoWithTryAgainBox(AutoSaveStateIfConfigured, "Failed to auto-save state."); - if (stateSaveResult == TryAgainResult.Canceled) return false; + var result = ShowMessageBox3( + owner: this, + "Failed flushing the game's Save RAM to your disk.\n" + + "Do you want to try again?", + "IOError while writing SaveRAM", + EMsgBoxIcon.Error); + + if (result is false) break; + if (result is null) return; + } + } StopAv(); + AutoSaveStateIfConfigured(); + CommitCoreSettingsToConfig(); DisableRewind(); + if (MovieSession.Movie.IsActive()) // Note: this must be called after CommitCoreSettingsToConfig() + { + StopMovie(); + } + RA?.Stop(); + CheatList.SaveOnClose(); Emulator.Dispose(); - - // This stuff might belong in LoadNullRom. - // However, Emulator.IsNull is used all over and at least one use (in LoadRomInternal) appears to depend on this code being here. - // Some refactoring is needed if these things are to be actually moved to LoadNullRom. Emulator = new NullEmulator(); Game = GameInfo.NullInstance; InputManager.SyncControls(Emulator, MovieSession, Config); RewireSound(); RebootStatusBarIcon.Visible = false; - - return true; + GameIsClosing = false; } - private FileWriteResult AutoSaveStateIfConfigured() + private void AutoSaveStateIfConfigured() { - if (Config.AutoSaveLastSaveSlot && Emulator.HasSavestates()) - { - return SaveQuickSave(Config.SaveSlot); - } - - return new(); + if (Config.AutoSaveLastSaveSlot && Emulator.HasSavestates()) SavestateCurrentSlot(); } - /// - /// This closes the current ROM, closes tools that require emulator services, and sets things up for the user to interact with the client having no loaded ROM. - /// - /// True if SRAM should be deleted instead of saved. - public void LoadNullRom(bool clearSram = false) + public bool GameIsClosing { get; private set; } // Lets tools make better decisions when being called by CloseGame + + public void CloseRom(bool clearSram = false) { + // This gets called after Close Game gets called. + // Tested with NESHawk and SMB3 (U) if (Tools.AskSave()) { CloseGame(clearSram); @@ -4015,7 +4009,7 @@ public void LoadNullRom(bool clearSram = false) PauseOnFrame = null; CurrentlyOpenRom = null; CurrentlyOpenRomArgs = null; - CheatList.NewList(""); + CheatList.NewList("", autosave: true); OnRomChanged(); } } @@ -4153,36 +4147,29 @@ public bool LoadQuickSave(int slot, bool suppressOSD = false) return LoadState(path: path, userFriendlyStateName: quickSlotName, suppressOSD: suppressOSD); } - private FileWriteResult SaveStateInternal(string path, string userFriendlyStateName, bool suppressOSD, bool makeBackup) + public void SaveState(string path, string userFriendlyStateName, bool fromLua = false, bool suppressOSD = false) { if (!Emulator.HasSavestates()) { - return new(FileWriteEnum.Aborted, new("", ""), new UnlessUsingApiException("The current emulator does not support savestates.")); + return; } if (ToolControllingSavestates is { } tool) { tool.SaveState(); - // assume success by the tool: state was created, but not as a file. So no path. - return new(); + return; } if (MovieSession.Movie.IsActive() && Emulator.Frame > MovieSession.Movie.FrameCount) { - const string errmsg = "Cannot savestate after movie end!"; - AddOnScreenMessage(errmsg); - // Failed to create state due to limitations of our movie handling code. - return new(FileWriteEnum.Aborted, new("", ""), new UnlessUsingApiException(errmsg)); + AddOnScreenMessage("Cannot savestate after movie end!"); + return; } - FileWriteResult result = new SavestateFile(Emulator, MovieSession, MovieSession.UserBag) - .Create(path, Config.Savestates, makeBackup); - if (result.IsError) - { - AddOnScreenMessage($"Unable to save state {path}"); - } - else + try { + new SavestateFile(Emulator, MovieSession, MovieSession.UserBag).Create(path, Config.Savestates); + if (SavestateSaved is not null) { StateSavedEventArgs args = new(userFriendlyStateName); @@ -4190,32 +4177,29 @@ private FileWriteResult SaveStateInternal(string path, string userFriendlyStateN } RA?.OnSaveState(path); - if (Tools.Has()) - { - Tools.LuaConsole.CallStateSaveCallbacks(userFriendlyStateName); - } - if (!suppressOSD) { AddOnScreenMessage($"Saved state: {userFriendlyStateName}"); } } + catch (IOException) + { + AddOnScreenMessage($"Unable to save state {path}"); + } - return result; - } - - public FileWriteResult SaveState(string path, string userFriendlyStateName, bool suppressOSD = false) - { - return SaveStateInternal(path, userFriendlyStateName, suppressOSD, false); + if (!fromLua) + { + UpdateStatusSlots(); + } } - public FileWriteResult SaveQuickSave(int slot, bool suppressOSD = false) + // TODO: should backup logic be stuffed in into Client.Common.SaveStateManager? + public void SaveQuickSave(int slot, bool suppressOSD = false, bool fromLua = false) { if (!Emulator.HasSavestates()) { - return new(FileWriteEnum.Aborted, new("", ""), new UnlessUsingApiException("The current emulator does not support savestates.")); + return; } - var quickSlotName = $"QuickSave{slot % 10}"; var handled = false; if (QuicksaveSave is not null) @@ -4226,29 +4210,27 @@ public FileWriteResult SaveQuickSave(int slot, bool suppressOSD = false) } if (handled) { - // I suppose this is a success? But we have no path. - return new(); + return; } if (ToolControllingSavestates is { } tool) { tool.SaveQuickSave(slot); - // assume success by the tool: state was created, but not as a file. So no path. - return new(); + return; } var path = $"{SaveStatePrefix()}.{quickSlotName}.State"; - var ret = SaveStateInternal(path, quickSlotName, suppressOSD, true); - UpdateStatusSlots(); - return ret; - } + new FileInfo(path).Directory?.Create(); - /// - /// Runs and displays a pop up message if there was an error. - /// - private void SaveQuickSaveAndShowError(int slot) - { - ShowMessageIfError(() => SaveQuickSave(slot), "Quick save failed."); + // Make backup first + if (Config.Savestates.MakeBackups) + { + Util.TryMoveBackupFile(path, $"{path}.bak"); + } + + SaveState(path, quickSlotName, fromLua, suppressOSD); + + if (Tools.Has()) Tools.LuaConsole.CallStateSaveCallbacks(quickSlotName); } public bool EnsureCoreIsAccurate() @@ -4311,17 +4293,12 @@ private void SaveStateAs() var path = Config.PathEntries.SaveStateAbsolutePath(Game.System); new FileInfo(path).Directory?.Create(); - var shouldSaveResult = this.ShowFileSaveDialog( + var result = this.ShowFileSaveDialog( fileExt: "State", filter: EmuHawkSaveStatesFSFilterSet, initDir: path, initFileName: $"{SaveStatePrefix()}.QuickSave0.State"); - if (shouldSaveResult is not null) - { - ShowMessageIfError( - () => SaveState(path: shouldSaveResult, userFriendlyStateName: shouldSaveResult), - "Unable to save state."); - } + if (result is not null) SaveState(path: result, userFriendlyStateName: result); if (Tools.IsLoaded()) { @@ -4634,15 +4611,6 @@ public bool ShowMessageBox2( _ => null, }; - public void ShowMessageIfError(Func action, string message) - { - FileWriteResult result = action(); - if (result.IsError) - { - this.ErrorMessageBox(result, message); - } - } - public void StartSound() => Sound.StartSound(); public void StopSound() => Sound.StopSound(); diff --git a/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index 483b3db3b47..5adcee66746 100644 --- a/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -470,11 +470,7 @@ private void ButtonSaveDefaults_Click(object sender, EventArgs e) SaveToDefaults(cd); - FileWriteResult saveResult = ConfigService.Save(Config.ControlDefaultPath, cd); - if (saveResult.IsError) - { - this.ErrorMessageBox(saveResult); - } + ConfigService.Save(Config.ControlDefaultPath, cd); } } diff --git a/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs b/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs index c19de4fa71e..0ddb2e07fa3 100644 --- a/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs +++ b/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs @@ -55,8 +55,7 @@ private void Save() _movie.Comments.Add(c.Value.ToString()); } - FileWriteResult result = _movie.Save(); - if (result.IsError) throw result.Exception!; + _movie.Save(); } private void Cancel_Click(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/tools/CDL.cs b/src/BizHawk.Client.EmuHawk/tools/CDL.cs index 3061b6223f3..d0cc8754472 100644 --- a/src/BizHawk.Client.EmuHawk/tools/CDL.cs +++ b/src/BizHawk.Client.EmuHawk/tools/CDL.cs @@ -214,23 +214,19 @@ public override bool AskSaveChanges() { if (_currentFilename != null) { - bool saveResult2 = RunSave(); + RunSave(); ShutdownCDL(); - return saveResult2; + return true; } } - var result = DialogController.ShowMessageBox3("Save changes to CDL session?", "CDL Save", EMsgBoxIcon.Question); - if (result == false) + // TODO - I don't like this system. It's hard to figure out how to use it. It should be done in multiple passes. + var result = DialogController.ShowMessageBox2("Save changes to CDL session?", "CDL Auto Save", EMsgBoxIcon.Question); + if (!result) { ShutdownCDL(); return true; } - else if (result == null) - { - ShutdownCDL(); - return false; - } if (string.IsNullOrWhiteSpace(_currentFilename)) { @@ -244,9 +240,9 @@ public override bool AskSaveChanges() return false; } - bool saveResult = RunSave(); + RunSave(); ShutdownCDL(); - return saveResult; + return true; } private bool _autoloading; @@ -345,20 +341,11 @@ private void OpenMenuItem_Click(object sender, EventArgs e) LoadFile(file.FullName); } - /// - /// returns false if the operation was canceled - /// - private bool RunSave() + private void RunSave() { - TryAgainResult result = this.DoWithTryAgainBox( - () => FileWriter.Write(_currentFilename, _cdl.Save), - "Failed to save CDL session."); - if (result == TryAgainResult.Saved) - { - _recent.Add(_currentFilename); - return true; - } - return result != TryAgainResult.Canceled; + _recent.Add(_currentFilename); + using var fs = new FileStream(_currentFilename, FileMode.Create, FileAccess.Write); + _cdl.Save(fs); } private void SaveMenuItem_Click(object sender, EventArgs e) @@ -399,7 +386,8 @@ private bool RunSaveAs() return false; SetCurrentFilename(file.FullName); - return RunSave(); + RunSave(); + return true; } private void SaveAsMenuItem_Click(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs b/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs index 558165dc793..775f009f789 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Cheats/Cheats.cs @@ -142,7 +142,7 @@ private void LoadFile(FileSystemInfo file, bool append) } } - private FileWriteResult SaveAs() + private bool SaveAs() { var fileName = MainForm.CheatList.CurrentFileName; if (string.IsNullOrWhiteSpace(fileName)) @@ -156,8 +156,7 @@ private FileWriteResult SaveAs() CheatsFSFilterSet, this); - if (file == null) return new(); - else return MainForm.CheatList.SaveFile(file.FullName); + return file != null && MainForm.CheatList.SaveFile(file.FullName); } private void Cheats_Load(object sender, EventArgs e) @@ -362,12 +361,7 @@ private void SaveMenuItem_Click(object sender, EventArgs e) { if (MainForm.CheatList.Changes) { - FileWriteResult result = MainForm.CheatList.Save(); - if (result.IsError) - { - this.ErrorMessageBox(result); - } - else + if (MainForm.CheatList.Save()) { UpdateMessageLabel(saved: true); } @@ -380,12 +374,7 @@ private void SaveMenuItem_Click(object sender, EventArgs e) private void SaveAsMenuItem_Click(object sender, EventArgs e) { - FileWriteResult result = SaveAs(); - if (result.IsError) - { - this.ErrorMessageBox(result); - } - else + if (SaveAs()) { UpdateMessageLabel(saved: true); } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index 4d2d35e0461..b36d53c530c 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -225,10 +225,10 @@ public override void Restart() return; } - runningScripts = LuaImp.ScriptList.Where(lf => lf.Enabled).ToList(); + runningScripts = _openedFiles.Where(lf => lf.Enabled).ToList(); // we don't use runningScripts here as the other scripts need to be stopped too - foreach (var file in LuaImp.ScriptList) + foreach (var file in _openedFiles) { file.Stop(); } @@ -263,7 +263,7 @@ public override void Restart() _nonFile = new LuaFile(Config.PathEntries.LuaAbsolutePath(), UpdateRegisteredFunctionsDialog); _nonFile.Start(LuaImp.SpawnBlankCoroutineAndSandbox(null)); - LuaImp.ScriptList.Insert(0, _nonFile); + _openedFiles.Insert(0, _nonFile); UpdateDialog(); } @@ -360,7 +360,7 @@ public void LoadLuaFile(string path) { var luaFile = new LuaFile(absolutePath, UpdateRegisteredFunctionsDialog); - LuaImp.ScriptList.Add(luaFile); + _openedFiles.Add(luaFile); _openedFiles.Add(luaFile); LuaListView.RowCount = _openedFiles.Count; Config.RecentLua.Add(absolutePath); @@ -403,7 +403,7 @@ private void RemoveLuaFile(LuaFile item) item.Stop(); RemoveFileWatcher(item); } - LuaImp.ScriptList.Remove(item); + _openedFiles.Remove(item); _openedFiles.Remove(item); } @@ -549,9 +549,9 @@ public void ClearOutputWindow() private void SyncScriptList() { - LuaImp.ScriptList.Clear(); - LuaImp.ScriptList.Add(_nonFile); - LuaImp.ScriptList.AddRange(_openedFiles.Where(static lf => !lf.IsSeparator)); + _openedFiles.Clear(); + _openedFiles.Add(_nonFile); + _openedFiles.AddRange(_openedFiles.Where(static lf => !lf.IsSeparator)); } public bool LoadLuaSession(string path) @@ -685,25 +685,15 @@ private FileInfo GetSaveFileFromUser() return result is not null ? new FileInfo(result) : null; } - private FileWriteResult SaveSessionAs() + private void SaveSessionAs() { var file = GetSaveFileFromUser(); if (file != null) { - FileWriteResult saveResult = _openedFiles.Save(file.FullName); - if (saveResult.IsError) - { - this.ErrorMessageBox(saveResult); - OutputMessages.Text = $"Lua session could not be saved to {file.Name}"; - } - else - { - Config.RecentLuaSession.Add(file.FullName); - OutputMessages.Text = $"{file.Name} saved."; - } - return saveResult; + _openedFiles.Save(file.FullName); + Config.RecentLuaSession.Add(file.FullName); + OutputMessages.Text = $"{file.Name} saved."; } - return new(); } private void LoadSessionFromRecent(string path) @@ -731,11 +721,7 @@ public override bool AskSaveChanges() icon: EMsgBoxIcon.Question, text: $"Save {WindowTitleStatic} session?")); if (result is null) return false; - if (result.Value) - { - TryAgainResult saveResult = this.DoWithTryAgainBox(SaveOrSaveAs, "Failed to save Lua session."); - return saveResult != TryAgainResult.Canceled; - } + if (result.Value) SaveOrSaveAs(); else _openedFiles.Changes = false; return true; } @@ -746,24 +732,20 @@ private void UpdateRegisteredFunctionsDialog() foreach (var form in Application.OpenForms.OfType().ToList()) { - form.UpdateValues(LuaImp.ScriptList); + form.UpdateValues(_openedFiles); } } - private FileWriteResult SaveOrSaveAs() + private void SaveOrSaveAs() { if (!string.IsNullOrWhiteSpace(_openedFiles.Filename)) { - FileWriteResult result = _openedFiles.Save(_openedFiles.Filename); - if (!result.IsError) - { - Config.RecentLuaSession.Add(_openedFiles.Filename); - } - return result; + _openedFiles.Save(_openedFiles.Filename); + Config.RecentLuaSession.Add(_openedFiles.Filename); } else { - return SaveSessionAs(); + SaveSessionAs(); } } @@ -806,16 +788,8 @@ private void SaveSessionMenuItem_Click(object sender, EventArgs e) { if (_openedFiles.Changes) { - FileWriteResult result = SaveOrSaveAs(); - if (result.IsError) - { - this.ErrorMessageBox(result, "Failed to save Lua session."); - OutputMessages.Text = $"Failed to save {Path.GetFileName(_openedFiles.Filename)}"; - } - else - { - OutputMessages.Text = $"{Path.GetFileName(_openedFiles.Filename)} saved."; - } + SaveOrSaveAs(); + OutputMessages.Text = $"{Path.GetFileName(_openedFiles.Filename)} saved."; } } @@ -1112,7 +1086,7 @@ private void RegisteredFunctionsMenuItem_Click(object sender, EventArgs e) if (!alreadyOpen) { - new LuaRegisteredFunctionsList(LuaImp.ScriptList) + new LuaRegisteredFunctionsList(_openedFiles) { StartLocation = this.ChildPointToScreen(LuaListView), }.Show(); @@ -1225,15 +1199,15 @@ private void ScriptListContextMenu_Opening(object sender, CancelEventArgs e) StopAllScriptsContextItem.Visible = ScriptContextSeparator.Visible = - LuaImp.ScriptList.Exists(file => file.Enabled); + _openedFiles.Exists(file => file.Enabled); - ClearRegisteredFunctionsContextItem.Enabled = LuaImp.ScriptList.Exists(lf => lf.Functions.Count != 0); + ClearRegisteredFunctionsContextItem.Enabled = _openedFiles.Exists(lf => lf.Functions.Count != 0); } private void ConsoleContextMenu_Opening(object sender, CancelEventArgs e) { RegisteredFunctionsContextItem.Enabled = ClearRegisteredFunctionsLogContextItem.Enabled - = LuaImp.ScriptList.Exists(lf => lf.Functions.Count != 0); + = _openedFiles.Exists(lf => lf.Functions.Count != 0); CopyContextItem.Enabled = OutputBox.SelectedText.Length is not 0; ClearConsoleContextItem.Enabled = SelectAllContextItem.Enabled = OutputBox.Text.Length is not 0; } @@ -1267,7 +1241,7 @@ private void CopyContextItem_Click(object sender, EventArgs e) private void ClearRegisteredFunctionsContextMenuItem_Click(object sender, EventArgs e) { - foreach (LuaFile lf in LuaImp.ScriptList) + foreach (LuaFile lf in _openedFiles) lf.Functions.Clear(); } diff --git a/src/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs b/src/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs index e17f74132e5..9b71a4a2843 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Macros/MacroInput.cs @@ -112,7 +112,6 @@ public override bool AskSaveChanges() return true; } - // Intentionally not updating this to use FileWriter because this tool is going to be removed later. foreach (var zone in _unsavedZones) { SaveMacroAs(_zones[zone]); @@ -274,7 +273,7 @@ private bool SaveMacroAs(MovieZone macro) return false; } - macro.Save(result); // ignore errors: This tool is going to be removed. + macro.Save(result); Config!.RecentMacros.Add(result); return true; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs index 6455b24610f..fc5192a0f72 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs @@ -59,10 +59,13 @@ public void ToggleReadOnly() public void StopMovie(bool suppressSave) { - Activate(); - _suppressAskSave = suppressSave; - StartNewTasMovie(); - _suppressAskSave = false; + if (!MainForm.GameIsClosing) + { + Activate(); + _suppressAskSave = suppressSave; + StartNewTasMovie(); + _suppressAskSave = false; + } } public bool WantsToControlRewind { get; private set; } = true; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 8ea5d6cb7d6..828ad450284 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -157,16 +157,12 @@ public override bool AskSaveChanges() } if (CurrentTasMovie?.Changes is not true) return true; - var shouldSaveResult = DialogController.DoWithTempMute(() => this.ModalMessageBox3( + var result = DialogController.DoWithTempMute(() => this.ModalMessageBox3( caption: "Closing with Unsaved Changes", icon: EMsgBoxIcon.Question, text: $"Save {WindowTitleStatic} project?")); - if (shouldSaveResult == true) - { - TryAgainResult saveResult = this.DoWithTryAgainBox(() => SaveTas(), "Failed to save movie."); - return saveResult != TryAgainResult.Canceled; - } - if (shouldSaveResult is null) return false; + if (result is null) return false; + if (result.Value) SaveTas(); else CurrentTasMovie.ClearChanges(); return true; } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 3480707f117..354616ce0b6 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -44,15 +44,11 @@ private void StartNewProjectFromNowMenuItem_Click(object sender, EventArgs e) { if (AskSaveChanges()) { - var result = CurrentTasMovie.ConvertToSavestateAnchoredMovie( + var newProject = CurrentTasMovie.ConvertToSavestateAnchoredMovie( Emulator.Frame, StatableEmulator.CloneSavestate()); - DisplayMessageIfFailed(() => result, "Failed to create movie."); - if (result.Value is ITasMovie newProject) - { - MainForm.PauseEmulator(); - LoadMovie(newProject, true); - } + MainForm.PauseEmulator(); + LoadMovie(newProject, true); } } @@ -62,14 +58,9 @@ private void StartANewProjectFromSaveRamMenuItem_Click(object sender, EventArgs { var saveRam = SaveRamEmulator?.CloneSaveRam(clearDirty: false) ?? throw new Exception("No SaveRam"); GoToFrame(AnyRowsSelected ? FirstSelectedRowIndex : 0); - var result = CurrentTasMovie.ConvertToSaveRamAnchoredMovie(saveRam); - DisplayMessageIfFailed(() => result, "Failed to create movie."); - - if (result.Value is ITasMovie newProject) - { - MainForm.PauseEmulator(); - LoadMovie(newProject, true); - } + var newProject = CurrentTasMovie.ConvertToSaveRamAnchoredMovie(saveRam); + MainForm.PauseEmulator(); + LoadMovie(newProject, true); } } @@ -125,30 +116,30 @@ public bool LoadMovieFile(string filename, bool askToSave = true) private void SaveTasMenuItem_Click(object sender, EventArgs e) { - DisplayMessageIfFailed(() => SaveTas(), "Failed to save movie."); + SaveTas(); if (Settings.BackupPerFileSave) { - DisplayMessageIfFailed(() => SaveTas(saveBackup: true), "Failed to save backup."); + SaveTas(saveBackup: true); } } private void SaveAsTasMenuItem_Click(object sender, EventArgs e) { - DisplayMessageIfFailed(() => SaveAsTas(), "Failed to save movie."); + SaveAsTas(); if (Settings.BackupPerFileSave) { - DisplayMessageIfFailed(() => SaveTas(saveBackup: true), "Failed to save backup."); + SaveTas(saveBackup: true); } } private void SaveBackupMenuItem_Click(object sender, EventArgs e) { - DisplayMessageIfFailed(() => SaveTas(saveBackup: true), "Failed to save backup."); + SaveTas(saveBackup: true); } private void SaveBk2BackupMenuItem_Click(object sender, EventArgs e) { - DisplayMessageIfFailed(() => SaveTas(saveAsBk2: true, saveBackup: true), "Failed to save backup."); + SaveTas(saveAsBk2: true, saveBackup: true); } private void SaveSelectionToMacroMenuItem_Click(object sender, EventArgs e) @@ -174,23 +165,13 @@ private void SaveSelectionToMacroMenuItem_Click(object sender, EventArgs e) if (file != null) { var selectionStart = FirstSelectedRowIndex; - MovieZone macro = new( + new MovieZone( CurrentTasMovie, start: selectionStart, - length: endIndex - selectionStart + 1); - FileWriteResult saveResult = macro.Save(file.FullName); + length: endIndex - selectionStart + 1) + .Save(file.FullName); - if (saveResult.IsError) - { - DialogController.ShowMessageBox( - $"Failed to save macro.\n{saveResult.UserFriendlyErrorMessage()}\n{saveResult.Exception.Message}", - "Error", - EMsgBoxIcon.Error); - } - else - { - Config.RecentMacros.Add(file.FullName); - } + Config.RecentMacros.Add(file.FullName); } } @@ -239,24 +220,13 @@ private void ToBk2MenuItem_Click(object sender, EventArgs e) { MessageStatusLabel.Text = "Exporting to .bk2..."; MessageStatusLabel.Owner.Update(); - Cursor = Cursors.WaitCursor; var bk2 = CurrentTasMovie.ToBk2(); bk2.Filename = fileInfo.FullName; bk2.Attach(Emulator); // required to be able to save the cycle count for ICycleTiming emulators - FileWriteResult saveResult = bk2.Save(); + bk2.Save(); + MessageStatusLabel.Text = $"{bk2.Name} exported."; Cursor = Cursors.Default; - - while (saveResult.IsError) - { - DialogResult d = MessageBox.Show( - $"Failed to save .bk2. {saveResult.UserFriendlyErrorMessage()}\nTry again?", - "Error", - MessageBoxButtons.YesNo); - if (d == DialogResult.Yes) saveResult = bk2.Save(); - else break; - } - if (!saveResult.IsError) MessageStatusLabel.Text = $"{bk2.Name} exported."; } else { diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 85cdda2e7c2..a066dffc8f8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -35,7 +35,6 @@ public static Icon ToolIcon private const string FrameColumnName = "FrameColumn"; private UndoHistoryForm _undoForm; private Timer _autosaveTimer; - private bool _lastAutoSaveSuccess = true; private readonly int _defaultMainSplitDistance; private readonly int _defaultBranchMarkerSplitDistance; @@ -237,7 +236,11 @@ private void Tastudio_Load(object sender, EventArgs e) _autosaveTimer = new Timer(components); _autosaveTimer.Tick += AutosaveTimerEventProcessor; - ScheduleAutoSave(Settings.AutosaveInterval); + if (Settings.AutosaveInterval > 0) + { + _autosaveTimer.Interval = (int)Settings.AutosaveInterval; + _autosaveTimer.Start(); + } BranchesMarkersSplit.SetDistanceOrDefault( Settings.BranchMarkerSplitDistance, @@ -303,27 +306,20 @@ private bool Engage() { changesString = "The current movie has unsaved changes. Would you like to save before closing it?"; } - var shouldSaveResult = DialogController.ShowMessageBox3( + var result = DialogController.ShowMessageBox3( "TAStudio will create a new project file from the current movie.\n\n" + changesString, "Convert movie", EMsgBoxIcon.Question); - if (shouldSaveResult == true) + if (result == true) { - FileWriteResult saveResult = MovieSession.Movie.Save(); - if (saveResult.IsError) - { - DisplayMessageIfFailed(() => saveResult, "Failed to save movie."); - return false; - } + MovieSession.Movie.Save(); } - else if (shouldSaveResult == null) + else if (result == null) { return false; } - var tasMovie = MovieSession.Movie.ToTasMovie(); - // No need to save new movie, as there are no changes. - // User will save future changes if they want (potentially via auto-save). + var tasMovie = ConvertCurrentMovieToTasproj(); success = LoadMovie(tasMovie); } @@ -364,16 +360,6 @@ private bool Engage() return true; } - private void ScheduleAutoSave(uint secondsUntil) - { - _autosaveTimer.Stop(); - if (secondsUntil != 0) - { - _autosaveTimer.Interval = (int)secondsUntil; - _autosaveTimer.Start(); - } - } - private void AutosaveTimerEventProcessor(object sender, EventArgs e) { if (CurrentTasMovie == null) @@ -387,55 +373,28 @@ private void AutosaveTimerEventProcessor(object sender, EventArgs e) return; } - FileWriteResult saveResult; if (Settings.AutosaveAsBackupFile) { if (Settings.AutosaveAsBk2) { - saveResult = SaveTas(saveAsBk2: true, saveBackup: true); + SaveTas(saveAsBk2: true, saveBackup: true); } else { - saveResult = SaveTas(saveBackup: true); + SaveTas(saveBackup: true); } } else { if (Settings.AutosaveAsBk2) { - saveResult = SaveTas(saveAsBk2: true); + SaveTas(saveAsBk2: true); } else { - saveResult = SaveTas(); + SaveTas(); } } - - if (saveResult.IsError && _lastAutoSaveSuccess) - { - // Should we alert the user? - // Let's try again once after a bit, then alert if it fails again. - ScheduleAutoSave(60); - } - else if (saveResult.IsError) - { - _autosaveTimer.Stop(); - bool tryAgain = DialogController.ShowMessageBox2( - $"Failed to auto-save. {saveResult.UserFriendlyErrorMessage()}\n{saveResult.Exception.Message}\n\nTry again?", - "Error"); - if (tryAgain) - { - AutosaveTimerEventProcessor(null, EventArgs.Empty); - return; - } - ScheduleAutoSave(Settings.AutosaveInterval); - } - else - { - ScheduleAutoSave(Settings.AutosaveInterval); - } - - _lastAutoSaveSuccess = !saveResult.IsError; } private static readonly string[] N64CButtonSuffixes = { " C Up", " C Down", " C Left", " C Right" }; @@ -642,6 +601,13 @@ public IMovieController GetBranchInput(string branchId, int frame) return controller; } + private ITasMovie ConvertCurrentMovieToTasproj() + { + var tasMovie = MovieSession.Movie.ToTasMovie(); + tasMovie.Save(); // should this be done? + return tasMovie; + } + private bool LoadMovie(ITasMovie tasMovie, bool startsFromSavestate = false, int gotoFrame = 0) { _engaged = false; @@ -892,23 +858,12 @@ private string SuggestedTasProjName() $"{Game.FilesystemSafeName()}.{MovieService.TasMovieExtension}"); } - private void DisplayMessageIfFailed(Func action, string message) - { - FileWriteResult result = action(); - if (result.IsError) - { - DialogController.ShowMessageBox( - $"{message}\n{result.UserFriendlyErrorMessage()}\n{result.Exception.Message}", - "Error", - EMsgBoxIcon.Error); - } - } - - private FileWriteResult SaveTas(bool saveAsBk2 = false, bool saveBackup = false) + private void SaveTas(bool saveAsBk2 = false, bool saveBackup = false) { if (string.IsNullOrEmpty(CurrentTasMovie.Filename) || CurrentTasMovie.Filename == DefaultTasProjName()) { - return SaveAsTas(); + SaveAsTas(); + return; } _autosaveTimer.Stop(); @@ -925,29 +880,22 @@ private FileWriteResult SaveTas(bool saveAsBk2 = false, bool saveBackup = false) movieToSave.Attach(Emulator); } - FileWriteResult result; if (saveBackup) - result = movieToSave.SaveBackup(); + movieToSave.SaveBackup(); else - result = movieToSave.Save(); + movieToSave.Save(); - if (!result.IsError) - { - MessageStatusLabel.Text = saveBackup - ? $"Backup .{(saveAsBk2 ? MovieService.StandardMovieExtension : MovieService.TasMovieExtension)} saved to \"Movie backups\" path." - : "File saved."; - } - else + MessageStatusLabel.Text = saveBackup + ? $"Backup .{(saveAsBk2 ? MovieService.StandardMovieExtension : MovieService.TasMovieExtension)} saved to \"Movie backups\" path." + : "File saved."; + Cursor = Cursors.Default; + if (Settings.AutosaveInterval > 0) { - MessageStatusLabel.Text = "Failed to save movie."; + _autosaveTimer.Start(); } - - Cursor = Cursors.Default; - ScheduleAutoSave(Settings.AutosaveInterval); - return result; } - private FileWriteResult SaveAsTas() + private void SaveAsTas() { _autosaveTimer.Stop(); @@ -963,33 +911,25 @@ private FileWriteResult SaveAsTas() TAStudioProjectsFSFilterSet, this); - FileWriteResult saveResult = null; if (fileInfo != null) { MessageStatusLabel.Text = "Saving..."; MessageStatusLabel.Owner.Update(); Cursor = Cursors.WaitCursor; CurrentTasMovie.Filename = fileInfo.FullName; - saveResult = CurrentTasMovie.Save(); + CurrentTasMovie.Save(); Settings.RecentTas.Add(CurrentTasMovie.Filename); + MessageStatusLabel.Text = "File saved."; Cursor = Cursors.Default; + } - if (!saveResult.IsError) - { - MessageStatusLabel.Text = "File saved."; - ScheduleAutoSave(Settings.AutosaveInterval); - } - else - { - MessageStatusLabel.Text = "Failed to save."; - } + if (Settings.AutosaveInterval > 0) + { + _autosaveTimer.Start(); } UpdateWindowTitle(); // changing the movie's filename does not flag changes, so we need to ensure the window title is always updated MainForm.UpdateWindowTitle(); - - if (fileInfo != null) return saveResult; - else return new(); // user cancelled, so we were successful in not saving } protected override string WindowTitle diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs index d1cef842326..163b6153609 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs @@ -1008,13 +1008,7 @@ private void SaveMenuItem_Click(object sender, EventArgs e) if (!string.IsNullOrWhiteSpace(watches.CurrentFileName)) { - FileWriteResult saveResult = watches.Save(); - if (saveResult.IsError) - { - this.ErrorMessageBox(saveResult); - MessageLabel.Text = $"Failed to save {Path.GetFileName(_currentFileName)}"; - } - else + if (watches.Save()) { _currentFileName = watches.CurrentFileName; MessageLabel.Text = $"{Path.GetFileName(_currentFileName)} saved"; @@ -1023,20 +1017,11 @@ private void SaveMenuItem_Click(object sender, EventArgs e) } else { - FileInfo/*?*/ file = GetWatchSaveFileFromUser(CurrentFileName()); - if (file != null) + var result = watches.SaveAs(GetWatchSaveFileFromUser(CurrentFileName())); + if (result) { - var result = watches.SaveAs(file); - if (result.IsError) - { - this.ErrorMessageBox(result); - MessageLabel.Text = $"Failed to save {Path.GetFileName(_currentFileName)}"; - } - else - { - MessageLabel.Text = $"{Path.GetFileName(_currentFileName)} saved"; - Settings.RecentSearches.Add(watches.CurrentFileName); - } + MessageLabel.Text = $"{Path.GetFileName(_currentFileName)} saved"; + Settings.RecentSearches.Add(watches.CurrentFileName); } } } @@ -1050,15 +1035,7 @@ private void SaveAsMenuItem_Click(object sender, EventArgs e) watches.Add(_searches[i]); } - FileInfo/*?*/ file = GetWatchSaveFileFromUser(CurrentFileName()); - if (file == null) return; - FileWriteResult result = watches.SaveAs(file); - if (result.IsError) - { - this.ErrorMessageBox(result); - MessageLabel.Text = $"Failed to save {Path.GetFileName(_currentFileName)}"; - } - else + if (watches.SaveAs(GetWatchSaveFileFromUser(CurrentFileName()))) { _currentFileName = watches.CurrentFileName; MessageLabel.Text = $"{Path.GetFileName(_currentFileName)} saved"; diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs index 86288fda74a..f0f7a9b3d8d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs @@ -213,8 +213,15 @@ public override bool AskSaveChanges() if (result is null) return false; if (result.Value) { - TryAgainResult saveResult = this.DoWithTryAgainBox(Save, "Failed to save watch list."); - return saveResult != TryAgainResult.Canceled; + if (string.IsNullOrWhiteSpace(_watches.CurrentFileName)) + { + SaveAs(); + } + else + { + _watches.Save(); + Config.RecentWatches.Add(_watches.CurrentFileName); + } } else { @@ -580,45 +587,13 @@ private string CurrentFileName() : Game.FilesystemSafeName(); } - private FileWriteResult SaveAs() + private void SaveAs() { - FileInfo/*?*/ file = GetWatchSaveFileFromUser(CurrentFileName()); - if (file == null) return new(); - - FileWriteResult result = _watches.SaveAs(file); - if (result.IsError) - { - MessageLabel.Text = $"Failed to save {Path.GetFileName(_watches.CurrentFileName)}"; - } - else + var result = _watches.SaveAs(GetWatchSaveFileFromUser(CurrentFileName())); + if (result) { - MessageLabel.Text = $"{Path.GetFileName(_watches.CurrentFileName)} saved"; - Config.RecentWatches.Add(_watches.CurrentFileName); UpdateStatusBar(saved: true); - } - return result; - } - - private FileWriteResult Save() - { - if (string.IsNullOrWhiteSpace(_watches.CurrentFileName)) - { - return SaveAs(); - } - else - { - FileWriteResult saveResult = _watches.Save(); - if (saveResult.IsError) - { - MessageLabel.Text = $"Failed to save {Path.GetFileName(_watches.CurrentFileName)}"; - } - else - { - MessageLabel.Text = $"{Path.GetFileName(_watches.CurrentFileName)} saved"; - Config.RecentWatches.Add(_watches.CurrentFileName); - UpdateStatusBar(saved: true); - } - return saveResult; + Config.RecentWatches.Add(_watches.CurrentFileName); } } @@ -748,20 +723,23 @@ private void OpenMenuItem_Click(object sender, EventArgs e) private void SaveMenuItem_Click(object sender, EventArgs e) { - FileWriteResult saveResult = Save(); - if (saveResult.IsError) + if (!string.IsNullOrWhiteSpace(_watches.CurrentFileName)) { - this.ErrorMessageBox(saveResult, "Failed to save watch list."); + if (_watches.Save()) + { + Config.RecentWatches.Add(_watches.CurrentFileName); + UpdateStatusBar(saved: true); + } + } + else + { + SaveAs(); } } private void SaveAsMenuItem_Click(object sender, EventArgs e) { - FileWriteResult saveResult = SaveAs(); - if (saveResult.IsError) - { - this.ErrorMessageBox(saveResult, "Failed to save watch list."); - } + SaveAs(); } private void RecentSubMenu_DropDownOpened(object sender, EventArgs e) diff --git a/src/BizHawk.Tests.Client.Common/Movie/FakeMovieSession.cs b/src/BizHawk.Tests.Client.Common/Movie/FakeMovieSession.cs index c7497538b4c..93c383efe95 100644 --- a/src/BizHawk.Tests.Client.Common/Movie/FakeMovieSession.cs +++ b/src/BizHawk.Tests.Client.Common/Movie/FakeMovieSession.cs @@ -70,10 +70,6 @@ public FakeMovieSession(IEmulator emulator) public void PopupMessage(string message) => throw new NotImplementedException(); public void QueueNewMovie(IMovie movie, string systemId, string loadedRomHash, PathEntryCollection pathEntries, IDictionary preferredCores) => throw new NotImplementedException(); public void RunQueuedMovie(bool recordMode, IEmulator emulator) => throw new NotImplementedException(); - public FileWriteResult StopMovie(bool saveChanges = true) - { - Movie?.Stop(); - return new(); - } + public void StopMovie(bool saveChanges = true) => Movie?.Stop(); } }