Skip to content

Commit 42ed4de

Browse files
committed
Release v1.1.2
1 parent 7326596 commit 42ed4de

8 files changed

Lines changed: 270 additions & 17 deletions

File tree

.github/workflows/release.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ jobs:
6565
- 缓存已生成缩略图,重复浏览未变化资源时可以跳过重复解码和渲染。
6666
- 支持中文和英文界面切换,并记住语言、目录和视图大小。
6767
68+
### v1.1.2 更新
69+
70+
- 右键菜单新增 `定位到文件`,可从搜索结果或当前列表跳转到选中项所在目录并聚焦目标文件。
71+
- 右键菜单新增 `复制名称`,支持复制单个选中项名称,或将多个选中项名称按行复制到剪贴板,并增加剪贴板繁忙重试提示。
72+
- BANK 预览和压缩资源前缀读取改为复用共享 `LzmaAloneDecoder`,统一普通资源与 BANK 的 LZMA 解码、前缀解码和流式解码路径。
73+
- 更新中英文说明书和 GitHub Release 文案,版本号提升到 `v1.1.2`。
74+
6875
### v1.1.1 更新
6976
7077
- 优化大型 FMOD `.bank` 预览:首条流准备好后即可打开播放器,剩余 FSB5 音频流会在后台递进追加。
@@ -106,6 +113,13 @@ jobs:
106113
- Cache generated thumbnails so unchanged resources can skip repeat decoding and rendering.
107114
- Switch between Chinese and English UI text, with remembered language, folders, and view size.
108115
116+
### v1.1.2 Changes
117+
118+
- Added a right-click `Locate File` command that jumps from search results or the current list to the selected item's containing folder and focuses the target file.
119+
- Added a right-click `Copy Name` command for copying one selected item name or multiple selected item names as separate lines, with retry feedback when the clipboard is busy.
120+
- Switched BANK previews and compressed-resource prefix reads to the shared `LzmaAloneDecoder`, unifying normal resource and BANK LZMA decode, prefix decode, and streaming decode paths.
121+
- Updated Chinese and English documentation plus GitHub Release notes, and bumped the application version to `v1.1.2`.
122+
109123
### v1.1.1 Changes
110124
111125
- Optimized large FMOD `.bank` previews: the player can open after the first stream is ready, while the remaining FSB5 streams are appended progressively in the background.

CFRezManager.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
<UseWPF>true</UseWPF>
99
<UseWindowsForms>true</UseWindowsForms>
1010
<ApplicationIcon>assets\app-icon.ico</ApplicationIcon>
11-
<Version>1.1.1</Version>
12-
<AssemblyVersion>1.1.1.0</AssemblyVersion>
13-
<FileVersion>1.1.1.0</FileVersion>
11+
<Version>1.1.2</Version>
12+
<AssemblyVersion>1.1.2.0</AssemblyVersion>
13+
<FileVersion>1.1.2.0</FileVersion>
1414
</PropertyGroup>
1515

1616
<ItemGroup>

ExplorerItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,7 @@ ArchiveFile is null ||
13431343
}
13441344

13451345
using FileStream source = File.OpenRead(SourcePath);
1346-
return BankLzmaAloneDecoder.TryDecompressPrefix(source, info.Length, maxDecodedBytes);
1346+
return LzmaAloneDecoder.TryDecompressPrefix(source, info.Length, maxDecodedBytes);
13471347
}
13481348

13491349
if (Archive is null ||
@@ -1356,7 +1356,7 @@ ArchiveFile is null ||
13561356

13571357
using FileStream archiveSource = File.OpenRead(Archive.FilePath);
13581358
archiveSource.Position = ArchiveFile.DataOffset;
1359-
return BankLzmaAloneDecoder.TryDecompressPrefix(archiveSource, ArchiveFile.Size, maxDecodedBytes);
1359+
return LzmaAloneDecoder.TryDecompressPrefix(archiveSource, ArchiveFile.Size, maxDecodedBytes);
13601360
}
13611361

13621362
private static ImageSource LoadBitmapImage(byte[] data, bool decodeThumbnail)

FmodBankDecoder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static FmodBankExportResult ExportDecodedFiles(byte[] data, string fileNa
124124

125125
public static bool IsCompressedBank(byte[] data)
126126
{
127-
return BankLzmaAloneDecoder.IsCompressed(data);
127+
return LzmaAloneDecoder.IsCompressed(data);
128128
}
129129

130130
public static bool TryPrepareDecodedData(
@@ -136,7 +136,7 @@ public static bool TryPrepareDecodedData(
136136
{
137137
bankData = null;
138138
errorMessage = null;
139-
compressed = BankLzmaAloneDecoder.IsCompressed(data);
139+
compressed = LzmaAloneDecoder.IsCompressed(data);
140140
decodedBytes = data.Length;
141141

142142
if (!compressed)
@@ -145,7 +145,7 @@ public static bool TryPrepareDecodedData(
145145
return true;
146146
}
147147

148-
if (!BankLzmaAloneDecoder.TryGetDecodedByteCount(data, out decodedBytes) ||
148+
if (!LzmaAloneDecoder.TryGetDecodedByteCount(data, out decodedBytes) ||
149149
decodedBytes <= 0 ||
150150
decodedBytes > MaxDecodedBytes ||
151151
decodedBytes > int.MaxValue)
@@ -154,7 +154,7 @@ public static bool TryPrepareDecodedData(
154154
return false;
155155
}
156156

157-
bankData = BankLzmaAloneDecoder.TryPrepareData(data, MaxDecodedBytes);
157+
bankData = LzmaAloneDecoder.TryPrepareData(data, MaxDecodedBytes);
158158
if (bankData is null)
159159
{
160160
errorMessage = "BANK compression could not be decoded.";

MainWindow.xaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@
424424
Header="Decode BANK..."
425425
Click="DecodeBankMenuItem_Click"/>
426426
<Separator x:Name="PreviewMenuSeparator"/>
427+
<MenuItem x:Name="LocateFileMenuItem"
428+
Header="定位到文件"
429+
Click="LocateFileMenuItem_Click"/>
430+
<MenuItem x:Name="CopyNameMenuItem"
431+
Header="Copy Name"
432+
Click="CopyNameMenuItem_Click"/>
427433
<MenuItem x:Name="ExtractSelectedMenuItem"
428434
Header="导出选中项..."
429435
Click="ExtractSelectedMenuItem_Click"/>

MainWindow.xaml.cs

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.IO;
22
using System.Diagnostics;
33
using System.Globalization;
4+
using System.Runtime.InteropServices;
45
using System.Threading;
56
using System.Windows;
67
using 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

Comments
 (0)