From b6dc9fcfa994934a1ad241ac67ddd87968840f79 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 6 Jul 2025 09:52:32 +0200 Subject: [PATCH 1/2] Move more of the settings infrastructure to ILSpyX for reuse. --- .../Settings}/DecompilerSettings.cs | 6 +- .../Settings/SettingsServiceBase.cs | 75 +++++++++++++++++++ ILSpy.ReadyToRun/ReadyToRunOptions.cs | 2 +- ILSpy/DecompilationOptions.cs | 2 +- ILSpy/LanguageSettings.cs | 1 + ILSpy/Options/DecompilerSettingsViewModel.cs | 1 + ILSpy/Options/DisplaySettings.cs | 2 + ILSpy/SessionSettings.cs | 1 + ILSpy/Updates/UpdateSettings.cs | 2 + ILSpy/Util/SettingsService.cs | 57 +------------- TestPlugin/CustomOptionPage.xaml.cs | 1 + 11 files changed, 89 insertions(+), 61 deletions(-) rename {ILSpy/Options => ICSharpCode.ILSpyX/Settings}/DecompilerSettings.cs (94%) create mode 100644 ICSharpCode.ILSpyX/Settings/SettingsServiceBase.cs diff --git a/ILSpy/Options/DecompilerSettings.cs b/ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs similarity index 94% rename from ILSpy/Options/DecompilerSettings.cs rename to ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs index 4281b6f0f..817f002a0 100644 --- a/ILSpy/Options/DecompilerSettings.cs +++ b/ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Tom Englert for the SharpDevelop Team +// Copyright (c) 2024 Tom Englert // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software @@ -21,9 +21,7 @@ using System.Linq; using System.Reflection; using System.Xml.Linq; -#nullable enable - -namespace ICSharpCode.ILSpy.Options +namespace ICSharpCode.ILSpyX.Settings { public class DecompilerSettings : Decompiler.DecompilerSettings, ISettingsSection { diff --git a/ICSharpCode.ILSpyX/Settings/SettingsServiceBase.cs b/ICSharpCode.ILSpyX/Settings/SettingsServiceBase.cs new file mode 100644 index 000000000..96ce572c0 --- /dev/null +++ b/ICSharpCode.ILSpyX/Settings/SettingsServiceBase.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2024 Tom Englert +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Xml.Linq; + +namespace ICSharpCode.ILSpyX.Settings +{ + public interface IChildSettings + { + ISettingsSection Parent { get; } + } + + public interface ISettingsSection : INotifyPropertyChanged + { + XName SectionName { get; } + + void LoadFromXml(XElement section); + + XElement SaveToXml(); + } + + public class SettingsServiceBase(ISettingsProvider spySettings) + { + protected readonly ConcurrentDictionary sections = new(); + + protected ISettingsProvider SpySettings { get; set; } = spySettings; + + public T GetSettings() where T : ISettingsSection, new() + { + return (T)sections.GetOrAdd(typeof(T), _ => { + T section = new T(); + + var sectionElement = SpySettings[section.SectionName]; + + section.LoadFromXml(sectionElement); + section.PropertyChanged += Section_PropertyChanged; + + return section; + }); + } + + protected static void SaveSection(ISettingsSection section, XElement root) + { + var element = section.SaveToXml(); + + var existingElement = root.Element(section.SectionName); + if (existingElement != null) + existingElement.ReplaceWith(element); + else + root.Add(element); + } + + protected virtual void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + } + } +} diff --git a/ILSpy.ReadyToRun/ReadyToRunOptions.cs b/ILSpy.ReadyToRun/ReadyToRunOptions.cs index 9b13f7ba7..79ecb5bf0 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptions.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptions.cs @@ -18,7 +18,7 @@ using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; +using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf; diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index c7c87322d..77078fa97 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -23,7 +23,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpyX; -using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; +using DecompilerSettings = ICSharpCode.ILSpyX.Settings.DecompilerSettings; namespace ICSharpCode.ILSpy { diff --git a/ILSpy/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index c07b993ec..12fc1d2d5 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Xml.Linq; using ICSharpCode.ILSpyX; +using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf; diff --git a/ILSpy/Options/DecompilerSettingsViewModel.cs b/ILSpy/Options/DecompilerSettingsViewModel.cs index a01fa1587..4d3999f7a 100644 --- a/ILSpy/Options/DecompilerSettingsViewModel.cs +++ b/ILSpy/Options/DecompilerSettingsViewModel.cs @@ -24,6 +24,7 @@ using System.Reflection; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf; diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs index 8b21987bb..9ff525959 100644 --- a/ILSpy/Options/DisplaySettings.cs +++ b/ILSpy/Options/DisplaySettings.cs @@ -19,6 +19,8 @@ using System.Windows.Media; using System.Xml.Linq; +using ICSharpCode.ILSpyX.Settings; + using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Options diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index 59a939887..b11380c8b 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -30,6 +30,7 @@ using System.Xml.Linq; using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpyX.Search; +using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy { diff --git a/ILSpy/Updates/UpdateSettings.cs b/ILSpy/Updates/UpdateSettings.cs index 4fd3a883a..bd7cee5d0 100644 --- a/ILSpy/Updates/UpdateSettings.cs +++ b/ILSpy/Updates/UpdateSettings.cs @@ -19,6 +19,8 @@ using System; using System.Xml.Linq; +using ICSharpCode.ILSpyX.Settings; + using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Updates diff --git a/ILSpy/Util/SettingsService.cs b/ILSpy/Util/SettingsService.cs index 80b7dc6e0..1a87e6a7e 100644 --- a/ILSpy/Util/SettingsService.cs +++ b/ILSpy/Util/SettingsService.cs @@ -16,71 +16,18 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Concurrent; using System.ComponentModel; -using System.Xml.Linq; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Settings; -using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; +using DecompilerSettings = ICSharpCode.ILSpyX.Settings.DecompilerSettings; #nullable enable namespace ICSharpCode.ILSpy.Util { - public interface IChildSettings - { - ISettingsSection Parent { get; } - } - - public interface ISettingsSection : INotifyPropertyChanged - { - XName SectionName { get; } - - void LoadFromXml(XElement section); - - XElement SaveToXml(); - } - - public abstract class SettingsServiceBase(ISettingsProvider spySettings) - { - protected readonly ConcurrentDictionary sections = new(); - - protected ISettingsProvider SpySettings { get; set; } = spySettings; - - public T GetSettings() where T : ISettingsSection, new() - { - return (T)sections.GetOrAdd(typeof(T), _ => { - T section = new T(); - - var sectionElement = SpySettings[section.SectionName]; - - section.LoadFromXml(sectionElement); - section.PropertyChanged += Section_PropertyChanged; - - return section; - }); - } - - protected static void SaveSection(ISettingsSection section, XElement root) - { - var element = section.SaveToXml(); - - var existingElement = root.Element(section.SectionName); - if (existingElement != null) - existingElement.ReplaceWith(element); - else - root.Add(element); - } - - protected virtual void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - } - } - public class SettingsSnapshot(SettingsService parent, ISettingsProvider spySettings) : SettingsServiceBase(spySettings) { public void Save() @@ -173,7 +120,7 @@ namespace ICSharpCode.ILSpy.Util SpySettings.Update(root => { SaveSection(section, root); }); - }; + } } if (sender is DecompilerSettings decompilerSettings && assemblyListManager != null) diff --git a/TestPlugin/CustomOptionPage.xaml.cs b/TestPlugin/CustomOptionPage.xaml.cs index 95116a743..e28e5ec44 100644 --- a/TestPlugin/CustomOptionPage.xaml.cs +++ b/TestPlugin/CustomOptionPage.xaml.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Util; +using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf; using TomsToolbox.Wpf.Composition.AttributedModel; From bf5249be2b83dd77b04ff9297da4508f455b23af Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 6 Jul 2025 10:30:43 +0200 Subject: [PATCH 2/2] Add --ilspy-settingsfile and -ds|--decompiler-setting name=value options --- ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs | 88 +++++++++++++++++-- .../Settings/DecompilerSettings.cs | 15 ++++ 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs index 2879b2d89..3a641d50c 100644 --- a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs +++ b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs @@ -91,7 +91,7 @@ Examples: public (bool IsSet, string Value) InputPDBFile { get; } [Option("-l|--list ", "Lists all entities of the specified type(s). Valid types: c(lass), i(nterface), s(truct), d(elegate), e(num)", CommandOptionType.MultipleValue)] - public string[] EntityTypes { get; } = new string[0]; + public string[] EntityTypes { get; } = Array.Empty(); public string DecompilerVersion => "ilspycmd: " + typeof(ILSpyCmdProgram).Assembly.GetName().Version.ToString() + Environment.NewLine @@ -100,9 +100,16 @@ Examples: [Option("-lv|--languageversion ", "C# Language version: CSharp1, CSharp2, CSharp3, " + "CSharp4, CSharp5, CSharp6, CSharp7, CSharp7_1, CSharp7_2, CSharp7_3, CSharp8_0, CSharp9_0, " + - "CSharp10_0, Preview or Latest", CommandOptionType.SingleValue)] + "CSharp10_0, CSharp11_0, CSharp12_0, CSharp13_0, Preview or Latest", CommandOptionType.SingleValue)] public LanguageVersion LanguageVersion { get; } = LanguageVersion.Latest; + [FileExists] + [Option("--ilspy-settingsfile ", "Path to an ILSpy settings file.", CommandOptionType.SingleValue)] + public string ILSpySettingsFile { get; } + + [Option("-ds|--decompiler-setting =", "Set a decompiler setting. Use multiple times to set multiple settings.", CommandOptionType.MultipleValue)] + public string[] DecompilerSettingOverrides { get; set; } = Array.Empty(); + [DirectoryExists] [Option("-r|--referencepath ", "Path to a directory containing dependencies of the assembly that is being decompiled.", CommandOptionType.MultipleValue)] public string[] ReferencePaths { get; } @@ -325,13 +332,76 @@ Examples: DecompilerSettings GetSettings(PEFile module) { - return new DecompilerSettings(LanguageVersion) { - ThrowOnAssemblyResolveErrors = false, - RemoveDeadCode = RemoveDeadCode, - RemoveDeadStores = RemoveDeadStores, - UseSdkStyleProjectFormat = WholeProjectDecompiler.CanUseSdkStyleProjectFormat(module), - UseNestedDirectoriesForNamespaces = NestedDirectories, - }; + DecompilerSettings decompilerSettings = null; + + if (ILSpySettingsFile != null) + { + try + { + ILSpyX.Settings.ILSpySettings.SettingsFilePathProvider = new ILSpyX.Settings.DefaultSettingsFilePathProvider(ILSpySettingsFile); + var settingsService = new ILSpyX.Settings.SettingsServiceBase(ILSpyX.Settings.ILSpySettings.Load()); + decompilerSettings = settingsService.GetSettings(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error loading ILSpy settings file '{ILSpySettingsFile}': {ex.Message}"); + } + } + + if (decompilerSettings == null) + { + decompilerSettings = new DecompilerSettings(LanguageVersion) { + ThrowOnAssemblyResolveErrors = false, + RemoveDeadCode = RemoveDeadCode, + RemoveDeadStores = RemoveDeadStores, + UseSdkStyleProjectFormat = WholeProjectDecompiler.CanUseSdkStyleProjectFormat(module), + UseNestedDirectoriesForNamespaces = NestedDirectories, + }; + } + + if (DecompilerSettingOverrides is { Length: > 0 }) + { + foreach (var entry in DecompilerSettingOverrides) + { + int equals = entry.IndexOf('='); + if (equals <= 0) + { + Console.Error.WriteLine($"Decompiler setting '{entry}' is invalid; use '='"); + continue; + } + + string name = entry[..equals].Trim(); + string value = entry[(equals + 1)..].Trim(); + + if (!ILSpyX.Settings.DecompilerSettings.IsKnownOption(name, out var property)) + { + Console.Error.WriteLine($"Decompiler setting '{name}' is unknown."); + continue; + } + + object typedValue; + + try + { + typedValue = Convert.ChangeType(value, property.PropertyType); + } + catch (Exception) + { + Console.Error.WriteLine($"Decompiler setting '{name}': Value '{value}' could not be converted to '{property.PropertyType.FullName}'."); + continue; + } + + if (typedValue == null && property.PropertyType.IsValueType) + { + Console.Error.WriteLine($"Decompiler setting '{name}': Value '{value}' could not be converted to '{property.PropertyType.FullName}'."); + continue; + } + + property.SetValue(decompilerSettings, typedValue); + } + } + + return decompilerSettings; } CSharpDecompiler GetDecompiler(string assemblyFileName) diff --git a/ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs b/ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs index 817f002a0..76d437ed8 100644 --- a/ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs +++ b/ICSharpCode.ILSpyX/Settings/DecompilerSettings.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Xml.Linq; @@ -57,5 +58,19 @@ namespace ICSharpCode.ILSpyX.Settings { return (DecompilerSettings)base.Clone(); } + + public static bool IsKnownOption(string name, [NotNullWhen(true)] out PropertyInfo? property) + { + property = null; + foreach (var item in properties) + { + if (item.Name != name) + continue; + property = item; + return true; + } + + return false; + } } }