2222using System . IO ;
2323using System . Linq ;
2424using System . Threading . Tasks ;
25+ using System . Windows ;
2526
2627using ICSharpCode . Decompiler ;
2728using ICSharpCode . Decompiler . CSharp . ProjectDecompiler ;
3031using ICSharpCode . ILSpy . TextView ;
3132using ICSharpCode . ILSpy . TreeNodes ;
3233using ICSharpCode . ILSpyX ;
34+ using ICSharpCode . ILSpyX . TreeView ;
3335
3436using Microsoft . Win32 ;
3537
38+ using static ICSharpCode . ILSpyX . LoadedPackage ;
39+
3640namespace ICSharpCode . ILSpy
3741{
3842 [ ExportContextMenuEntry ( Header = nameof ( Resources . ExtractPackageEntry ) , Category = nameof ( Resources . Save ) , Icon = "Images/Save" ) ]
@@ -41,52 +45,184 @@ sealed class ExtractPackageEntryContextMenuEntry(DockWorkspace dockWorkspace) :
4145 {
4246 public void Execute ( TextViewContext context )
4347 {
44- // Get all assemblies in the selection that are stored inside a package.
45- var selectedNodes = context . SelectedTreeNodes . OfType < AssemblyTreeNode > ( )
46- . Where ( asm => asm . PackageEntry != null ) . ToArray ( ) ;
48+ var selectedNodes = Array . FindAll ( context . SelectedTreeNodes , x => x is AssemblyTreeNode { PackageEntry : { } } or PackageFolderTreeNode ) ;
4749 // Get root assembly to infer the initial directory for the save dialog.
4850 var bundleNode = selectedNodes . FirstOrDefault ( ) ? . Ancestors ( ) . OfType < AssemblyTreeNode > ( )
4951 . FirstOrDefault ( asm => asm . PackageEntry == null ) ;
5052 if ( bundleNode == null )
5153 return ;
52- var assembly = selectedNodes [ 0 ] . PackageEntry ;
53- SaveFileDialog dlg = new SaveFileDialog ( ) ;
54- dlg . FileName = Path . GetFileName ( WholeProjectDecompiler . SanitizeFileName ( assembly . Name ) ) ;
55- dlg . Filter = ".NET assemblies|*.dll;*.exe;*.winmd" + Resources . AllFiles ;
56- dlg . InitialDirectory = Path . GetDirectoryName ( bundleNode . LoadedAssembly . FileName ) ;
54+ if ( selectedNodes is [ AssemblyTreeNode { PackageEntry : { } assembly } ] )
55+ {
56+ SaveFileDialog dlg = new SaveFileDialog ( ) ;
57+ dlg . FileName = Path . GetFileName ( WholeProjectDecompiler . SanitizeFileName ( assembly . Name ) ) ;
58+ dlg . Filter = ".NET assemblies|*.dll;*.exe;*.winmd" + Resources . AllFiles ;
59+ dlg . InitialDirectory = Path . GetDirectoryName ( bundleNode . LoadedAssembly . FileName ) ;
60+ if ( dlg . ShowDialog ( ) != true )
61+ return ;
62+
63+ string fileName = dlg . FileName ;
64+ dockWorkspace . RunWithCancellation ( ct => Task < AvalonEditTextOutput > . Factory . StartNew ( ( ) => {
65+ AvalonEditTextOutput output = new AvalonEditTextOutput ( ) ;
66+ Stopwatch stopwatch = Stopwatch . StartNew ( ) ;
67+ SaveEntry ( output , assembly , fileName ) ;
68+ stopwatch . Stop ( ) ;
69+ output . WriteLine ( Resources . GenerationCompleteInSeconds , stopwatch . Elapsed . TotalSeconds . ToString ( "F1" ) ) ;
70+ output . WriteLine ( ) ;
71+ output . AddButton ( null , Resources . OpenExplorer , delegate { Process . Start ( "explorer" , "/select,\" " + fileName + "\" " ) ; } ) ;
72+ output . WriteLine ( ) ;
73+ return output ;
74+ } , ct ) ) . Then ( dockWorkspace . ShowText ) . HandleExceptions ( ) ;
75+ }
76+ else
77+ {
78+ OpenFolderDialog dlg = new OpenFolderDialog ( ) ;
79+ dlg . InitialDirectory = Path . GetDirectoryName ( bundleNode . LoadedAssembly . FileName ) ;
80+ if ( dlg . ShowDialog ( ) != true )
81+ return ;
82+
83+ string folderName = dlg . FolderName ;
84+ if ( Directory . EnumerateFileSystemEntries ( folderName ) . Any ( ) )
85+ {
86+ var result = MessageBox . Show (
87+ Resources . AssemblySaveCodeDirectoryNotEmpty ,
88+ Resources . AssemblySaveCodeDirectoryNotEmptyTitle ,
89+ MessageBoxButton . YesNo , MessageBoxImage . Question , MessageBoxResult . No ) ;
90+ if ( result == MessageBoxResult . No )
91+ return ;
92+ }
93+
94+ dockWorkspace . RunWithCancellation ( ct => Task < AvalonEditTextOutput > . Factory . StartNew ( ( ) => {
95+ AvalonEditTextOutput output = new AvalonEditTextOutput ( ) ;
96+ Stopwatch stopwatch = Stopwatch . StartNew ( ) ;
97+ foreach ( var selectedNode in selectedNodes )
98+ {
99+ if ( selectedNode is AssemblyTreeNode { PackageEntry : { } assembly } )
100+ {
101+ string fileName = Path . Combine ( folderName , GetFullPath ( selectedNode . Parent ) + WholeProjectDecompiler . SanitizeFileName ( assembly . Name ) ) ;
102+ SaveEntry ( output , assembly , fileName ) ;
103+ }
104+ else if ( selectedNode is PackageFolderTreeNode )
105+ {
106+ selectedNode . EnsureLazyChildren ( ) ;
107+ foreach ( var node in selectedNode . DescendantsAndSelf ( ) )
108+ {
109+ if ( node is AssemblyTreeNode { PackageEntry : { } asm } )
110+ {
111+ string fileName = Path . Combine ( folderName , GetFullPath ( node . Parent ) + WholeProjectDecompiler . SanitizeFileName ( asm . Name ) ) ;
112+ SaveEntry ( output , asm , fileName ) ;
113+ }
114+ else if ( node is PackageFolderTreeNode )
115+ {
116+ Directory . CreateDirectory ( Path . Combine ( folderName , GetFullPath ( node ) ) ) ;
117+ }
118+ }
119+ }
120+ }
121+ stopwatch . Stop ( ) ;
122+ output . WriteLine ( Resources . GenerationCompleteInSeconds , stopwatch . Elapsed . TotalSeconds . ToString ( "F1" ) ) ;
123+ output . WriteLine ( ) ;
124+ output . AddButton ( null , Resources . OpenExplorer , delegate { Process . Start ( "explorer" , "\" " + folderName + "\" " ) ; } ) ;
125+ output . WriteLine ( ) ;
126+ return output ;
127+ } , ct ) ) . Then ( dockWorkspace . ShowText ) . HandleExceptions ( ) ;
128+ }
129+ }
130+
131+ static string GetFullPath ( SharpTreeNode node )
132+ {
133+ if ( node is PackageFolderTreeNode )
134+ {
135+ string name = node . Text + "\\ " ;
136+ if ( GetFullPath ( node . Parent ) is string parent )
137+ return parent + "\\ " + name ;
138+ return name ;
139+ }
140+ return null ;
141+ }
142+
143+ void SaveEntry ( ITextOutput output , PackageEntry entry , string targetFileName )
144+ {
145+ output . Write ( entry . Name + ": " ) ;
146+ using Stream stream = entry . TryOpenStream ( ) ;
147+ if ( stream == null )
148+ {
149+ output . WriteLine ( "Could not open stream!" ) ;
150+ return ;
151+ }
152+
153+ stream . Position = 0 ;
154+ using FileStream fileStream = new FileStream ( targetFileName , FileMode . OpenOrCreate ) ;
155+ stream . CopyTo ( fileStream ) ;
156+ output . WriteLine ( "Written to " + targetFileName ) ;
157+ }
158+
159+ public bool IsEnabled ( TextViewContext context ) => true ;
160+
161+ public bool IsVisible ( TextViewContext context ) =>
162+ context . SelectedTreeNodes ? . Any ( x => x is AssemblyTreeNode { PackageEntry : { } } or PackageFolderTreeNode ) == true ;
163+ }
164+
165+ [ ExportContextMenuEntry ( Header = nameof ( Resources . ExtractAllPackageEntries ) , Category = nameof ( Resources . Save ) , Icon = "Images/Save" ) ]
166+ [ Shared ]
167+ sealed class ExtractAllPackageEntriesContextMenuEntry ( DockWorkspace dockWorkspace ) : IContextMenuEntry
168+ {
169+ public void Execute ( TextViewContext context )
170+ {
171+ if ( context . SelectedTreeNodes is not [ AssemblyTreeNode { PackageEntry : null } asm ] )
172+ return ;
173+ OpenFolderDialog dlg = new OpenFolderDialog ( ) ;
174+ dlg . InitialDirectory = Path . GetDirectoryName ( asm . LoadedAssembly . FileName ) ;
57175 if ( dlg . ShowDialog ( ) != true )
58176 return ;
59177
60- string fileName = dlg . FileName ;
61- string outputFolderOrFileName = fileName ;
62- if ( selectedNodes . Length > 1 )
63- outputFolderOrFileName = Path . GetDirectoryName ( outputFolderOrFileName ) ;
178+ string folderName = dlg . FolderName ;
179+ if ( Directory . EnumerateFileSystemEntries ( folderName ) . Any ( ) )
180+ {
181+ var result = MessageBox . Show (
182+ Resources . AssemblySaveCodeDirectoryNotEmpty ,
183+ Resources . AssemblySaveCodeDirectoryNotEmptyTitle ,
184+ MessageBoxButton . YesNo , MessageBoxImage . Question , MessageBoxResult . No ) ;
185+ if ( result == MessageBoxResult . No )
186+ return ;
187+ }
64188
65189 dockWorkspace . RunWithCancellation ( ct => Task < AvalonEditTextOutput > . Factory . StartNew ( ( ) => {
66190 AvalonEditTextOutput output = new AvalonEditTextOutput ( ) ;
67191 Stopwatch stopwatch = Stopwatch . StartNew ( ) ;
68- stopwatch . Stop ( ) ;
69-
70- if ( selectedNodes . Length == 1 )
192+ asm . EnsureLazyChildren ( ) ;
193+ foreach ( var node in asm . Descendants ( ) )
71194 {
72- SaveEntry ( output , selectedNodes [ 0 ] . PackageEntry , outputFolderOrFileName ) ;
73- }
74- else
75- {
76- foreach ( var node in selectedNodes )
195+ if ( node is AssemblyTreeNode { PackageEntry : { } assembly } )
196+ {
197+ string fileName = Path . Combine ( folderName , GetFullPath ( node . Parent ) + WholeProjectDecompiler . SanitizeFileName ( assembly . Name ) ) ;
198+ SaveEntry ( output , assembly , fileName ) ;
199+ }
200+ else if ( node is PackageFolderTreeNode )
77201 {
78- var fileName = Path . GetFileName ( WholeProjectDecompiler . SanitizeFileName ( node . PackageEntry . Name ) ) ;
79- SaveEntry ( output , node . PackageEntry , Path . Combine ( outputFolderOrFileName , fileName ) ) ;
202+ Directory . CreateDirectory ( Path . Combine ( folderName , GetFullPath ( node ) ) ) ;
80203 }
81204 }
205+ stopwatch . Stop ( ) ;
82206 output . WriteLine ( Resources . GenerationCompleteInSeconds , stopwatch . Elapsed . TotalSeconds . ToString ( "F1" ) ) ;
83207 output . WriteLine ( ) ;
84- output . AddButton ( null , Resources . OpenExplorer , delegate { Process . Start ( "explorer" , "/select, \" " + fileName + "\" " ) ; } ) ;
208+ output . AddButton ( null , Resources . OpenExplorer , delegate { Process . Start ( "explorer" , "\" " + folderName + "\" " ) ; } ) ;
85209 output . WriteLine ( ) ;
86210 return output ;
87211 } , ct ) ) . Then ( dockWorkspace . ShowText ) . HandleExceptions ( ) ;
88212 }
89213
214+ static string GetFullPath ( SharpTreeNode node )
215+ {
216+ if ( node is PackageFolderTreeNode )
217+ {
218+ string name = node . Text + "\\ " ;
219+ if ( GetFullPath ( node . Parent ) is string parent )
220+ return parent + "\\ " + name ;
221+ return name ;
222+ }
223+ return null ;
224+ }
225+
90226 void SaveEntry ( ITextOutput output , PackageEntry entry , string targetFileName )
91227 {
92228 output . Write ( entry . Name + ": " ) ;
@@ -107,9 +243,18 @@ void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName)
107243
108244 public bool IsVisible ( TextViewContext context )
109245 {
110- var selectedNodes = context . SelectedTreeNodes ? . OfType < AssemblyTreeNode > ( )
111- . Where ( asm => asm . PackageEntry != null ) ?? Enumerable . Empty < AssemblyTreeNode > ( ) ;
112- return selectedNodes . Any ( ) ;
246+ if ( context . SelectedTreeNodes is [ AssemblyTreeNode { PackageEntry : null } asm ] )
247+ {
248+ try
249+ {
250+ if ( asm . LoadedAssembly . GetLoadResultAsync ( ) . GetAwaiter ( ) . GetResult ( ) . Package is { Kind : PackageKind . Bundle } )
251+ return true ;
252+ }
253+ catch
254+ {
255+ }
256+ }
257+ return false ;
113258 }
114259 }
115260}
0 commit comments