mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-12 10:19:07 +00:00
Support .apk and .apk.1
This commit is contained in:
244
QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkInfoPanel.xaml
Normal file
244
QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkInfoPanel.xaml
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<UserControl x:Class="QuickLook.Plugin.AppViewer.ApkInfoPanel"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:QuickLook.Plugin.AppViewer"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
FontSize="14"
|
||||||
|
UseLayoutRounding="True"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="15" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image x:Name="image"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Width="120"
|
||||||
|
Height="120"
|
||||||
|
Margin="8"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Opacity="0"
|
||||||
|
Stretch="Fill">
|
||||||
|
<Image.Style>
|
||||||
|
<Style TargetType="{x:Type Image}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Source, ElementName=image}" Value="{x:Null}">
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation BeginTime="0:0:0"
|
||||||
|
Storyboard.TargetProperty="Opacity"
|
||||||
|
From="0"
|
||||||
|
To="1"
|
||||||
|
Duration="0:0:0.05" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Image.Style>
|
||||||
|
</Image>
|
||||||
|
<Grid Grid.Row="1" Grid.Column="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="5" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="10" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="10" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="10" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="* " />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
MaxHeight="60"
|
||||||
|
Padding="3"
|
||||||
|
FontSize="19"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
LineHeight="25"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run x:Name="filename" Text="FilenameFilenameFilenameFilenameFilenameFilenameFilenameFilenameFilenameFilename.ext" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<!-- Application Name -->
|
||||||
|
<TextBlock x:Name="applicationNameTitle"
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Application Name" />
|
||||||
|
<TextBlock x:Name="applicationName"
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Version Name -->
|
||||||
|
<TextBlock x:Name="versionNameTitle"
|
||||||
|
Grid.Row="4"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Version" />
|
||||||
|
<TextBlock x:Name="versionName"
|
||||||
|
Grid.Row="4"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Version Code -->
|
||||||
|
<TextBlock x:Name="versionCodeTitle"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Version Code" />
|
||||||
|
<TextBlock x:Name="versionCode"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Package Name -->
|
||||||
|
<TextBlock x:Name="packageNameTitle"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Package Name" />
|
||||||
|
<TextBlock x:Name="packageName"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- MinSdkVersion -->
|
||||||
|
<TextBlock x:Name="minSdkVersionTitle"
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="MinSdkVersion" />
|
||||||
|
<TextBlock x:Name="minSdkVersion"
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- TargetSdkVersion -->
|
||||||
|
<TextBlock x:Name="targetSdkVersionTitle"
|
||||||
|
Grid.Row="8"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="TargetSdkVersion" />
|
||||||
|
<TextBlock x:Name="targetSdkVersion"
|
||||||
|
Grid.Row="8"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Total Size -->
|
||||||
|
<TextBlock x:Name="totalSizeTitle"
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Total Size" />
|
||||||
|
<TextBlock x:Name="totalSize"
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Calculating size..." />
|
||||||
|
<!-- Last Modified -->
|
||||||
|
<TextBlock x:Name="modDateTitle"
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Last Modified" />
|
||||||
|
<TextBlock x:Name="modDate"
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<!-- Permissions -->
|
||||||
|
<GroupBox x:Name="permissionsGroupBox"
|
||||||
|
Grid.Row="11"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,0,16,16"
|
||||||
|
BorderThickness="0.4"
|
||||||
|
Header="Capabilities">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<ScrollViewer.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBox}">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</ScrollViewer.Resources>
|
||||||
|
<ItemsControl x:Name="permissions">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Margin="8,3,16,3"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding ., Mode=OneTime}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using QuickLook.Common.ExtensionMethods;
|
||||||
|
using QuickLook.Common.Helpers;
|
||||||
|
using QuickLook.Common.Plugin;
|
||||||
|
using QuickLook.Plugin.AppViewer.ApkPackageParser;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer;
|
||||||
|
|
||||||
|
public partial class ApkInfoPanel : UserControl, IAppInfoPanel
|
||||||
|
{
|
||||||
|
private ContextObject _context;
|
||||||
|
|
||||||
|
public ApkInfoPanel(ContextObject context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
DataContext = this;
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
|
||||||
|
applicationNameTitle.Text = TranslationHelper.Get("APP_NAME", translationFile);
|
||||||
|
versionNameTitle.Text = TranslationHelper.Get("APP_VERSION_NAME", translationFile);
|
||||||
|
versionCodeTitle.Text = TranslationHelper.Get("APP_VERSION_CODE", translationFile);
|
||||||
|
packageNameTitle.Text = TranslationHelper.Get("PACKAGE_NAME", translationFile);
|
||||||
|
minSdkVersionTitle.Text = TranslationHelper.Get("APP_MIN_SDK_VERSION", translationFile);
|
||||||
|
targetSdkVersionTitle.Text = TranslationHelper.Get("APP_TARGET_SDK_VERSION", translationFile);
|
||||||
|
totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile);
|
||||||
|
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
||||||
|
permissionsGroupBox.Header = TranslationHelper.Get("PERMISSIONS", translationFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayInfo(string path)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(path);
|
||||||
|
filename.Text = string.IsNullOrEmpty(name) ? path : name;
|
||||||
|
|
||||||
|
_ = Task.Run(() =>
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var size = new FileInfo(path).Length;
|
||||||
|
ApkInfo apkInfo = ApkParser.Parse(path);
|
||||||
|
var last = File.GetLastWriteTime(path);
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
applicationName.Text = apkInfo.Label;
|
||||||
|
versionName.Text = apkInfo.VersionName;
|
||||||
|
versionCode.Text = apkInfo.VersionCode;
|
||||||
|
packageName.Text = apkInfo.PackageName;
|
||||||
|
minSdkVersion.Text = apkInfo.MinSdkVersion;
|
||||||
|
targetSdkVersion.Text = apkInfo.TargetSdkVersion;
|
||||||
|
totalSize.Text = size.ToPrettySize(2);
|
||||||
|
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
||||||
|
permissions.ItemsSource = apkInfo.Permissions;
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(apkInfo.Logo);
|
||||||
|
var icon = new BitmapImage();
|
||||||
|
icon.BeginInit();
|
||||||
|
icon.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
icon.StreamSource = stream;
|
||||||
|
icon.EndInit();
|
||||||
|
icon.Freeze();
|
||||||
|
image.Source = icon;
|
||||||
|
|
||||||
|
_context.IsBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://github.com/hylander0/Iteedee.ApkReader
|
||||||
|
/// </summary>
|
||||||
|
public class ApkInfo
|
||||||
|
{
|
||||||
|
public string Label { get; set; }
|
||||||
|
|
||||||
|
public string VersionName { get; set; }
|
||||||
|
|
||||||
|
public string VersionCode { get; set; }
|
||||||
|
|
||||||
|
public string MinSdkVersion { get; set; }
|
||||||
|
|
||||||
|
public string TargetSdkVersion { get; set; }
|
||||||
|
|
||||||
|
public string PackageName { get; set; }
|
||||||
|
|
||||||
|
public string Debuggable { get; set; }
|
||||||
|
|
||||||
|
public List<string> Permissions { get; set; } = [];
|
||||||
|
|
||||||
|
public List<string> IconFileName { get; set; }
|
||||||
|
|
||||||
|
public byte[] Logo { get; set; }
|
||||||
|
|
||||||
|
public bool HasIcon { get; set; } = false;
|
||||||
|
|
||||||
|
public bool SupportSmallScreens { get; set; } = false;
|
||||||
|
|
||||||
|
public bool SupportNormalScreens { get; set; } = false;
|
||||||
|
|
||||||
|
public bool SupportLargeScreens { get; set; } = false;
|
||||||
|
|
||||||
|
public bool SupportAnyDensity { get; set; } = true;
|
||||||
|
|
||||||
|
public Dictionary<string, List<string>> ResStrings { get; set; }
|
||||||
|
|
||||||
|
public bool IsDebuggable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Debuggable == null) return false; // debugabble is not in the manifest
|
||||||
|
if (Debuggable.Equals("-1")) return true; // Debuggable == true
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,270 @@
|
|||||||
|
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://github.com/hylander0/Iteedee.ApkReader
|
||||||
|
/// </summary>
|
||||||
|
public class ApkManifest
|
||||||
|
{
|
||||||
|
private string result = string.Empty;
|
||||||
|
|
||||||
|
private bool isUtf8;
|
||||||
|
|
||||||
|
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
|
||||||
|
// such as for AndroidManifest.xml in .apk files
|
||||||
|
private const int startDocTag = 0x00100100;
|
||||||
|
|
||||||
|
private const int endDocTag = 0x00100101;
|
||||||
|
private const int startTag = 0x00100102;
|
||||||
|
private const int endTag = 0x00100103;
|
||||||
|
private const int textTag = 0x00100104;
|
||||||
|
|
||||||
|
public string ReadManifestFileIntoXml(byte[] manifestFileData)
|
||||||
|
{
|
||||||
|
if (manifestFileData.Length == 0)
|
||||||
|
throw new Exception("Failed to read manifest data. Byte array was empty");
|
||||||
|
// Compressed XML file/bytes starts with 24x bytes of data,
|
||||||
|
// 9 32 bit words in little endian order (LSB first):
|
||||||
|
// 0th word is 03 00 08 00
|
||||||
|
// 3rd word SEEMS TO BE: Offset at then of StringTable
|
||||||
|
// 4th word is: Number of strings in string table
|
||||||
|
// WARNING: Sometime I indiscriminently display or refer to word in
|
||||||
|
// little endian storage format, or in integer format (ie MSB first).
|
||||||
|
int numbStrings = LEW(manifestFileData, 4 * 4);
|
||||||
|
|
||||||
|
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
|
||||||
|
// of the length/string data in the StringTable.
|
||||||
|
int sitOff = 0x24; // Offset of start of StringIndexTable
|
||||||
|
|
||||||
|
// StringTable, each string is represented with a 16 bit little endian
|
||||||
|
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
|
||||||
|
int stOff = sitOff + numbStrings * 4; // StringTable follows StrIndexTable
|
||||||
|
|
||||||
|
// XMLTags, The XML tag tree starts after some unknown content after the
|
||||||
|
// StringTable. There is some unknown data after the StringTable, scan
|
||||||
|
// forward from this point to the flag for the start of an XML start tag.
|
||||||
|
int xmlTagOff = LEW(manifestFileData, 3 * 4); // Start from the offset in the 3rd word.
|
||||||
|
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
|
||||||
|
|
||||||
|
// String pool is encoded in UTF-8
|
||||||
|
// https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/include/androidfw/ResourceTypes.h#451
|
||||||
|
int flag = LEW(manifestFileData, 4 * 6);
|
||||||
|
this.isUtf8 = (flag & (1 << 8)) > 0;
|
||||||
|
|
||||||
|
for (int ii = xmlTagOff; ii < manifestFileData.Length - 4; ii += 4)
|
||||||
|
{
|
||||||
|
if (LEW(manifestFileData, ii) == startTag)
|
||||||
|
{
|
||||||
|
xmlTagOff = ii; break;
|
||||||
|
}
|
||||||
|
} // end of hack, scanning for start of first start tag
|
||||||
|
|
||||||
|
// XML tags and attributes:
|
||||||
|
// Every XML start and end tag consists of 6 32 bit words:
|
||||||
|
// 0th word: 02011000 for startTag and 03011000 for endTag
|
||||||
|
// 1st word: a flag?, like 38000000
|
||||||
|
// 2nd word: Line of where this tag appeared in the original source file
|
||||||
|
// 3rd word: FFFFFFFF ??
|
||||||
|
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
|
||||||
|
// 5th word: StringIndex of Element Name
|
||||||
|
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
|
||||||
|
|
||||||
|
// Start tags (not end tags) contain 3 more words:
|
||||||
|
// 6th word: 14001400 meaning??
|
||||||
|
// 7th word: Number of Attributes that follow this tag(follow word 8th)
|
||||||
|
// 8th word: 00000000 meaning??
|
||||||
|
|
||||||
|
// Attributes consist of 5 words:
|
||||||
|
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
|
||||||
|
// 1st word: StringIndex of Attribute Name
|
||||||
|
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
|
||||||
|
// 3rd word: Flags?
|
||||||
|
// 4th word: str ind of attr value again, or ResourceId of value
|
||||||
|
|
||||||
|
// TMP, dump string table to tr for debugging
|
||||||
|
//tr.addSelect("strings", null);
|
||||||
|
//for (int ii=0; ii<numbStrings; ii++) {
|
||||||
|
// // Length of string starts at StringTable plus offset in StrIndTable
|
||||||
|
// String str = CompXmlString(xml, sitOff, stOff, ii);
|
||||||
|
// tr.add(String.valueOf(ii), str);
|
||||||
|
//}
|
||||||
|
//tr.parent();
|
||||||
|
|
||||||
|
// Step through the XML tree element tags and attributes
|
||||||
|
int off = xmlTagOff;
|
||||||
|
int indent = 0;
|
||||||
|
int startTagLineNo = -2;
|
||||||
|
int startDocTagCounter = 1;
|
||||||
|
while (off < manifestFileData.Length)
|
||||||
|
{
|
||||||
|
int tag0 = LEW(manifestFileData, off);
|
||||||
|
//int tag1 = LEW(manifestFileData, off+1*4);
|
||||||
|
int lineNo = LEW(manifestFileData, off + 2 * 4);
|
||||||
|
//int tag3 = LEW(manifestFileData, off+3*4);
|
||||||
|
int nameNsSi = LEW(manifestFileData, off + 4 * 4);
|
||||||
|
int nameSi = LEW(manifestFileData, off + 5 * 4);
|
||||||
|
|
||||||
|
if (tag0 == startTag)
|
||||||
|
{ // XML START TAG
|
||||||
|
int tag6 = LEW(manifestFileData, off + 6 * 4); // Expected to be 14001400
|
||||||
|
int numbAttrs = LEW(manifestFileData, off + 7 * 4); // Number of Attributes to follow
|
||||||
|
//int tag8 = LEW(manifestFileData, off+8*4); // Expected to be 00000000
|
||||||
|
off += 9 * 4; // Skip over 6+3 words of startTag data
|
||||||
|
string name = CompXmlString(manifestFileData, sitOff, stOff, nameSi);
|
||||||
|
//tr.addSelect(name, null);
|
||||||
|
startTagLineNo = lineNo;
|
||||||
|
|
||||||
|
// Look for the Attributes
|
||||||
|
|
||||||
|
string sb = string.Empty;
|
||||||
|
for (int ii = 0; ii < numbAttrs; ii++)
|
||||||
|
{
|
||||||
|
int attrNameNsSi = LEW(manifestFileData, off); // AttrName Namespace Str Ind, or FFFFFFFF
|
||||||
|
int attrNameSi = LEW(manifestFileData, off + 1 * 4); // AttrName String Index
|
||||||
|
int attrValueSi = LEW(manifestFileData, off + 2 * 4); // AttrValue Str Ind, or FFFFFFFF
|
||||||
|
int attrFlags = LEW(manifestFileData, off + 3 * 4);
|
||||||
|
int attrResId = LEW(manifestFileData, off + 4 * 4); // AttrValue ResourceId or dup AttrValue StrInd
|
||||||
|
off += 5 * 4; // Skip over the 5 words of an attribute
|
||||||
|
|
||||||
|
string attrName = CompXmlString(manifestFileData, sitOff, stOff, attrNameSi);
|
||||||
|
string attrValue = attrValueSi != -1
|
||||||
|
? CompXmlString(manifestFileData, sitOff, stOff, attrValueSi)
|
||||||
|
: /*"resourceID 0x" + */attrResId.ToString();
|
||||||
|
sb += " " + attrName + "=\"" + attrValue + "\"";
|
||||||
|
//tr.add(attrName, attrValue);
|
||||||
|
}
|
||||||
|
PrtIndent(indent, "<" + name + sb + ">");
|
||||||
|
indent++;
|
||||||
|
}
|
||||||
|
else if (tag0 == endTag)
|
||||||
|
{
|
||||||
|
// XML END TAG
|
||||||
|
indent--;
|
||||||
|
off += 6 * 4; // Skip over 6 words of endTag data
|
||||||
|
string name = CompXmlString(manifestFileData, sitOff, stOff, nameSi);
|
||||||
|
PrtIndent(indent, "</" + name + "> \r\n"/*+"(line " + startTagLineNo + "-" + lineNo + ")"*/);
|
||||||
|
//tr.parent(); // Step back up the NobTree
|
||||||
|
}
|
||||||
|
else if (tag0 == startDocTag)
|
||||||
|
{
|
||||||
|
startDocTagCounter++;
|
||||||
|
off += 4;
|
||||||
|
}
|
||||||
|
else if (tag0 == endDocTag)
|
||||||
|
{
|
||||||
|
// END OF XML DOC TAG
|
||||||
|
startDocTagCounter--;
|
||||||
|
if (startDocTagCounter == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (tag0 == textTag)
|
||||||
|
{
|
||||||
|
// code "copied" https://github.com/mikandi/php-apk-parser/blob/fixed-mikandi-versionName/lib/ApkParser/XmlParser.php
|
||||||
|
uint sentinal = 0xffffffff;
|
||||||
|
while (off < manifestFileData.Length)
|
||||||
|
{
|
||||||
|
uint curr = (uint)LEW(manifestFileData, off);
|
||||||
|
off += 4;
|
||||||
|
if (off > manifestFileData.Length)
|
||||||
|
{
|
||||||
|
throw new Exception("Sentinal not found before end of file");
|
||||||
|
}
|
||||||
|
if (curr == sentinal && sentinal == 0xffffffff)
|
||||||
|
{
|
||||||
|
sentinal = 0x00000000;
|
||||||
|
}
|
||||||
|
else if (curr == sentinal)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Prt(" Unrecognized tag code '" + tag0.ToString("X")
|
||||||
|
+ "' at offset " + off);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // end of while loop scanning tags and attributes of XML tree
|
||||||
|
//Prt(" end at offset " + off);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} // end of decompressXML
|
||||||
|
|
||||||
|
public string CompXmlString(byte[] xml, int sitOff, int stOff, int strInd)
|
||||||
|
{
|
||||||
|
if (strInd < 0) return null;
|
||||||
|
int strOff = stOff + LEW(xml, sitOff + strInd * 4);
|
||||||
|
return CompXmlStringAt(xml, strOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string spaces = " ";
|
||||||
|
|
||||||
|
public void PrtIndent(int indent, string str)
|
||||||
|
{
|
||||||
|
Prt(spaces.Substring(0, Math.Min(indent * 2, spaces.Length)) + str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Prt(string p)
|
||||||
|
{
|
||||||
|
result += p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompXmlStringAt -- Return the string stored in StringTable format at
|
||||||
|
// offset strOff. This offset points to the 16 bit string length, which
|
||||||
|
// is followed by that number of 16 bit (Unicode) chars.
|
||||||
|
public string CompXmlStringAt(byte[] arr, int strOff)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Strings in UTF-8 format have length indicated by a length encoded in the
|
||||||
|
* stored data. It is either 1 or 2 characters of length data. This allows a
|
||||||
|
* maximum length of 0x7FFF (32767 bytes), but you should consider storing
|
||||||
|
* text in another way if you're using that much data in a single string.
|
||||||
|
*
|
||||||
|
* If the high bit is set, then there are two characters or 2 bytes of length
|
||||||
|
* data encoded. In that case, drop the high bit of the first character and
|
||||||
|
* add it together with the next character.
|
||||||
|
* https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/ResourceTypes.cpp#674
|
||||||
|
*/
|
||||||
|
int strLen = arr[strOff];
|
||||||
|
if ((strLen & 0x80) != 0)
|
||||||
|
strLen = ((strLen & 0x7f) << 8) + arr[strOff + 1];
|
||||||
|
|
||||||
|
if (!isUtf8)
|
||||||
|
strLen *= 2;
|
||||||
|
|
||||||
|
byte[] chars = new byte[strLen];
|
||||||
|
for (int ii = 0; ii < strLen; ii++)
|
||||||
|
{
|
||||||
|
chars[ii] = arr[strOff + 2 + ii];
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.Text.Encoding.GetEncoding(isUtf8 ? "UTF-8" : "UTF-16").GetString(chars);
|
||||||
|
} // end of CompXmlStringAt
|
||||||
|
|
||||||
|
// LEW -- Return value of a Little Endian 32 bit word from the byte array
|
||||||
|
// at offset off.
|
||||||
|
public int LEW(byte[] arr, int off)
|
||||||
|
{
|
||||||
|
//return (int)(arr[off + 3] << 24 & 0xff000000 | arr[off + 2] << 16 & 0xff0000 | arr[off + 1] << 8 & 0xff00 | arr[off] & 0xFF);
|
||||||
|
return (int)(((uint)arr[off + 3]) << 24 & 0xff000000 | ((uint)arr[off + 2]) << 16 & 0xff0000 | ((uint)arr[off + 1]) << 8 & 0xff00 | ((uint)arr[off]) & 0xFF);
|
||||||
|
} // end of LEW
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
|
||||||
|
|
||||||
|
public static class ApkParser
|
||||||
|
{
|
||||||
|
public static ApkInfo Parse(string path)
|
||||||
|
{
|
||||||
|
byte[] manifestData = null;
|
||||||
|
byte[] resourcesData = null;
|
||||||
|
|
||||||
|
using var zip = new ZipFile(path);
|
||||||
|
|
||||||
|
// AndroidManifest.xml
|
||||||
|
{
|
||||||
|
ZipEntry entry = zip.GetEntry("AndroidManifest.xml");
|
||||||
|
using var s = new BinaryReader(zip.GetInputStream(entry));
|
||||||
|
manifestData = s.ReadBytes((int)entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resources.arsc
|
||||||
|
{
|
||||||
|
ZipEntry entry = zip.GetEntry("resources.arsc");
|
||||||
|
using var s = new BinaryReader(zip.GetInputStream(entry));
|
||||||
|
resourcesData = s.ReadBytes((int)entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApkReader apkReader = new();
|
||||||
|
ApkInfo info = apkReader.ExtractInfo(manifestData, resourcesData);
|
||||||
|
|
||||||
|
// Logo
|
||||||
|
if (info.HasIcon)
|
||||||
|
{
|
||||||
|
ZipEntry entry = zip.GetEntry(info.IconFileName.LastOrDefault());
|
||||||
|
using var s = new BinaryReader(zip.GetInputStream(entry));
|
||||||
|
info.Logo = s.ReadBytes((int)entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,280 @@
|
|||||||
|
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://github.com/hylander0/Iteedee.ApkReader
|
||||||
|
/// </summary>
|
||||||
|
public class ApkReader
|
||||||
|
{
|
||||||
|
private const int VER_ID = 0;
|
||||||
|
private const int ICN_ID = 1;
|
||||||
|
private const int LABEL_ID = 2;
|
||||||
|
private readonly string[] VER_ICN = new string[3];
|
||||||
|
|
||||||
|
// Some possible tags and attributes
|
||||||
|
private readonly string[] TAGS = ["manifest", "application", "activity"];
|
||||||
|
|
||||||
|
public string FuzzFindInDocument(XmlDocument doc, string tag, string attr)
|
||||||
|
{
|
||||||
|
foreach (string t in TAGS)
|
||||||
|
{
|
||||||
|
XmlNodeList nodelist = doc.GetElementsByTagName(t);
|
||||||
|
for (int i = 0; i < nodelist.Count; i++)
|
||||||
|
{
|
||||||
|
XmlNode element = nodelist.Item(i);
|
||||||
|
if (element.NodeType == XmlNodeType.Element)
|
||||||
|
{
|
||||||
|
XmlAttributeCollection map = element.Attributes;
|
||||||
|
for (int j = 0; j < map.Count; j++)
|
||||||
|
{
|
||||||
|
XmlNode element2 = map.Item(j);
|
||||||
|
if (element2.Name.EndsWith(attr))
|
||||||
|
{
|
||||||
|
return element2.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractPermissions(ApkInfo info, XmlDocument doc)
|
||||||
|
{
|
||||||
|
ExtractPermission(info, doc, "uses-permission", "name");
|
||||||
|
ExtractPermission(info, doc, "permission-group", "name");
|
||||||
|
ExtractPermission(info, doc, "service", "permission");
|
||||||
|
ExtractPermission(info, doc, "provider", "permission");
|
||||||
|
ExtractPermission(info, doc, "activity", "permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReadBoolean(XmlDocument doc, string tag, string attribute)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string str = FindInDocument(doc, tag, attribute);
|
||||||
|
return Convert.ToBoolean(str);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractSupportScreens(ApkInfo info, XmlDocument doc)
|
||||||
|
{
|
||||||
|
info.SupportSmallScreens = ReadBoolean(doc, "supports-screens", "android:smallScreens");
|
||||||
|
info.SupportNormalScreens = ReadBoolean(doc, "supports-screens", "android:normalScreens");
|
||||||
|
info.SupportLargeScreens = ReadBoolean(doc, "supports-screens", "android:largeScreens");
|
||||||
|
|
||||||
|
if (info.SupportSmallScreens || info.SupportNormalScreens || info.SupportLargeScreens)
|
||||||
|
info.SupportAnyDensity = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApkInfo ExtractInfo(byte[] manifest_xml, byte[] resources_arsx)
|
||||||
|
{
|
||||||
|
string manifestXml;
|
||||||
|
ApkManifest manifest = new();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
manifestXml = manifest.ReadManifestFileIntoXml(manifest_xml);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
XmlDocument doc = new();
|
||||||
|
doc.LoadXml(manifestXml);
|
||||||
|
return ExtractInfo(doc, resources_arsx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApkInfo ExtractInfo(XmlDocument manifestXml, byte[] resources_arsx)
|
||||||
|
{
|
||||||
|
ApkInfo info = new();
|
||||||
|
VER_ICN[VER_ID] = string.Empty;
|
||||||
|
VER_ICN[ICN_ID] = string.Empty;
|
||||||
|
VER_ICN[LABEL_ID] = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XmlDocument doc = manifestXml ?? throw new Exception("Document initialize failed");
|
||||||
|
|
||||||
|
// Fill up the permission field
|
||||||
|
ExtractPermissions(info, doc);
|
||||||
|
|
||||||
|
// Fill up some basic fields
|
||||||
|
info.MinSdkVersion = FindInDocument(doc, "uses-sdk", "minSdkVersion");
|
||||||
|
info.TargetSdkVersion = FindInDocument(doc, "uses-sdk", "targetSdkVersion");
|
||||||
|
info.VersionCode = FindInDocument(doc, "manifest", "versionCode");
|
||||||
|
info.VersionName = FindInDocument(doc, "manifest", "versionName");
|
||||||
|
info.PackageName = FindInDocument(doc, "manifest", "package");
|
||||||
|
|
||||||
|
info.Label = FindInDocument(doc, "application", "label");
|
||||||
|
if (info.Label.StartsWith("@"))
|
||||||
|
VER_ICN[LABEL_ID] = info.Label;
|
||||||
|
else if (int.TryParse(info.Label, out int labelID))
|
||||||
|
VER_ICN[LABEL_ID] = string.Format("@{0}", labelID.ToString("X4"));
|
||||||
|
|
||||||
|
// Get the value of android:Debuggable in the manifest
|
||||||
|
// "0" = false and "-1" = true
|
||||||
|
info.Debuggable = FindInDocument(doc, "application", "debuggable");
|
||||||
|
|
||||||
|
// Fill up the support screen field
|
||||||
|
ExtractSupportScreens(info, doc);
|
||||||
|
|
||||||
|
info.VersionCode ??= FuzzFindInDocument(doc, "manifest", "versionCode");
|
||||||
|
|
||||||
|
if (info.VersionName == null)
|
||||||
|
info.VersionName = FuzzFindInDocument(doc, "manifest", "versionName");
|
||||||
|
else if (info.VersionName.StartsWith("@"))
|
||||||
|
VER_ICN[VER_ID] = info.VersionName;
|
||||||
|
|
||||||
|
string id = FindInDocument(doc, "application", "android:icon");
|
||||||
|
if (null == id)
|
||||||
|
{
|
||||||
|
id = FuzzFindInDocument(doc, "manifest", "icon");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == id)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("icon resId Not Found!");
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find real strings
|
||||||
|
if (!info.HasIcon && id != null)
|
||||||
|
{
|
||||||
|
if (id.StartsWith("@android:"))
|
||||||
|
VER_ICN[ICN_ID] = "@" + id.Substring("@android:".Length);
|
||||||
|
else
|
||||||
|
VER_ICN[ICN_ID] = string.Format("@{0}", Convert.ToInt32(id).ToString("X4"));
|
||||||
|
|
||||||
|
List<string> resId = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < VER_ICN.Length; i++)
|
||||||
|
{
|
||||||
|
if (VER_ICN[i].StartsWith("@"))
|
||||||
|
resId.Add(VER_ICN[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApkResourceFinder finder = new();
|
||||||
|
info.ResStrings = finder.ProcessResourceTable(resources_arsx, resId);
|
||||||
|
|
||||||
|
if (!VER_ICN[VER_ID].Equals(string.Empty))
|
||||||
|
{
|
||||||
|
List<string> versions = null;
|
||||||
|
if (info.ResStrings.ContainsKey(VER_ICN[VER_ID].ToUpper()))
|
||||||
|
versions = info.ResStrings[VER_ICN[VER_ID].ToUpper()];
|
||||||
|
if (versions != null)
|
||||||
|
{
|
||||||
|
if (versions.Count > 0)
|
||||||
|
info.VersionName = versions[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("VersionName Cant Find in resource with id " + VER_ICN[VER_ID]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> iconPaths = null;
|
||||||
|
if (info.ResStrings.ContainsKey(VER_ICN[ICN_ID].ToUpper()))
|
||||||
|
iconPaths = info.ResStrings[VER_ICN[ICN_ID].ToUpper()];
|
||||||
|
if (iconPaths != null && iconPaths.Count > 0)
|
||||||
|
{
|
||||||
|
info.IconFileName = [];
|
||||||
|
foreach (string iconFileName in iconPaths)
|
||||||
|
{
|
||||||
|
if (iconFileName != null)
|
||||||
|
{
|
||||||
|
if (iconFileName.Contains(@"/"))
|
||||||
|
{
|
||||||
|
info.IconFileName.Add(iconFileName);
|
||||||
|
info.HasIcon = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Icon Cant Find in resource with id " + VER_ICN[ICN_ID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!VER_ICN[LABEL_ID].Equals(string.Empty))
|
||||||
|
{
|
||||||
|
List<string> labels = null;
|
||||||
|
if (info.ResStrings.ContainsKey(VER_ICN[LABEL_ID]))
|
||||||
|
labels = info.ResStrings[VER_ICN[LABEL_ID]];
|
||||||
|
if (labels.Count > 0)
|
||||||
|
{
|
||||||
|
info.Label = labels[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractPermission(ApkInfo info, XmlDocument doc, string keyName, string attribName)
|
||||||
|
{
|
||||||
|
XmlNodeList usesPermissions = doc.GetElementsByTagName(keyName);
|
||||||
|
|
||||||
|
if (usesPermissions != null)
|
||||||
|
{
|
||||||
|
for (int s = 0; s < usesPermissions.Count; s++)
|
||||||
|
{
|
||||||
|
XmlNode permissionNode = usesPermissions.Item(s);
|
||||||
|
if (permissionNode.NodeType == XmlNodeType.Element)
|
||||||
|
{
|
||||||
|
XmlNode node = permissionNode.Attributes.GetNamedItem(attribName);
|
||||||
|
if (node != null)
|
||||||
|
info.Permissions.Add(node.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FindInDocument(XmlDocument doc, string keyName, string attribName)
|
||||||
|
{
|
||||||
|
XmlNodeList usesPermissions = doc.GetElementsByTagName(keyName);
|
||||||
|
|
||||||
|
if (usesPermissions != null)
|
||||||
|
{
|
||||||
|
for (int s = 0; s < usesPermissions.Count; s++)
|
||||||
|
{
|
||||||
|
XmlNode permissionNode = usesPermissions.Item(s);
|
||||||
|
if (permissionNode.NodeType == XmlNodeType.Element)
|
||||||
|
{
|
||||||
|
XmlNode node = permissionNode.Attributes.GetNamedItem(attribName);
|
||||||
|
if (node != null)
|
||||||
|
return node.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,502 @@
|
|||||||
|
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://github.com/hylander0/Iteedee.ApkReader
|
||||||
|
/// </summary>
|
||||||
|
public class ApkResourceFinder
|
||||||
|
{
|
||||||
|
private const short RES_STRING_POOL_TYPE = 0x0001;
|
||||||
|
private const short RES_TABLE_TYPE = 0x0002;
|
||||||
|
private const short RES_TABLE_PACKAGE_TYPE = 0x0200;
|
||||||
|
private const short RES_TABLE_TYPE_TYPE = 0x0201;
|
||||||
|
private const short RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
|
||||||
|
|
||||||
|
private string[] valueStringPool = null;
|
||||||
|
private string[] typeStringPool = null;
|
||||||
|
private string[] keyStringPool = null;
|
||||||
|
|
||||||
|
private int package_id = 0;
|
||||||
|
private List<string> resIdList;
|
||||||
|
|
||||||
|
//// Contains no data.
|
||||||
|
//static byte TYPE_NULL = 0x00;
|
||||||
|
//// The 'data' holds an attribute resource identifier.
|
||||||
|
//static byte TYPE_ATTRIBUTE = 0x02;
|
||||||
|
//// The 'data' holds a single-precision floating point number.
|
||||||
|
//static byte TYPE_FLOAT = 0x04;
|
||||||
|
//// The 'data' holds a complex number encoding a dimension value,
|
||||||
|
//// such as "100in".
|
||||||
|
//static byte TYPE_DIMENSION = 0x05;
|
||||||
|
//// The 'data' holds a complex number encoding a fraction of a
|
||||||
|
//// container.
|
||||||
|
//static byte TYPE_FRACTION = 0x06;
|
||||||
|
//// The 'data' is a raw integer value of the form n..n.
|
||||||
|
//static byte TYPE_INT_DEC = 0x10;
|
||||||
|
//// The 'data' is a raw integer value of the form 0xn..n.
|
||||||
|
//static byte TYPE_INT_HEX = 0x11;
|
||||||
|
//// The 'data' is either 0 or 1, for input "false" or "true" respectively.
|
||||||
|
//static byte TYPE_INT_BOOLEAN = 0x12;
|
||||||
|
//// The 'data' is a raw integer value of the form #aarrggbb.
|
||||||
|
//static byte TYPE_INT_COLOR_ARGB8 = 0x1c;
|
||||||
|
//// The 'data' is a raw integer value of the form #rrggbb.
|
||||||
|
//static byte TYPE_INT_COLOR_RGB8 = 0x1d;
|
||||||
|
//// The 'data' is a raw integer value of the form #argb.
|
||||||
|
//static byte TYPE_INT_COLOR_ARGB4 = 0x1e;
|
||||||
|
//// The 'data' is a raw integer value of the form #rgb.
|
||||||
|
//static byte TYPE_INT_COLOR_RGB4 = 0x1f;
|
||||||
|
|
||||||
|
// The 'data' holds a ResTable_ref, a reference to another resource
|
||||||
|
// table entry.
|
||||||
|
private const byte TYPE_REFERENCE = 0x01;
|
||||||
|
|
||||||
|
// The 'data' holds an index into the containing resource table's
|
||||||
|
// global value string pool.
|
||||||
|
private const byte TYPE_STRING = 0x03;
|
||||||
|
|
||||||
|
private Dictionary<string, List<string>> responseMap;
|
||||||
|
|
||||||
|
private Dictionary<int, List<string>> entryMap = [];
|
||||||
|
|
||||||
|
public Dictionary<string, List<string>> Initialize()
|
||||||
|
{
|
||||||
|
byte[] data = File.ReadAllBytes("resources.arsc");
|
||||||
|
return ProcessResourceTable(data, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, List<string>> ProcessResourceTable(byte[] data, List<string> resIdList)
|
||||||
|
{
|
||||||
|
this.resIdList = resIdList;
|
||||||
|
|
||||||
|
responseMap = [];
|
||||||
|
long lastPosition;
|
||||||
|
|
||||||
|
using var ms = new MemoryStream(data);
|
||||||
|
using var br = new BinaryReader(ms);
|
||||||
|
short type = br.ReadInt16();
|
||||||
|
short headerSize = br.ReadInt16();
|
||||||
|
int size = br.ReadInt32();
|
||||||
|
int packageCount = br.ReadInt32();
|
||||||
|
|
||||||
|
if (type != RES_TABLE_TYPE)
|
||||||
|
{
|
||||||
|
throw new Exception("No RES_TABLE_TYPE found!");
|
||||||
|
}
|
||||||
|
if (size != br.BaseStream.Length)
|
||||||
|
{
|
||||||
|
throw new Exception("The buffer size not matches to the resource table size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int realStringPoolCount = 0;
|
||||||
|
int realPackageCount = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
long pos = br.BaseStream.Position;
|
||||||
|
short t = br.ReadInt16();
|
||||||
|
short hs = br.ReadInt16();
|
||||||
|
int s = br.ReadInt32();
|
||||||
|
|
||||||
|
if (t == RES_STRING_POOL_TYPE)
|
||||||
|
{
|
||||||
|
if (realStringPoolCount == 0)
|
||||||
|
{
|
||||||
|
// Only the first string pool is processed.
|
||||||
|
Debug.WriteLine("Processing the string pool ...");
|
||||||
|
|
||||||
|
byte[] buffer = new byte[s];
|
||||||
|
lastPosition = br.BaseStream.Position;
|
||||||
|
br.BaseStream.Seek(pos, SeekOrigin.Begin);
|
||||||
|
buffer = br.ReadBytes(s);
|
||||||
|
//br.BaseStream.Seek(lastPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
valueStringPool = ProcessStringPool(buffer);
|
||||||
|
}
|
||||||
|
realStringPoolCount++;
|
||||||
|
}
|
||||||
|
else if (t == RES_TABLE_PACKAGE_TYPE)
|
||||||
|
{
|
||||||
|
// Process the package
|
||||||
|
Debug.WriteLine("Processing package {0} ...", realPackageCount);
|
||||||
|
|
||||||
|
byte[] buffer = new byte[s];
|
||||||
|
lastPosition = br.BaseStream.Position;
|
||||||
|
br.BaseStream.Seek(pos, SeekOrigin.Begin);
|
||||||
|
buffer = br.ReadBytes(s);
|
||||||
|
//br.BaseStream.Seek(lastPosition, SeekOrigin.Begin);
|
||||||
|
ProcessPackage(buffer);
|
||||||
|
|
||||||
|
realPackageCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unsupported Type");
|
||||||
|
}
|
||||||
|
br.BaseStream.Seek(pos + s, SeekOrigin.Begin);
|
||||||
|
if (br.BaseStream.Position == br.BaseStream.Length)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realStringPoolCount != 1)
|
||||||
|
{
|
||||||
|
throw new Exception("More than 1 string pool found!");
|
||||||
|
}
|
||||||
|
if (realPackageCount != packageCount)
|
||||||
|
{
|
||||||
|
throw new Exception("Real package count not equals the declared count.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessPackage(byte[] data)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream(data);
|
||||||
|
using var br = new BinaryReader(ms);
|
||||||
|
//HEADER
|
||||||
|
short type = br.ReadInt16();
|
||||||
|
short headerSize = br.ReadInt16();
|
||||||
|
int size = br.ReadInt32();
|
||||||
|
|
||||||
|
int id = br.ReadInt32();
|
||||||
|
package_id = id;
|
||||||
|
|
||||||
|
//PackageName
|
||||||
|
char[] name = new char[256];
|
||||||
|
for (int i = 0; i < 256; ++i)
|
||||||
|
{
|
||||||
|
name[i] = br.ReadChar();
|
||||||
|
}
|
||||||
|
int typeStrings = br.ReadInt32();
|
||||||
|
int lastPublicType = br.ReadInt32();
|
||||||
|
int keyStrings = br.ReadInt32();
|
||||||
|
int lastPublicKey = br.ReadInt32();
|
||||||
|
|
||||||
|
if (typeStrings != headerSize)
|
||||||
|
{
|
||||||
|
throw new Exception("TypeStrings must immediately follow the package structure header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine("Type strings:");
|
||||||
|
long lastPosition = br.BaseStream.Position;
|
||||||
|
br.BaseStream.Seek(typeStrings, SeekOrigin.Begin);
|
||||||
|
byte[] bbTypeStrings = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
|
||||||
|
br.BaseStream.Seek(lastPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
typeStringPool = ProcessStringPool(bbTypeStrings);
|
||||||
|
|
||||||
|
Debug.WriteLine("Key strings:");
|
||||||
|
|
||||||
|
br.BaseStream.Seek(keyStrings, SeekOrigin.Begin);
|
||||||
|
short key_type = br.ReadInt16();
|
||||||
|
short key_headerSize = br.ReadInt16();
|
||||||
|
int key_size = br.ReadInt32();
|
||||||
|
|
||||||
|
lastPosition = br.BaseStream.Position;
|
||||||
|
br.BaseStream.Seek(keyStrings, SeekOrigin.Begin);
|
||||||
|
byte[] bbKeyStrings = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
|
||||||
|
br.BaseStream.Seek(lastPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
keyStringPool = ProcessStringPool(bbKeyStrings);
|
||||||
|
|
||||||
|
// Iterate through all chunks
|
||||||
|
//
|
||||||
|
int typeSpecCount = 0;
|
||||||
|
int typeCount = 0;
|
||||||
|
|
||||||
|
br.BaseStream.Seek((keyStrings + key_size), SeekOrigin.Begin);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int pos = (int)br.BaseStream.Position;
|
||||||
|
short t = br.ReadInt16();
|
||||||
|
short hs = br.ReadInt16();
|
||||||
|
int s = br.ReadInt32();
|
||||||
|
|
||||||
|
if (t == RES_TABLE_TYPE_SPEC_TYPE)
|
||||||
|
{
|
||||||
|
// Process the string pool
|
||||||
|
byte[] buffer = new byte[s];
|
||||||
|
br.BaseStream.Seek(pos, SeekOrigin.Begin);
|
||||||
|
buffer = br.ReadBytes(s);
|
||||||
|
|
||||||
|
ProcessTypeSpec(buffer);
|
||||||
|
|
||||||
|
typeSpecCount++;
|
||||||
|
}
|
||||||
|
else if (t == RES_TABLE_TYPE_TYPE)
|
||||||
|
{
|
||||||
|
// Process the package
|
||||||
|
byte[] buffer = new byte[s];
|
||||||
|
br.BaseStream.Seek(pos, SeekOrigin.Begin);
|
||||||
|
buffer = br.ReadBytes(s);
|
||||||
|
|
||||||
|
ProcessType(buffer);
|
||||||
|
|
||||||
|
typeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
br.BaseStream.Seek(pos + s, SeekOrigin.Begin);
|
||||||
|
if (br.BaseStream.Position == br.BaseStream.Length)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PutIntoMap(string resId, string value)
|
||||||
|
{
|
||||||
|
List<string> valueList = null;
|
||||||
|
if (responseMap.ContainsKey(resId.ToUpper()))
|
||||||
|
valueList = responseMap[resId.ToUpper()];
|
||||||
|
valueList ??= [];
|
||||||
|
valueList.Add(value);
|
||||||
|
if (responseMap.ContainsKey(resId.ToUpper()))
|
||||||
|
responseMap[resId.ToUpper()] = valueList;
|
||||||
|
else
|
||||||
|
responseMap.Add(resId.ToUpper(), valueList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessType(byte[] typeData)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream(typeData);
|
||||||
|
using var br = new BinaryReader(ms);
|
||||||
|
short type = br.ReadInt16();
|
||||||
|
short headerSize = br.ReadInt16();
|
||||||
|
int size = br.ReadInt32();
|
||||||
|
byte id = br.ReadByte();
|
||||||
|
byte res0 = br.ReadByte();
|
||||||
|
short res1 = br.ReadInt16();
|
||||||
|
int entryCount = br.ReadInt32();
|
||||||
|
int entriesStart = br.ReadInt32();
|
||||||
|
|
||||||
|
Dictionary<string, int> refKeys = [];
|
||||||
|
|
||||||
|
int config_size = br.ReadInt32();
|
||||||
|
|
||||||
|
// Skip the config data
|
||||||
|
br.BaseStream.Seek(headerSize, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
if (headerSize + entryCount * 4 != entriesStart)
|
||||||
|
{
|
||||||
|
throw new Exception("HeaderSize, entryCount and entriesStart are not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start to get entry indices
|
||||||
|
int[] entryIndices = new int[entryCount];
|
||||||
|
for (int i = 0; i < entryCount; ++i)
|
||||||
|
{
|
||||||
|
entryIndices[i] = br.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get entries
|
||||||
|
for (int i = 0; i < entryCount; ++i)
|
||||||
|
{
|
||||||
|
if (entryIndices[i] == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int resource_id = (package_id << 24) | (id << 16) | i;
|
||||||
|
|
||||||
|
long pos = br.BaseStream.Position;
|
||||||
|
short entry_size = br.ReadInt16();
|
||||||
|
short entry_flag = br.ReadInt16();
|
||||||
|
int entry_key = br.ReadInt32();
|
||||||
|
|
||||||
|
// Get the value (simple) or map (complex)
|
||||||
|
int FLAG_COMPLEX = 0x0001;
|
||||||
|
|
||||||
|
if ((entry_flag & FLAG_COMPLEX) == 0)
|
||||||
|
{
|
||||||
|
// Simple case
|
||||||
|
short value_size = br.ReadInt16();
|
||||||
|
byte value_res0 = br.ReadByte();
|
||||||
|
byte value_dataType = br.ReadByte();
|
||||||
|
int value_data = br.ReadInt32();
|
||||||
|
|
||||||
|
string idStr = resource_id.ToString("X4");
|
||||||
|
string keyStr = keyStringPool[entry_key];
|
||||||
|
string data = null;
|
||||||
|
|
||||||
|
Debug.WriteLine("Entry 0x" + idStr + ", key: " + keyStr + ", simple value type: ");
|
||||||
|
|
||||||
|
List<string> entryArr = null;
|
||||||
|
if (entryMap.ContainsKey(int.Parse(idStr, System.Globalization.NumberStyles.HexNumber)))
|
||||||
|
entryArr = entryMap[int.Parse(idStr, System.Globalization.NumberStyles.HexNumber)];
|
||||||
|
|
||||||
|
entryArr ??= [];
|
||||||
|
|
||||||
|
entryArr.Add(keyStr);
|
||||||
|
if (entryMap.ContainsKey(int.Parse(idStr, System.Globalization.NumberStyles.HexNumber)))
|
||||||
|
entryMap[int.Parse(idStr, System.Globalization.NumberStyles.HexNumber)] = entryArr;
|
||||||
|
else
|
||||||
|
entryMap.Add(int.Parse(idStr, System.Globalization.NumberStyles.HexNumber), entryArr);
|
||||||
|
|
||||||
|
if (value_dataType == TYPE_STRING)
|
||||||
|
{
|
||||||
|
data = valueStringPool[value_data];
|
||||||
|
Debug.WriteLine(", data: " + valueStringPool[value_data]);
|
||||||
|
}
|
||||||
|
else if (value_dataType == TYPE_REFERENCE)
|
||||||
|
{
|
||||||
|
string hexIndex = value_data.ToString("X4");
|
||||||
|
refKeys.Add(idStr, value_data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = value_data.ToString();
|
||||||
|
Debug.WriteLine(", data: " + value_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (inReqList("@" + idStr)) {
|
||||||
|
PutIntoMap("@" + idStr, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int entry_parent = br.ReadInt32();
|
||||||
|
int entry_count = br.ReadInt32();
|
||||||
|
|
||||||
|
for (int j = 0; j < entry_count; ++j)
|
||||||
|
{
|
||||||
|
int ref_name = br.ReadInt32();
|
||||||
|
short value_size = br.ReadInt16();
|
||||||
|
byte value_res0 = br.ReadByte();
|
||||||
|
byte value_dataType = br.ReadByte();
|
||||||
|
int value_data = br.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine("Entry 0x"
|
||||||
|
+ resource_id.ToString("X4") + ", key: "
|
||||||
|
+ keyStringPool[entry_key]
|
||||||
|
+ ", complex value, not printed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HashSet<string> refKs = [.. refKeys.Keys];
|
||||||
|
|
||||||
|
foreach (string refK in refKs)
|
||||||
|
{
|
||||||
|
List<string> values = null;
|
||||||
|
if (responseMap.ContainsKey("@" + refKeys[refK].ToString("X4").ToUpper()))
|
||||||
|
values = responseMap["@" + refKeys[refK].ToString("X4").ToUpper()];
|
||||||
|
|
||||||
|
if (values != null)
|
||||||
|
foreach (string value in values)
|
||||||
|
{
|
||||||
|
PutIntoMap("@" + refK, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] ProcessStringPool(byte[] data)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream(data);
|
||||||
|
using var br = new BinaryReader(ms);
|
||||||
|
short type = br.ReadInt16();
|
||||||
|
short headerSize = br.ReadInt16();
|
||||||
|
int size = br.ReadInt32();
|
||||||
|
int stringCount = br.ReadInt32();
|
||||||
|
int styleCount = br.ReadInt32();
|
||||||
|
int flags = br.ReadInt32();
|
||||||
|
int stringsStart = br.ReadInt32();
|
||||||
|
int stylesStart = br.ReadInt32();
|
||||||
|
|
||||||
|
bool isUTF_8 = (flags & 256) != 0;
|
||||||
|
|
||||||
|
int[] offsets = new int[stringCount];
|
||||||
|
for (int i = 0; i < stringCount; ++i)
|
||||||
|
{
|
||||||
|
offsets[i] = br.ReadInt32();
|
||||||
|
}
|
||||||
|
string[] strings = new string[stringCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < stringCount; i++)
|
||||||
|
{
|
||||||
|
int pos = stringsStart + offsets[i];
|
||||||
|
br.BaseStream.Seek(pos, SeekOrigin.Begin);
|
||||||
|
strings[i] = string.Empty;
|
||||||
|
if (isUTF_8)
|
||||||
|
{
|
||||||
|
int u16len = br.ReadByte(); // u16len
|
||||||
|
if ((u16len & 0x80) != 0)
|
||||||
|
{
|
||||||
|
// larger than 128
|
||||||
|
u16len = ((u16len & 0x7F) << 8) + br.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
int u8len = br.ReadByte(); // u8len
|
||||||
|
if ((u8len & 0x80) != 0)
|
||||||
|
{
|
||||||
|
// larger than 128
|
||||||
|
u8len = ((u8len & 0x7F) << 8) + br.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u8len > 0)
|
||||||
|
strings[i] = Encoding.UTF8.GetString(br.ReadBytes(u8len));
|
||||||
|
else
|
||||||
|
strings[i] = string.Empty;
|
||||||
|
}
|
||||||
|
else // UTF_16
|
||||||
|
{
|
||||||
|
int u16len = br.ReadUInt16();
|
||||||
|
if ((u16len & 0x8000) != 0)
|
||||||
|
{
|
||||||
|
// larger than 32768
|
||||||
|
u16len = ((u16len & 0x7FFF) << 16) + br.ReadUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u16len > 0)
|
||||||
|
{
|
||||||
|
strings[i] = Encoding.Unicode.GetString(br.ReadBytes(u16len * 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.WriteLine("Parsed value: {0}", strings[i]);
|
||||||
|
}
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessTypeSpec(byte[] data)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream(data);
|
||||||
|
using var br = new BinaryReader(ms);
|
||||||
|
short type = br.ReadInt16();
|
||||||
|
short headerSize = br.ReadInt16();
|
||||||
|
int size = br.ReadInt32();
|
||||||
|
byte id = br.ReadByte();
|
||||||
|
byte res0 = br.ReadByte();
|
||||||
|
short res1 = br.ReadInt16();
|
||||||
|
int entryCount = br.ReadInt32();
|
||||||
|
|
||||||
|
Debug.WriteLine("Processing type spec {0}", typeStringPool[id - 1]);
|
||||||
|
|
||||||
|
int[] flags = new int[entryCount];
|
||||||
|
for (int i = 0; i < entryCount; ++i)
|
||||||
|
{
|
||||||
|
flags[i] = br.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
using QuickLook.Common.ExtensionMethods;
|
using QuickLook.Common.ExtensionMethods;
|
||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
|
using QuickLook.Common.Plugin;
|
||||||
using QuickLook.Plugin.AppViewer.AppxPackageParser;
|
using QuickLook.Plugin.AppViewer.AppxPackageParser;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -29,8 +30,12 @@ namespace QuickLook.Plugin.AppViewer;
|
|||||||
|
|
||||||
public partial class AppxInfoPanel : UserControl, IAppInfoPanel
|
public partial class AppxInfoPanel : UserControl, IAppInfoPanel
|
||||||
{
|
{
|
||||||
public AppxInfoPanel()
|
private ContextObject _context;
|
||||||
|
|
||||||
|
public AppxInfoPanel(ContextObject context)
|
||||||
{
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
@@ -67,6 +72,8 @@ public partial class AppxInfoPanel : UserControl, IAppInfoPanel
|
|||||||
|
|
||||||
using var icon = appxInfo.Logo;
|
using var icon = appxInfo.Logo;
|
||||||
image.Source = icon?.ToBitmapSource() ?? GetWindowsThumbnail(path);
|
image.Source = icon?.ToBitmapSource() ?? GetWindowsThumbnail(path);
|
||||||
|
|
||||||
|
_context.IsBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
using QuickLook.Common.ExtensionMethods;
|
using QuickLook.Common.ExtensionMethods;
|
||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
|
using QuickLook.Common.Plugin;
|
||||||
using QuickLook.Plugin.AppViewer.MsiPackageParser;
|
using QuickLook.Plugin.AppViewer.MsiPackageParser;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -29,8 +30,12 @@ namespace QuickLook.Plugin.AppViewer;
|
|||||||
|
|
||||||
public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
||||||
{
|
{
|
||||||
public MsiInfoPanel()
|
private ContextObject _context;
|
||||||
|
|
||||||
|
public MsiInfoPanel(ContextObject context)
|
||||||
{
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
|
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
|
||||||
@@ -77,6 +82,8 @@ public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
|||||||
manufacturer.Text = msiInfo.Manufacturer;
|
manufacturer.Text = msiInfo.Manufacturer;
|
||||||
totalSize.Text = size.ToPrettySize(2);
|
totalSize.Text = size.ToPrettySize(2);
|
||||||
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
|
_context.IsBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -28,7 +28,7 @@ public class Plugin : IViewer
|
|||||||
private static readonly string[] _extensions =
|
private static readonly string[] _extensions =
|
||||||
[
|
[
|
||||||
// Android
|
// Android
|
||||||
//".apk", ".apk.1", // Android Package
|
".apk", ".apk.1", // Android Package
|
||||||
//".aar", // Android Archive
|
//".aar", // Android Archive
|
||||||
//".aab", // Android App Bundle
|
//".aab", // Android App Bundle
|
||||||
|
|
||||||
@@ -67,8 +67,9 @@ public class Plugin : IViewer
|
|||||||
|
|
||||||
public void Prepare(string path, ContextObject context)
|
public void Prepare(string path, ContextObject context)
|
||||||
{
|
{
|
||||||
context.PreferredSize = Path.GetExtension(path).ToLower() switch
|
context.PreferredSize = Path.GetExtension(ConfirmPath(path)).ToLower() switch
|
||||||
{
|
{
|
||||||
|
".apk" => new Size { Width = 560, Height = 500 },
|
||||||
".msi" => new Size { Width = 520, Height = 230 },
|
".msi" => new Size { Width = 520, Height = 230 },
|
||||||
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 328 },
|
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 328 },
|
||||||
".wgt" or ".wgtu" => new Size { Width = 600, Height = 328 },
|
".wgt" or ".wgtu" => new Size { Width = 600, Height = 328 },
|
||||||
@@ -79,11 +80,12 @@ public class Plugin : IViewer
|
|||||||
public void View(string path, ContextObject context)
|
public void View(string path, ContextObject context)
|
||||||
{
|
{
|
||||||
_path = path;
|
_path = path;
|
||||||
_ip = Path.GetExtension(path).ToLower() switch
|
_ip = Path.GetExtension(ConfirmPath(path)).ToLower() switch
|
||||||
{
|
{
|
||||||
".msi" => new MsiInfoPanel(),
|
".apk" => new ApkInfoPanel(context),
|
||||||
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(),
|
".msi" => new MsiInfoPanel(context),
|
||||||
".wgt" or ".wgtu" => new WgtInfoPanel(),
|
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(context),
|
||||||
|
".wgt" or ".wgtu" => new WgtInfoPanel(context),
|
||||||
_ => throw new NotSupportedException("Extension is not supported."),
|
_ => throw new NotSupportedException("Extension is not supported."),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,7 +93,6 @@ public class Plugin : IViewer
|
|||||||
_ip.Tag = context;
|
_ip.Tag = context;
|
||||||
|
|
||||||
context.ViewerContent = _ip;
|
context.ViewerContent = _ip;
|
||||||
context.IsBusy = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
@@ -100,4 +101,13 @@ public class Plugin : IViewer
|
|||||||
|
|
||||||
_ip = null;
|
_ip = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ConfirmPath(string path)
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(path) == ".1")
|
||||||
|
{
|
||||||
|
return path.Substring(0, path.Length - 2);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,9 +10,12 @@
|
|||||||
<PUBLISHER>Publisher</PUBLISHER>
|
<PUBLISHER>Publisher</PUBLISHER>
|
||||||
<CAPABILITIES>Capabilities</CAPABILITIES>
|
<CAPABILITIES>Capabilities</CAPABILITIES>
|
||||||
<APP_NAME>Application</APP_NAME>
|
<APP_NAME>Application</APP_NAME>
|
||||||
<APP_VERSION>Version</APP_VERSION>
|
<APP_VERSION_NAME>Version Name</APP_VERSION_NAME>
|
||||||
<APP_VERSION_CODE>Version Code</APP_VERSION_CODE>
|
<APP_VERSION_CODE>Version Code</APP_VERSION_CODE>
|
||||||
<PERMISSIONS>Permissions</PERMISSIONS>
|
<PERMISSIONS>Permissions</PERMISSIONS>
|
||||||
|
<PACKAGE_NAME>Package Name</PACKAGE_NAME>
|
||||||
|
<APP_MIN_SDK_VERSION>Min SDK Version</APP_MIN_SDK_VERSION>
|
||||||
|
<APP_TARGET_SDK_VERSION>Target SDK Version</APP_TARGET_SDK_VERSION>
|
||||||
</en>
|
</en>
|
||||||
<pt-BR>
|
<pt-BR>
|
||||||
<PRODUCT_VERSION>Versão do produto</PRODUCT_VERSION>
|
<PRODUCT_VERSION>Versão do produto</PRODUCT_VERSION>
|
||||||
@@ -23,9 +26,12 @@
|
|||||||
<PUBLISHER>Editora</PUBLISHER>
|
<PUBLISHER>Editora</PUBLISHER>
|
||||||
<CAPABILITIES>Recursos</CAPABILITIES>
|
<CAPABILITIES>Recursos</CAPABILITIES>
|
||||||
<APP_NAME>Nome do aplicativo</APP_NAME>
|
<APP_NAME>Nome do aplicativo</APP_NAME>
|
||||||
<APP_VERSION>Versão</APP_VERSION>
|
<APP_VERSION_NAME>Nome da versão</APP_VERSION_NAME>
|
||||||
<APP_VERSION_CODE>Código da versão</APP_VERSION_CODE>
|
<APP_VERSION_CODE>Código da versão</APP_VERSION_CODE>
|
||||||
<PERMISSIONS>Permissões</PERMISSIONS>
|
<PERMISSIONS>Permissões</PERMISSIONS>
|
||||||
|
<PACKAGE_NAME>Nome do pacote</PACKAGE_NAME>
|
||||||
|
<APP_MIN_SDK_VERSION>Versão mínima do SDK</APP_MIN_SDK_VERSION>
|
||||||
|
<APP_TARGET_SDK_VERSION>Versão alvo do SDK</APP_TARGET_SDK_VERSION>
|
||||||
</pt-BR>
|
</pt-BR>
|
||||||
<zh-CN>
|
<zh-CN>
|
||||||
<PRODUCT_VERSION>产品版本</PRODUCT_VERSION>
|
<PRODUCT_VERSION>产品版本</PRODUCT_VERSION>
|
||||||
@@ -36,9 +42,12 @@
|
|||||||
<PUBLISHER>发布者</PUBLISHER>
|
<PUBLISHER>发布者</PUBLISHER>
|
||||||
<CAPABILITIES>功能</CAPABILITIES>
|
<CAPABILITIES>功能</CAPABILITIES>
|
||||||
<APP_NAME>应用名称</APP_NAME>
|
<APP_NAME>应用名称</APP_NAME>
|
||||||
<APP_VERSION>版本名称</APP_VERSION>
|
<APP_VERSION_NAME>版本名称</APP_VERSION_NAME>
|
||||||
<APP_VERSION_CODE>版本号</APP_VERSION_CODE>
|
<APP_VERSION_CODE>版本号</APP_VERSION_CODE>
|
||||||
<PERMISSIONS>权限</PERMISSIONS>
|
<PERMISSIONS>权限</PERMISSIONS>
|
||||||
|
<PACKAGE_NAME>包名</PACKAGE_NAME>
|
||||||
|
<APP_MIN_SDK_VERSION>最低支持 SDK 版本</APP_MIN_SDK_VERSION>
|
||||||
|
<APP_TARGET_SDK_VERSION>目标 SDK 版本</APP_TARGET_SDK_VERSION>
|
||||||
</zh-CN>
|
</zh-CN>
|
||||||
<zh-TW>
|
<zh-TW>
|
||||||
<PRODUCT_VERSION>產品版本</PRODUCT_VERSION>
|
<PRODUCT_VERSION>產品版本</PRODUCT_VERSION>
|
||||||
@@ -49,9 +58,12 @@
|
|||||||
<PUBLISHER>發行者</PUBLISHER>
|
<PUBLISHER>發行者</PUBLISHER>
|
||||||
<CAPABILITIES>功能</CAPABILITIES>
|
<CAPABILITIES>功能</CAPABILITIES>
|
||||||
<APP_NAME>程式名稱</APP_NAME>
|
<APP_NAME>程式名稱</APP_NAME>
|
||||||
<APP_VERSION>版本名稱</APP_VERSION>
|
<APP_VERSION_NAME>版本名稱</APP_VERSION_NAME>
|
||||||
<APP_VERSION_CODE>版本號</APP_VERSION_CODE>
|
<APP_VERSION_CODE>版本號</APP_VERSION_CODE>
|
||||||
<PERMISSIONS>權限</PERMISSIONS>
|
<PERMISSIONS>權限</PERMISSIONS>
|
||||||
|
<PACKAGE_NAME>套件名稱</PACKAGE_NAME>
|
||||||
|
<APP_MIN_SDK_VERSION>最低支援 SDK 版本</APP_MIN_SDK_VERSION>
|
||||||
|
<APP_TARGET_SDK_VERSION>目標 SDK 版本</APP_TARGET_SDK_VERSION>
|
||||||
</zh-TW>
|
</zh-TW>
|
||||||
<ja>
|
<ja>
|
||||||
<PRODUCT_VERSION>製品バージョン</PRODUCT_VERSION>
|
<PRODUCT_VERSION>製品バージョン</PRODUCT_VERSION>
|
||||||
@@ -62,8 +74,11 @@
|
|||||||
<PUBLISHER>発行元</PUBLISHER>
|
<PUBLISHER>発行元</PUBLISHER>
|
||||||
<CAPABILITIES>機能</CAPABILITIES>
|
<CAPABILITIES>機能</CAPABILITIES>
|
||||||
<APP_NAME>アプリケーションネーム</APP_NAME>
|
<APP_NAME>アプリケーションネーム</APP_NAME>
|
||||||
<APP_VERSION>バージョン</APP_VERSION>
|
<APP_VERSION_NAME>バージョン名</APP_VERSION_NAME>
|
||||||
<APP_VERSION_CODE>バージョンコード</APP_VERSION_CODE>
|
<APP_VERSION_CODE>バージョンコード</APP_VERSION_CODE>
|
||||||
<PERMISSIONS>権限</PERMISSIONS>
|
<PERMISSIONS>権限</PERMISSIONS>
|
||||||
|
<PACKAGE_NAME>パッケージ名</PACKAGE_NAME>
|
||||||
|
<APP_MIN_SDK_VERSION>最小SDKバージョン</APP_MIN_SDK_VERSION>
|
||||||
|
<APP_TARGET_SDK_VERSION>ターゲットSDKバージョン</APP_TARGET_SDK_VERSION>
|
||||||
</ja>
|
</ja>
|
||||||
</Translations>
|
</Translations>
|
||||||
|
@@ -79,14 +79,14 @@
|
|||||||
Text="Searching..."
|
Text="Searching..."
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<!-- Version -->
|
<!-- Version Name -->
|
||||||
<TextBlock x:Name="versionTitle"
|
<TextBlock x:Name="versionNameTitle"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Padding="3"
|
Padding="3"
|
||||||
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
Text="Version" />
|
Text="Version" />
|
||||||
<TextBlock x:Name="version"
|
<TextBlock x:Name="versionName"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="8,0,0,0"
|
Margin="8,0,0,0"
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
using QuickLook.Common.ExtensionMethods;
|
using QuickLook.Common.ExtensionMethods;
|
||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
|
using QuickLook.Common.Plugin;
|
||||||
using QuickLook.Plugin.AppViewer.WgtPackageParser;
|
using QuickLook.Plugin.AppViewer.WgtPackageParser;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -28,14 +29,18 @@ namespace QuickLook.Plugin.AppViewer;
|
|||||||
|
|
||||||
public partial class WgtInfoPanel : UserControl, IAppInfoPanel
|
public partial class WgtInfoPanel : UserControl, IAppInfoPanel
|
||||||
{
|
{
|
||||||
public WgtInfoPanel()
|
private ContextObject _context;
|
||||||
|
|
||||||
|
public WgtInfoPanel(ContextObject context)
|
||||||
{
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
|
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
|
||||||
applicationNameTitle.Text = TranslationHelper.Get("APP_NAME", translationFile);
|
applicationNameTitle.Text = TranslationHelper.Get("APP_NAME", translationFile);
|
||||||
versionTitle.Text = TranslationHelper.Get("APP_VERSION", translationFile);
|
versionNameTitle.Text = TranslationHelper.Get("APP_VERSION_NAME", translationFile);
|
||||||
versionCodeTitle.Text = TranslationHelper.Get("APP_VERSION_CODE", translationFile);
|
versionCodeTitle.Text = TranslationHelper.Get("APP_VERSION_CODE", translationFile);
|
||||||
totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile);
|
totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile);
|
||||||
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
||||||
@@ -58,11 +63,13 @@ public partial class WgtInfoPanel : UserControl, IAppInfoPanel
|
|||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
applicationName.Text = wgtInfo.AppNameLocale ?? wgtInfo.AppName;
|
applicationName.Text = wgtInfo.AppNameLocale ?? wgtInfo.AppName;
|
||||||
version.Text = wgtInfo.AppVersion;
|
versionName.Text = wgtInfo.AppVersionName;
|
||||||
versionCode.Text = wgtInfo.AppVersionCode;
|
versionCode.Text = wgtInfo.AppVersionCode;
|
||||||
totalSize.Text = size.ToPrettySize(2);
|
totalSize.Text = size.ToPrettySize(2);
|
||||||
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
||||||
permissions.ItemsSource = wgtInfo.Permissions;
|
permissions.ItemsSource = wgtInfo.Permissions;
|
||||||
|
|
||||||
|
_context.IsBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -15,12 +15,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using QuickLook.Plugin.AppViewer.AppxPackageParser;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Resources;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace QuickLook.Plugin.AppViewer.WgtPackageParser;
|
namespace QuickLook.Plugin.AppViewer.WgtPackageParser;
|
||||||
|
|
||||||
@@ -84,12 +80,12 @@ public sealed class WgtInfo
|
|||||||
public string CompilerVersion { get; set; }
|
public string CompilerVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Json path: version.name
|
/// Json path: versionName.name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AppVersion { get; set; }
|
public string AppVersionName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Json path: version.code
|
/// Json path: versionName.code
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AppVersionCode { get; set; }
|
public string AppVersionCode { get; set; }
|
||||||
|
|
||||||
|
@@ -56,7 +56,7 @@ internal static class WgtParser
|
|||||||
|
|
||||||
if (version != null)
|
if (version != null)
|
||||||
{
|
{
|
||||||
if (version.ContainsKey("name")) wgtInfo.AppVersion = version["name"].ToString();
|
if (version.ContainsKey("name")) wgtInfo.AppVersionName = version["name"].ToString();
|
||||||
if (version.ContainsKey("code")) wgtInfo.AppVersionCode = version["code"].ToString();
|
if (version.ContainsKey("code")) wgtInfo.AppVersionCode = version["code"].ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user