From b553a802c68eced506fd18f8e374d9dd97d95040 Mon Sep 17 00:00:00 2001 From: ema Date: Thu, 15 Jan 2026 03:47:07 +0800 Subject: [PATCH] Add PAK file support to ArchiveViewer plugin Introduced PakInfoPanel for displaying PAK archive contents, including new XAML and code-behind files. Updated Plugin.cs to use PakInfoPanel when opening .pak files, enabling preview and basic information display for Chromium resource packages. --- .../ChromiumResourcePackage/PakInfoPanel.xaml | 72 ++++++++++ .../PakInfoPanel.xaml.cs | 136 ++++++++++++++++++ .../QuickLook.Plugin.ArchiveViewer/Plugin.cs | 5 +- 3 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml create mode 100644 QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml.cs diff --git a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml new file mode 100644 index 0000000..64546c3 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml.cs new file mode 100644 index 0000000..798c802 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/ChromiumResourcePackage/PakInfoPanel.xaml.cs @@ -0,0 +1,136 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Common.ExtensionMethods; +using QuickLook.Common.Helpers; +using QuickLook.Plugin.ArchiveViewer.ArchiveFile; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace QuickLook.Plugin.ArchiveViewer.ChromiumResourcePackage; + +public partial class PakInfoPanel : UserControl, IDisposable, INotifyPropertyChanged +{ + private readonly Dictionary _fileEntries = []; + private bool _disposed; + private double _loadPercent; + private ulong _totalSize; + + public PakInfoPanel(string path) + { + InitializeComponent(); + Resources.MergedDictionaries.Clear(); + BeginLoadPak(path); + } + + public double LoadPercent + { + get => _loadPercent; + private set + { + if (value == _loadPercent) return; + _loadPercent = value; + OnPropertyChanged(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _disposed = true; + fileListView?.Dispose(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void BeginLoadPak(string path) + { + new Task(() => + { + _totalSize = (ulong)new FileInfo(path).Length; + var root = new ArchiveFileEntry(Path.GetFileName(path), true); + _fileEntries.Add(string.Empty, root); + try + { + LoadItemsFromPak(path); + } + catch (Exception e) + { + ProcessHelper.WriteLog(e.ToString()); + Dispatcher.Invoke(() => { lblLoading.Content = "Preview failed. See log for more details."; }); + return; + } + + var folders = -1; // do not count root node + var files = 0; + ulong sizeU = 0L; + foreach (var item in _fileEntries) + { + if (item.Value.IsFolder) + folders++; + else + files++; + sizeU += item.Value.Size; + } + + string t; + var d = folders != 0 ? $"{folders} folders" : string.Empty; + var f = files != 0 ? $"{files} files" : string.Empty; + if (!string.IsNullOrEmpty(d) && !string.IsNullOrEmpty(f)) + t = $", {d} folders and {f} files"; + else if (string.IsNullOrEmpty(d) && string.IsNullOrEmpty(f)) + t = string.Empty; + else + t = $", {d}{f}"; + + Dispatcher.Invoke(() => + { + if (_disposed) + return; + fileListView?.SetDataContext(_fileEntries[string.Empty].Children.Keys); + archiveCount.Content = $"PAK File{t}"; + archiveSizeC.Content = string.Empty; + archiveSizeU.Content = $"Total resource size {((long)sizeU).ToPrettySize(2)}"; + }); + LoadPercent = 100d; + }).Start(); + } + + private void LoadItemsFromPak(string path) + { + var dict = PakExtractor.ExtractToDictionary(path, true); + foreach (var kv in dict) + { + _fileEntries.TryGetValue(string.Empty, out var parent); + var entry = new ArchiveFileEntry(kv.Key, false, parent) + { + Size = (ulong)kv.Value.Length + }; + _fileEntries.Add(kv.Key, entry); + } + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs index 084ea7b..50f53f5 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs @@ -92,10 +92,7 @@ public sealed partial class Plugin : IViewer, IMoreMenu } else if (path.EndsWith(".pak", StringComparison.OrdinalIgnoreCase)) { - var dict = PakExtractor.ExtractToDictionary(path); - - // TODO - _ = dict; + _panel = new PakInfoPanel(path); } else {