1717// DEALINGS IN THE SOFTWARE.
1818
1919using System ;
20+ using System . Collections . Generic ;
2021using System . Composition ;
2122using System . Diagnostics ;
2223using System . IO ;
2324using System . Linq ;
2425using System . Threading . Tasks ;
26+ using System . Windows ;
2527
2628using ICSharpCode . Decompiler ;
2729using ICSharpCode . Decompiler . CSharp . ProjectDecompiler ;
3032using ICSharpCode . ILSpy . TextView ;
3133using ICSharpCode . ILSpy . TreeNodes ;
3234using ICSharpCode . ILSpyX ;
35+ using ICSharpCode . ILSpyX . TreeView ;
3336
3437using Microsoft . Win32 ;
3538
39+ using static ICSharpCode . ILSpyX . LoadedPackage ;
40+
3641namespace ICSharpCode . ILSpy
3742{
3843 [ ExportContextMenuEntry ( Header = nameof ( Resources . ExtractPackageEntry ) , Category = nameof ( Resources . Save ) , Icon = "Images/Save" ) ]
@@ -41,55 +46,115 @@ sealed class ExtractPackageEntryContextMenuEntry(DockWorkspace dockWorkspace) :
4146 {
4247 public void Execute ( TextViewContext context )
4348 {
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 ( ) ;
49+ var selectedNodes = Array . FindAll ( context . SelectedTreeNodes , IsBundleItem ) ;
4750 // Get root assembly to infer the initial directory for the save dialog.
4851 var bundleNode = selectedNodes . FirstOrDefault ( ) ? . Ancestors ( ) . OfType < AssemblyTreeNode > ( )
4952 . FirstOrDefault ( asm => asm . PackageEntry == null ) ;
5053 if ( bundleNode == null )
5154 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 ) ;
57- if ( dlg . ShowDialog ( ) != true )
58- return ;
55+ if ( selectedNodes is [ AssemblyTreeNode { PackageEntry : { } assembly } ] )
56+ {
57+ SaveFileDialog dlg = new SaveFileDialog ( ) ;
58+ dlg . FileName = Path . GetFileName ( WholeProjectDecompiler . SanitizeFileName ( assembly . Name ) ) ;
59+ dlg . Filter = ".NET assemblies|*.dll;*.exe;*.winmd" + Resources . AllFiles ;
60+ dlg . InitialDirectory = Path . GetDirectoryName ( bundleNode . LoadedAssembly . FileName ) ;
61+ if ( dlg . ShowDialog ( ) == true )
62+ Save ( dockWorkspace , selectedNodes , dlg . FileName , true ) ;
63+ }
64+ else if ( selectedNodes is [ ResourceTreeNode { Resource : { } resource } ] )
65+ {
66+ SaveFileDialog dlg = new SaveFileDialog ( ) ;
67+ dlg . FileName = Path . GetFileName ( WholeProjectDecompiler . SanitizeFileName ( resource . Name ) ) ;
68+ dlg . Filter = Resources . AllFiles [ 1 ..] ;
69+ dlg . InitialDirectory = Path . GetDirectoryName ( bundleNode . LoadedAssembly . FileName ) ;
70+ if ( dlg . ShowDialog ( ) == true )
71+ Save ( dockWorkspace , selectedNodes , dlg . FileName , true ) ;
72+ }
73+ else
74+ {
75+ OpenFolderDialog dlg = new OpenFolderDialog ( ) ;
76+ dlg . InitialDirectory = Path . GetDirectoryName ( bundleNode . LoadedAssembly . FileName ) ;
77+ if ( dlg . ShowDialog ( ) != true )
78+ return ;
79+
80+ string folderName = dlg . FolderName ;
81+ if ( Directory . EnumerateFileSystemEntries ( folderName ) . Any ( ) )
82+ {
83+ var result = MessageBox . Show (
84+ Resources . AssemblySaveCodeDirectoryNotEmpty ,
85+ Resources . AssemblySaveCodeDirectoryNotEmptyTitle ,
86+ MessageBoxButton . YesNo , MessageBoxImage . Question , MessageBoxResult . No ) ;
87+ if ( result == MessageBoxResult . No )
88+ return ;
89+ }
5990
60- string fileName = dlg . FileName ;
61- string outputFolderOrFileName = fileName ;
62- if ( selectedNodes . Length > 1 )
63- outputFolderOrFileName = Path . GetDirectoryName ( outputFolderOrFileName ) ;
91+ Save ( dockWorkspace , selectedNodes , folderName , false ) ;
92+ }
93+ }
6494
95+ static string GetPackageFolderPath ( SharpTreeNode node )
96+ {
97+ string name = "" ;
98+ while ( node is PackageFolderTreeNode )
99+ {
100+ name = Path . Combine ( node . Text . ToString ( ) , name ) ;
101+ node = node . Parent ;
102+ }
103+ return name ;
104+ }
105+
106+ internal static void Save ( DockWorkspace dockWorkspace , IEnumerable < SharpTreeNode > nodes , string path , bool isFile )
107+ {
65108 dockWorkspace . RunWithCancellation ( ct => Task < AvalonEditTextOutput > . Factory . StartNew ( ( ) => {
66109 AvalonEditTextOutput output = new AvalonEditTextOutput ( ) ;
67110 Stopwatch stopwatch = Stopwatch . StartNew ( ) ;
68- stopwatch . Stop ( ) ;
69-
70- if ( selectedNodes . Length == 1 )
71- {
72- SaveEntry ( output , selectedNodes [ 0 ] . PackageEntry , outputFolderOrFileName ) ;
73- }
74- else
111+ foreach ( var node in nodes )
75112 {
76- foreach ( var node in selectedNodes )
113+ if ( node is AssemblyTreeNode { PackageEntry : { } assembly } )
114+ {
115+ string fileName = isFile ? path : Path . Combine ( path , GetPackageFolderPath ( node . Parent ) , assembly . Name ) ;
116+ SaveEntry ( output , assembly , fileName ) ;
117+ }
118+ else if ( node is ResourceTreeNode { Resource : PackageEntry { } resource } )
77119 {
78- var fileName = Path . GetFileName ( WholeProjectDecompiler . SanitizeFileName ( node . PackageEntry . Name ) ) ;
79- SaveEntry ( output , node . PackageEntry , Path . Combine ( outputFolderOrFileName , fileName ) ) ;
120+ string fileName = isFile ? path : Path . Combine ( path , GetPackageFolderPath ( node . Parent ) , resource . Name ) ;
121+ SaveEntry ( output , resource , fileName ) ;
122+ }
123+ else if ( node is PackageFolderTreeNode )
124+ {
125+ node . EnsureLazyChildren ( ) ;
126+ foreach ( var item in node . DescendantsAndSelf ( ) )
127+ {
128+ if ( item is AssemblyTreeNode { PackageEntry : { } asm } )
129+ {
130+ string fileName = Path . Combine ( path , GetPackageFolderPath ( item . Parent ) , asm . Name ) ;
131+ SaveEntry ( output , asm , fileName ) ;
132+ }
133+ else if ( item is ResourceTreeNode { Resource : PackageEntry { } entry } )
134+ {
135+ string fileName = Path . Combine ( path , GetPackageFolderPath ( item . Parent ) , entry . Name ) ;
136+ SaveEntry ( output , entry , fileName ) ;
137+ }
138+ else if ( item is PackageFolderTreeNode )
139+ {
140+ Directory . CreateDirectory ( Path . Combine ( path , GetPackageFolderPath ( item ) ) ) ;
141+ }
142+ }
80143 }
81144 }
145+ stopwatch . Stop ( ) ;
82146 output . WriteLine ( Resources . GenerationCompleteInSeconds , stopwatch . Elapsed . TotalSeconds . ToString ( "F1" ) ) ;
83147 output . WriteLine ( ) ;
84- output . AddButton ( null , Resources . OpenExplorer , delegate { Process . Start ( "explorer" , "/select,\" " + fileName + " \" ") ; } ) ;
148+ output . AddButton ( null , Resources . OpenExplorer , delegate { Process . Start ( "explorer" , isFile ? $ "/select,\" { path } \" " : $ " \" { path } \" ") ; } ) ;
85149 output . WriteLine ( ) ;
86150 return output ;
87151 } , ct ) ) . Then ( dockWorkspace . ShowText ) . HandleExceptions ( ) ;
88152 }
89153
90- void SaveEntry ( ITextOutput output , PackageEntry entry , string targetFileName )
154+ static void SaveEntry ( ITextOutput output , PackageEntry entry , string targetFileName )
91155 {
92156 output . Write ( entry . Name + ": " ) ;
157+ targetFileName = WholeProjectDecompiler . SanitizeFileName ( targetFileName ) ;
93158 using Stream stream = entry . TryOpenStream ( ) ;
94159 if ( stream == null )
95160 {
@@ -105,11 +170,58 @@ void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName)
105170
106171 public bool IsEnabled ( TextViewContext context ) => true ;
107172
173+ public bool IsVisible ( TextViewContext context ) => context . SelectedTreeNodes ? . Any ( IsBundleItem ) == true ;
174+
175+ static bool IsBundleItem ( SharpTreeNode node )
176+ {
177+ if ( node is AssemblyTreeNode { PackageEntry : { } } or PackageFolderTreeNode )
178+ return true ;
179+ if ( node is ResourceTreeNode { Resource : PackageEntry { } resource } && resource . FullName . StartsWith ( "bundle://" ) )
180+ return true ;
181+ return false ;
182+ }
183+ }
184+
185+ [ ExportContextMenuEntry ( Header = nameof ( Resources . ExtractAllPackageEntries ) , Category = nameof ( Resources . Save ) , Icon = "Images/Save" ) ]
186+ [ Shared ]
187+ sealed class ExtractAllPackageEntriesContextMenuEntry ( DockWorkspace dockWorkspace ) : IContextMenuEntry
188+ {
189+ public void Execute ( TextViewContext context )
190+ {
191+ if ( context . SelectedTreeNodes is not [ AssemblyTreeNode { PackageEntry : null } asm ] )
192+ return ;
193+ OpenFolderDialog dlg = new OpenFolderDialog ( ) ;
194+ dlg . InitialDirectory = Path . GetDirectoryName ( asm . LoadedAssembly . FileName ) ;
195+ if ( dlg . ShowDialog ( ) != true )
196+ return ;
197+
198+ string folderName = dlg . FolderName ;
199+ if ( Directory . EnumerateFileSystemEntries ( folderName ) . Any ( ) )
200+ {
201+ var result = MessageBox . Show (
202+ Resources . AssemblySaveCodeDirectoryNotEmpty ,
203+ Resources . AssemblySaveCodeDirectoryNotEmptyTitle ,
204+ MessageBoxButton . YesNo , MessageBoxImage . Question , MessageBoxResult . No ) ;
205+ if ( result == MessageBoxResult . No )
206+ return ;
207+ }
208+
209+ asm . EnsureLazyChildren ( ) ;
210+ ExtractPackageEntryContextMenuEntry . Save ( dockWorkspace , asm . Descendants ( ) , folderName , false ) ;
211+ }
212+
213+ public bool IsEnabled ( TextViewContext context ) => true ;
214+
108215 public bool IsVisible ( TextViewContext context )
109216 {
110- var selectedNodes = context . SelectedTreeNodes ? . OfType < AssemblyTreeNode > ( )
111- . Where ( asm => asm . PackageEntry != null ) ?? Enumerable . Empty < AssemblyTreeNode > ( ) ;
112- return selectedNodes . Any ( ) ;
217+ if ( context . SelectedTreeNodes is [ AssemblyTreeNode { LoadedAssembly . IsLoaded : true , LoadedAssembly . HasLoadError : false , PackageEntry : null } asm ] )
218+ {
219+ // Using .GetAwaiter().GetResult() is no problem here, since we already checked IsLoaded and HasLoadError.
220+ var loadResult = asm . LoadedAssembly . GetLoadResultAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
221+ if ( loadResult . Package is { Kind : PackageKind . Bundle } )
222+ return true ;
223+ }
224+ return false ;
113225 }
114226 }
115227}
0 commit comments