11using System . IO ;
22using System . Diagnostics ;
33using System . Globalization ;
4+ using System . Runtime . InteropServices ;
45using System . Threading ;
56using System . Windows ;
67using System . Windows . Controls ;
@@ -279,6 +280,15 @@ public double TileNameMaxHeight
279280 [ "ExtractSelectedItems" ] = ( "导出 {0:N0} 个选中项..." , "Extract {0:N0} Selected Items..." ) ,
280281 [ "ExtractSelectedDefault" ] = ( "导出选中项..." , "Extract Selected..." ) ,
281282 [ "OpenPreview" ] = ( "打开预览..." , "Open Preview..." ) ,
283+ [ "LocateFile" ] = ( "\u5b9a \u4f4d \u5230 \u6587 \u4ef6 " , "Locate File" ) ,
284+ [ "LocatedItem" ] = ( "\u5df2 \u5b9a \u4f4d \u5230 : {0}" , "Located: {0}" ) ,
285+ [ "LocateFileFailed" ] = ( "\u5b9a \u4f4d \u5230 \u6587 \u4ef6 \u5931 \u8d25 " , "Locate file failed" ) ,
286+ [ "CopyName" ] = ( "\u590d \u5236 \u540d \u79f0 " , "Copy Name" ) ,
287+ [ "CopySelectedNames" ] = ( "\u590d \u5236 {0:N0} \u4e2a \u540d \u79f0 " , "Copy {0:N0} Names" ) ,
288+ [ "CopiedName" ] = ( "\u5df2 \u590d \u5236 \u540d \u79f0 : {0}" , "Copied name: {0}" ) ,
289+ [ "CopiedNames" ] = ( "\u5df2 \u590d \u5236 {0:N0} \u4e2a \u540d \u79f0 " , "Copied {0:N0} names" ) ,
290+ [ "CopyNameFailed" ] = ( "\u590d \u5236 \u540d \u79f0 \u5931 \u8d25 " , "Copy name failed" ) ,
291+ [ "CopyNameClipboardBusy" ] = ( "\u526a \u8d34 \u677f \u6b63 \u88ab \u5176 \u4ed6 \u7a0b \u5e8f \u5360 \u7528 \uff0c \u8bf7 \u7a0d \u540e \u518d \u8bd5 \u3002 " , "Clipboard is busy. Please try again." ) ,
282292 [ "DecodeBank" ] = ( "\u89e3 \u7801 BANK..." , "Decode BANK..." ) ,
283293 [ "OpenAudioPreview" ] = ( "播放音频..." , "Play Audio..." ) ,
284294 [ "OpenImagePreview" ] = ( "查看图片..." , "View Image..." ) ,
@@ -412,6 +422,201 @@ private async void ExtractSelectedMenuItem_Click(object sender, RoutedEventArgs
412422 await ExtractItemsAsync ( selectedItems , FormatText ( "PreparingItem" , label ) , preserveOutputRelativePaths : false ) ;
413423 }
414424
425+ private async void CopyNameMenuItem_Click ( object sender , RoutedEventArgs e )
426+ {
427+ List < ExplorerItem > selectedItems = GetSelectedExplorerItems ( ) ;
428+ if ( selectedItems . Count == 0 )
429+ {
430+ return ;
431+ }
432+
433+ try
434+ {
435+ if ( selectedItems . Count == 1 )
436+ {
437+ await SetClipboardTextAsync ( selectedItems [ 0 ] . Name ) ;
438+ SetStatus ( "CopiedName" , selectedItems [ 0 ] . Name ) ;
439+ return ;
440+ }
441+
442+ string names = string . Join ( Environment . NewLine , selectedItems . Select ( item => item . Name ) ) ;
443+ await SetClipboardTextAsync ( names ) ;
444+ SetStatus ( "CopiedNames" , selectedItems . Count ) ;
445+ }
446+ catch ( Exception ex )
447+ {
448+ ShowError ( "CopyNameFailed" , ex ) ;
449+ }
450+ }
451+
452+ private async void LocateFileMenuItem_Click ( object sender , RoutedEventArgs e )
453+ {
454+ List < ExplorerItem > selectedItems = GetSelectedExplorerItems ( ) ;
455+ if ( selectedItems . Count != 1 )
456+ {
457+ return ;
458+ }
459+
460+ try
461+ {
462+ await LocateExplorerItemAsync ( selectedItems [ 0 ] ) ;
463+ }
464+ catch ( Exception ex )
465+ {
466+ ShowError ( "LocateFileFailed" , ex ) ;
467+ }
468+ }
469+
470+ private async Task LocateExplorerItemAsync ( ExplorerItem item )
471+ {
472+ if ( item . Parent is not { } parent )
473+ {
474+ return ;
475+ }
476+
477+ await ShowFolderAsync ( parent ) ;
478+ if ( ! ReferenceEquals ( _currentItem , parent ) )
479+ {
480+ return ;
481+ }
482+
483+ ContentsList . SelectedItems . Clear ( ) ;
484+ ContentsList . SelectedItem = item ;
485+ ContentsList . ScrollIntoView ( item ) ;
486+ await Dispatcher . InvokeAsync (
487+ ( ) =>
488+ {
489+ ContentsList . UpdateLayout ( ) ;
490+ if ( ContentsList . ItemContainerGenerator . ContainerFromItem ( item ) is ListBoxItem container )
491+ {
492+ container . Focus ( ) ;
493+ }
494+ else
495+ {
496+ ContentsList . Focus ( ) ;
497+ }
498+ } ,
499+ DispatcherPriority . Background ) ;
500+ SetStatus ( "LocatedItem" , item . Name ) ;
501+ }
502+
503+ private async Task SetClipboardTextAsync ( string text )
504+ {
505+ const int maxAttempts = 24 ;
506+ int delayMilliseconds = 25 ;
507+
508+ for ( int attempt = 1 ; attempt <= maxAttempts ; attempt ++ )
509+ {
510+ if ( TrySetClipboardText ( text ) )
511+ {
512+ return ;
513+ }
514+
515+ if ( attempt < maxAttempts )
516+ {
517+ await Task . Delay ( delayMilliseconds ) ;
518+ delayMilliseconds = Math . Min ( delayMilliseconds + 25 , 250 ) ;
519+ }
520+ }
521+
522+ throw new InvalidOperationException ( T ( "CopyNameClipboardBusy" ) ) ;
523+ }
524+
525+ private bool TrySetClipboardText ( string text )
526+ {
527+ byte [ ] bytes = System . Text . Encoding . Unicode . GetBytes ( text + '\0 ' ) ;
528+ IntPtr memory = NativeMethods . GlobalAlloc ( NativeMethods . GmemMoveable , new UIntPtr ( checked ( ( uint ) bytes . Length ) ) ) ;
529+ if ( memory == IntPtr . Zero )
530+ {
531+ throw new InvalidOperationException ( Marshal . GetLastWin32Error ( ) . ToString ( CultureInfo . InvariantCulture ) ) ;
532+ }
533+
534+ bool clipboardOpened = false ;
535+ try
536+ {
537+ IntPtr lockedMemory = NativeMethods . GlobalLock ( memory ) ;
538+ if ( lockedMemory == IntPtr . Zero )
539+ {
540+ throw new InvalidOperationException ( Marshal . GetLastWin32Error ( ) . ToString ( CultureInfo . InvariantCulture ) ) ;
541+ }
542+
543+ try
544+ {
545+ Marshal . Copy ( bytes , 0 , lockedMemory , bytes . Length ) ;
546+ }
547+ finally
548+ {
549+ _ = NativeMethods . GlobalUnlock ( memory ) ;
550+ }
551+
552+ IntPtr owner = new WindowInteropHelper ( this ) . Handle ;
553+ if ( ! NativeMethods . OpenClipboard ( owner ) )
554+ {
555+ return false ;
556+ }
557+
558+ clipboardOpened = true ;
559+ if ( ! NativeMethods . EmptyClipboard ( ) )
560+ {
561+ return false ;
562+ }
563+
564+ if ( NativeMethods . SetClipboardData ( NativeMethods . CfUnicodeText , memory ) == IntPtr . Zero )
565+ {
566+ return false ;
567+ }
568+
569+ memory = IntPtr . Zero ;
570+ return true ;
571+ }
572+ finally
573+ {
574+ if ( clipboardOpened )
575+ {
576+ _ = NativeMethods . CloseClipboard ( ) ;
577+ }
578+
579+ if ( memory != IntPtr . Zero )
580+ {
581+ _ = NativeMethods . GlobalFree ( memory ) ;
582+ }
583+ }
584+ }
585+
586+ private static class NativeMethods
587+ {
588+ public const uint CfUnicodeText = 13 ;
589+ public const uint GmemMoveable = 0x0002 ;
590+
591+ [ DllImport ( "user32.dll" , SetLastError = true ) ]
592+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
593+ public static extern bool OpenClipboard ( IntPtr hWndNewOwner ) ;
594+
595+ [ DllImport ( "user32.dll" , SetLastError = true ) ]
596+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
597+ public static extern bool EmptyClipboard ( ) ;
598+
599+ [ DllImport ( "user32.dll" , SetLastError = true ) ]
600+ public static extern IntPtr SetClipboardData ( uint uFormat , IntPtr hMem ) ;
601+
602+ [ DllImport ( "user32.dll" , SetLastError = true ) ]
603+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
604+ public static extern bool CloseClipboard ( ) ;
605+
606+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
607+ public static extern IntPtr GlobalAlloc ( uint uFlags , UIntPtr dwBytes ) ;
608+
609+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
610+ public static extern IntPtr GlobalLock ( IntPtr hMem ) ;
611+
612+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
613+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
614+ public static extern bool GlobalUnlock ( IntPtr hMem ) ;
615+
616+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
617+ public static extern IntPtr GlobalFree ( IntPtr hMem ) ;
618+ }
619+
415620 private async void DecodeBankMenuItem_Click ( object sender , RoutedEventArgs e )
416621 {
417622 List < ExplorerItem > selectedItems = GetSelectedExplorerItems ( ) ;
@@ -837,7 +1042,7 @@ private async Task ShowBankAudioPreviewAsync(ExplorerItem item)
8371042
8381043 if ( compressed )
8391044 {
840- if ( ! BankLzmaAloneDecoder . TryGetDecodedByteCount ( header , out long decodedBytes ) ||
1045+ if ( ! LzmaAloneDecoder . TryGetDecodedByteCount ( header , out long decodedBytes ) ||
8411046 decodedBytes <= 0 ||
8421047 decodedBytes > FmodBankDecoder . MaxDecodedBytes ||
8431048 decodedBytes > int . MaxValue )
@@ -846,7 +1051,7 @@ private async Task ShowBankAudioPreviewAsync(ExplorerItem item)
8461051 return null ;
8471052 }
8481053
849- Stream ? decodedStream = BankLzmaAloneDecoder . TryCreateDecompressStream ( sourceStream , fileByteCount , out long streamDecodedBytes ) ;
1054+ Stream ? decodedStream = LzmaAloneDecoder . TryCreateDecompressStream ( sourceStream , fileByteCount , out long streamDecodedBytes ) ;
8501055 if ( decodedStream is null || streamDecodedBytes != decodedBytes )
8511056 {
8521057 decodedStream ? . Dispose ( ) ;
@@ -1969,6 +2174,12 @@ ExplorerItem audioItem when IsAudioPreviewCandidate(audioItem) => T("OpenAudioPr
19692174 ExtractSelectedMenuItem . Header = selectedItems . Count == 1
19702175 ? T ( "ExtractThisItem" )
19712176 : FormatText ( "ExtractSelectedItems" , selectedItems . Count ) ;
2177+ LocateFileMenuItem . IsEnabled = ! _isBusy && selectedItems . Count == 1 && selectedItems [ 0 ] . Parent is not null ;
2178+ LocateFileMenuItem . Header = T ( "LocateFile" ) ;
2179+ CopyNameMenuItem . IsEnabled = selectedItems . Count > 0 ;
2180+ CopyNameMenuItem . Header = selectedItems . Count <= 1
2181+ ? T ( "CopyName" )
2182+ : FormatText ( "CopySelectedNames" , selectedItems . Count ) ;
19722183 }
19732184
19742185 private void LanguageComboBox_SelectionChanged ( object sender , SelectionChangedEventArgs e )
@@ -2377,6 +2588,8 @@ private void ApplyLanguage()
23772588 EmptyStateText . Text = T ( "EmptyFolder" ) ;
23782589 OpenPreviewMenuItem . Header = T ( "OpenPreview" ) ;
23792590 DecodeBankMenuItem . Header = T ( "DecodeBank" ) ;
2591+ LocateFileMenuItem . Header = T ( "LocateFile" ) ;
2592+ CopyNameMenuItem . Header = T ( "CopyName" ) ;
23802593 ExtractSelectedMenuItem . Header = T ( "ExtractSelectedDefault" ) ;
23812594 SearchLabelText . Text = T ( "SearchLabel" ) ;
23822595 SearchTextBox . ToolTip = T ( "SearchTooltip" ) ;
0 commit comments