diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 55bf82496..63494931c 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -436,6 +436,10 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable { deserialized = JsonConvert.DeserializeObject(text, SerializerSettings); + + // If this reads as null, the file was empty, that's no good + if (deserialized == null) + throw new Exception("Read config was null."); }); } catch (FileNotFoundException) diff --git a/Dalamud/Configuration/PluginConfigurations.cs b/Dalamud/Configuration/PluginConfigurations.cs index 957a7c99e..d1f926b0d 100644 --- a/Dalamud/Configuration/PluginConfigurations.cs +++ b/Dalamud/Configuration/PluginConfigurations.cs @@ -1,6 +1,6 @@ using System.IO; -using Dalamud.Utility; +using Dalamud.Storage; using Newtonsoft.Json; namespace Dalamud.Configuration; @@ -33,22 +33,36 @@ public sealed class PluginConfigurations /// Plugin name. public void Save(IPluginConfiguration config, string pluginName) { - Util.WriteAllTextSafe(this.GetConfigFile(pluginName).FullName, SerializeConfig(config)); + Service.Get() + .WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config)); } /// /// Load plugin configuration. /// /// Plugin name. + /// WorkingPluginID of the plugin. /// Plugin configuration. - public IPluginConfiguration? Load(string pluginName) + public IPluginConfiguration? Load(string pluginName, Guid workingPluginId) { var path = this.GetConfigFile(pluginName); - if (!path.Exists) - return null; + IPluginConfiguration? config = null; + try + { + Service.Get().ReadAllText(path.FullName, text => + { + config = DeserializeConfig(text); + if (config == null) + throw new Exception("Read config was null."); + }, workingPluginId); + } + catch (FileNotFoundException) + { + // ignored + } - return DeserializeConfig(File.ReadAllText(path.FullName)); + return config; } /// diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index 6fdf875e5..0f5b4297c 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -370,7 +370,7 @@ public sealed class DalamudPluginInterface : IDisposable } // this shouldn't be a thing, I think, but just in case - return this.configs.Load(this.plugin.InternalName); + return this.configs.Load(this.plugin.InternalName, this.plugin.Manifest.WorkingPluginId); } /// diff --git a/Dalamud/Storage/ReliableFileStorage.cs b/Dalamud/Storage/ReliableFileStorage.cs index 14ab59143..32fba9aef 100644 --- a/Dalamud/Storage/ReliableFileStorage.cs +++ b/Dalamud/Storage/ReliableFileStorage.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Text; using Dalamud.Logging.Internal; +using Dalamud.Utility; using PInvoke; using SQLite; @@ -32,7 +33,6 @@ public class ReliableFileStorage : IServiceType, IDisposable /// Initializes a new instance of the class. /// /// Path to the VFS. - [ServiceManager.ServiceConstructor] public ReliableFileStorage(string vfsDbPath) { var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db"); @@ -113,7 +113,7 @@ public class ReliableFileStorage : IServiceType, IDisposable this.db.Update(file); } - WriteFileReliably(path, bytes); + Util.WriteAllBytesSafe(path, bytes); } /// @@ -252,39 +252,6 @@ public class ReliableFileStorage : IServiceType, IDisposable { this.db.Dispose(); } - - private static void WriteFileReliably(string path, byte[] bytes) - { - ArgumentException.ThrowIfNullOrEmpty(path); - - // Open the temp file - var tempPath = path + ".tmp"; - - using var tempFile = Kernel32 - .CreateFile(tempPath.AsSpan(), - new Kernel32.ACCESS_MASK(Kernel32.FileAccess.FILE_GENERIC_READ | Kernel32.FileAccess.FILE_GENERIC_WRITE), - Kernel32.FileShare.None, - null, - Kernel32.CreationDisposition.CREATE_ALWAYS, - Kernel32.CreateFileFlags.FILE_ATTRIBUTE_NORMAL, - Kernel32.SafeObjectHandle.Null); - - if (tempFile.IsInvalid) - throw new Win32Exception(); - - // Write the data - var bytesWritten = Kernel32.WriteFile(tempFile, new ArraySegment(bytes)); - if (bytesWritten != bytes.Length) - throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); - - if (!Kernel32.FlushFileBuffers(tempFile)) - throw new Win32Exception(); - - tempFile.Close(); - - if (!MoveFileEx(tempPath, path, MoveFileFlags.MovefileReplaceExisting | MoveFileFlags.MovefileWriteThrough)) - throw new Win32Exception(); - } /// /// Replace possible non-portable parts of a path with portable versions. @@ -299,20 +266,6 @@ public class ReliableFileStorage : IServiceType, IDisposable return path; } - - [Flags] -#pragma warning disable SA1201 - private enum MoveFileFlags -#pragma warning restore SA1201 - { - MovefileReplaceExisting = 0x00000001, - MovefileWriteThrough = 0x00000008, - } - - [return: MarshalAs(UnmanagedType.Bool)] - [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] - private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, - MoveFileFlags dwFlags); private class DbFile { diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 8ca87b691..36918abd2 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -20,6 +20,7 @@ using Dalamud.Logging.Internal; using Dalamud.Memory; using ImGuiNET; using Lumina.Excel.GeneratedSheets; +using PInvoke; using Serilog; namespace Dalamud.Utility; @@ -609,7 +610,7 @@ public static class Util } } } - + /// /// Overwrite text in a file by first writing it to a temporary file, and then /// moving that file to the path specified. @@ -618,12 +619,58 @@ public static class Util /// The text to write. public static void WriteAllTextSafe(string path, string text) { - var tmpPath = path + ".tmp"; - if (File.Exists(tmpPath)) - File.Delete(tmpPath); + WriteAllTextSafe(path, text, Encoding.UTF8); + } + + /// + /// Overwrite text in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The text to write. + /// Encoding to use. + public static void WriteAllTextSafe(string path, string text, Encoding encoding) + { + WriteAllBytesSafe(path, encoding.GetBytes(text)); + } + + /// + /// Overwrite data in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The data to write. + public static void WriteAllBytesSafe(string path, byte[] bytes) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + // Open the temp file + var tempPath = path + ".tmp"; - File.WriteAllText(tmpPath, text); - File.Move(tmpPath, path, true); + using var tempFile = Kernel32 + .CreateFile(tempPath.AsSpan(), + new Kernel32.ACCESS_MASK(Kernel32.FileAccess.FILE_GENERIC_READ | Kernel32.FileAccess.FILE_GENERIC_WRITE), + Kernel32.FileShare.None, + null, + Kernel32.CreationDisposition.CREATE_ALWAYS, + Kernel32.CreateFileFlags.FILE_ATTRIBUTE_NORMAL, + Kernel32.SafeObjectHandle.Null); + + if (tempFile.IsInvalid) + throw new Win32Exception(); + + // Write the data + var bytesWritten = Kernel32.WriteFile(tempFile, new ArraySegment(bytes)); + if (bytesWritten != bytes.Length) + throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); + + if (!Kernel32.FlushFileBuffers(tempFile)) + throw new Win32Exception(); + + tempFile.Close(); + + if (!MoveFileEx(tempPath, path, MoveFileFlags.MovefileReplaceExisting | MoveFileFlags.MovefileWriteThrough)) + throw new Win32Exception(); } /// @@ -762,4 +809,18 @@ public static class Util } } } + + [Flags] +#pragma warning disable SA1201 + private enum MoveFileFlags +#pragma warning restore SA1201 + { + MovefileReplaceExisting = 0x00000001, + MovefileWriteThrough = 0x00000008, + } + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, + MoveFileFlags dwFlags); }