diff --git a/Il2CppInspector.GUI/App.xaml.cs b/Il2CppInspector.GUI/App.xaml.cs index c8cdc34..5035e3d 100644 --- a/Il2CppInspector.GUI/App.xaml.cs +++ b/Il2CppInspector.GUI/App.xaml.cs @@ -10,6 +10,8 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using System.Windows; using System.Windows.Markup; @@ -27,6 +29,55 @@ namespace Il2CppInspectorGUI /// public partial class App : Application, INotifyPropertyChanged { + // Converter for IPlugin for System.Text.Json + private class PluginConverter : JsonConverter + { + public override IPlugin Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + return (IPlugin) JsonSerializer.Deserialize(ref reader, typeof(PluginState), options); + } + + public override void Write(Utf8JsonWriter writer, IPlugin value, JsonSerializerOptions options) { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + } + + private class PluginOptionConverter : JsonConverter + { + public override IPluginOption Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + return (IPluginOption) JsonSerializer.Deserialize(ref reader, typeof(PluginOptionState), options); + } + + public override void Write(Utf8JsonWriter writer, IPluginOption value, JsonSerializerOptions options) { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + } + + // Save state for plugins + private class PluginState : IPlugin + { + public string Id { get; set; } + [JsonIgnore] + public string Name { get; set; } + [JsonIgnore] + public string Author { get; set; } + [JsonIgnore] + public string Description { get; set; } + [JsonIgnore] + public string Version { get; set; } + public List Options { get; set; } + }; + + private class PluginOptionState : IPluginOption + { + public string Name { get; set; } + [JsonIgnore] + public string Description { get; set; } + [JsonIgnore] + public bool Required { get; set; } + public object Value { get; set; } + } + + // Application startup public App() : base() { // Catch unhandled exceptions for debugging startup failures and plugins var np = Environment.NewLine + Environment.NewLine; @@ -50,6 +101,12 @@ namespace Il2CppInspectorGUI } }; + // Load options + LoadOptions(); + } + + // Load options from user config + internal void LoadOptions() { // Migrate settings from previous version if necessary if (User.Default.UpgradeRequired) { User.Default.Upgrade(); @@ -57,8 +114,53 @@ namespace Il2CppInspectorGUI User.Default.Save(); } - // Load plugins + // Load plugin state (enabled / execution order) + var savedPluginState = Array.Empty(); + try { + savedPluginState = JsonSerializer.Deserialize(User.Default.PluginsState, + new JsonSerializerOptions { Converters = { new PluginConverter(), new PluginOptionConverter() } }); + } + + // Not set or invalid - just create a new set + catch (JsonException) { } + catch (NotSupportedException) { } + + // Load plugins if they aren't already PluginManager.EnsureInit(); + + // Arrange plugins + var loadedPlugins = PluginManager.AsInstance.ManagedPlugins; + foreach (var savedState in savedPluginState.Reverse()) { + if (loadedPlugins.FirstOrDefault(p => p.Plugin.Id == savedState.Plugin.Id) is ManagedPlugin managedPlugin) { + // Re-order to match saved order + loadedPlugins.Remove(managedPlugin); + loadedPlugins.Insert(0, managedPlugin); + + // Enable/disable to match saved state + managedPlugin.Enabled = savedState.Enabled; + + // Set options + if (savedState.Plugin.Options != null) + foreach (var savedOption in savedState.Plugin.Options) + if (managedPlugin.Plugin.Options.FirstOrDefault(o => o.Name == savedOption.Name) is IPluginOption option) { + // Ignore invalid values + try { + option.Value = savedOption.Value is JsonElement ? savedOption.Value.ToString() : savedOption.Value; + } catch { } + } + } + } + + // Save options in case no save exists or previous save is invalid + SaveOptions(); + } + + // Save options to user config + internal void SaveOptions() { + User.Default.PluginsState = JsonSerializer.Serialize( + PluginManager.Plugins.Values.Cast().Where(p => p.Available).ToArray(), + new JsonSerializerOptions { Converters = { new PluginConverter(), new PluginOptionConverter() } }); + User.Default.Save(); } private Metadata metadata; @@ -74,7 +176,7 @@ namespace Il2CppInspectorGUI } } - public LoadOptions LoadOptions { get; private set; } + public LoadOptions ImageLoadOptions { get; private set; } public List AppModels { get; } = new List(); @@ -114,7 +216,7 @@ namespace Il2CppInspectorGUI } public void ResetLoadOptions() { - LoadOptions = new LoadOptions { + ImageLoadOptions = new LoadOptions { ImageBase = 0xffffffff_ffffffff, BinaryFilePath = null }; @@ -166,7 +268,7 @@ namespace Il2CppInspectorGUI // Attempt to load an IL2CPP binary file public async Task LoadBinaryAsync(string binaryFile) { // For loaders which require the file path to find additional files - LoadOptions.BinaryFilePath = binaryFile; + ImageLoadOptions.BinaryFilePath = binaryFile; var stream = new MemoryStream(await File.ReadAllBytesAsync(binaryFile)); return await LoadBinaryAsync(stream); @@ -178,7 +280,7 @@ namespace Il2CppInspectorGUI OnStatusUpdate?.Invoke(this, "Processing binary"); // This may throw other exceptions from the individual loaders as well - IFileFormatStream stream = FileFormatStream.Load(binaryStream, LoadOptions, StatusUpdate); + IFileFormatStream stream = FileFormatStream.Load(binaryStream, ImageLoadOptions, StatusUpdate); if (stream == null) { throw new InvalidOperationException("Could not determine the binary file format"); } diff --git a/Il2CppInspector.GUI/LoadOptionsDialog.xaml.cs b/Il2CppInspector.GUI/LoadOptionsDialog.xaml.cs index 6bd9d81..b68faf5 100644 --- a/Il2CppInspector.GUI/LoadOptionsDialog.xaml.cs +++ b/Il2CppInspector.GUI/LoadOptionsDialog.xaml.cs @@ -28,7 +28,7 @@ namespace Il2CppInspector.GUI InitializeComponent(); var app = (App) Application.Current; - DataContext = app.LoadOptions; + DataContext = app.ImageLoadOptions; } private void okButton_Click(object sender, RoutedEventArgs e) { diff --git a/Il2CppInspector.GUI/PluginManagerDialog.xaml.cs b/Il2CppInspector.GUI/PluginManagerDialog.xaml.cs index 8a9eccc..37269bd 100644 --- a/Il2CppInspector.GUI/PluginManagerDialog.xaml.cs +++ b/Il2CppInspector.GUI/PluginManagerDialog.xaml.cs @@ -35,6 +35,8 @@ namespace Il2CppInspector.GUI private void okButton_Click(object sender, RoutedEventArgs e) { DialogResult = true; + + ((Il2CppInspectorGUI.App) Application.Current).SaveOptions(); } // Reload list of plugins and reset settings diff --git a/Il2CppInspector.GUI/User.Designer.cs b/Il2CppInspector.GUI/User.Designer.cs index f26b929..2cedb23 100644 --- a/Il2CppInspector.GUI/User.Designer.cs +++ b/Il2CppInspector.GUI/User.Designer.cs @@ -46,5 +46,17 @@ namespace Il2CppInspector.GUI { this["UpgradeRequired"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string PluginsState { + get { + return ((string)(this["PluginsState"])); + } + set { + this["PluginsState"] = value; + } + } } } diff --git a/Il2CppInspector.GUI/User.settings b/Il2CppInspector.GUI/User.settings index fe4eae9..aed3d8d 100644 --- a/Il2CppInspector.GUI/User.settings +++ b/Il2CppInspector.GUI/User.settings @@ -8,5 +8,8 @@ True + + + \ No newline at end of file