diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs
index 3f5dcddea..c20f01aaf 100644
--- a/ILSpy/Commands/DecompileAllCommand.cs
+++ b/ILSpy/Commands/DecompileAllCommand.cs
@@ -108,6 +108,78 @@ namespace ILSpy.Commands
}
}
+ ///
+ /// DEBUG-only File-menu stress test: disassemble every loaded assembly to
+ /// c:\temp\disassembled\<Name>.il in parallel. Same shape as
+ /// but routes through .
+ /// CanExecute gates on the output directory existing.
+ ///
+ [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.55)]
+ [Shared]
+ sealed class DisassembleAllCommand : SimpleCommand
+ {
+ const string OutputDir = @"c:\temp\disassembled";
+
+ readonly AssemblyTreeModel assemblyTreeModel;
+ readonly DockWorkspace dockWorkspace;
+
+ [ImportingConstructor]
+ public DisassembleAllCommand(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace)
+ {
+ this.assemblyTreeModel = assemblyTreeModel;
+ this.dockWorkspace = dockWorkspace;
+ }
+
+ public override bool CanExecute(object? parameter) => Directory.Exists(OutputDir);
+
+ public override void Execute(object? parameter) => _ = ExecuteAsync();
+
+ async Task ExecuteAsync()
+ {
+ try
+ {
+ var report = await dockWorkspace.RunWithCancellation(token => Task.Run(() => {
+ var output = new AvaloniaEditTextOutput { Title = "Disassemble All" };
+ var bag = new ConcurrentBag();
+ Parallel.ForEach(
+ Partitioner.Create(assemblyTreeModel.AssemblyList!.GetAssemblies(), loadBalance: true),
+ new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = token },
+ asm => {
+ if (asm.HasLoadError)
+ return;
+ var watch = Stopwatch.StartNew();
+ Exception? ex = null;
+ var safeName = (asm.Text?.ToString() ?? asm.ShortName).Replace("(", "").Replace(")", "").Replace(' ', '_');
+ var path = Path.Combine(OutputDir, safeName + ".il");
+ try
+ {
+ using var writer = new StreamWriter(path);
+ var options = new DecompilationOptions {
+ CancellationToken = token,
+ FullDecompilation = true,
+ };
+ new ILLanguage().DecompileAssembly(asm, new PlainTextOutput(writer), options);
+ }
+ catch (Exception caught)
+ {
+ ex = caught;
+ }
+ watch.Stop();
+ bag.Add(asm.ShortName + " - " + watch.Elapsed + (ex != null ? " - " + ex.GetType().Name : ""));
+ });
+ foreach (var line in bag.OrderBy(s => s, StringComparer.OrdinalIgnoreCase))
+ {
+ output.Write(line);
+ output.WriteLine();
+ }
+ return output;
+ }, token), "Disassembling all assemblies…");
+ dockWorkspace.ShowText(report);
+ }
+ catch (OperationCanceledException) { }
+ }
+ }
+
///
/// DEBUG-only stress test: re-decompile the current selection 100 times in series
/// and report average wall-clock time. Used for tracking decompilation perf
diff --git a/ILSpy/Commands/FileCommands.cs b/ILSpy/Commands/FileCommands.cs
index 445d865ac..f37f97860 100644
--- a/ILSpy/Commands/FileCommands.cs
+++ b/ILSpy/Commands/FileCommands.cs
@@ -110,12 +110,8 @@ namespace ILSpy.Commands
// DEBUG-only DecompileAllCommand + Decompile100TimesCommand live in DecompileAllCommand.cs;
// the stubs that lived here were replaced with the real implementations in ``.
- [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)]
- [Shared]
- sealed class DisassembleAllCommand : SimpleCommand
- {
- public override void Execute(object? parameter) => NotImplementedDialog.Show(Resources.DEBUGDisassemble);
- }
+ // DEBUG-only DisassembleAllCommand also moved to DecompileAllCommand.cs to keep all
+ // three parallel-decompile/disassemble stress-test commands in one place.
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)]
[Shared]