diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkInfoPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkInfoPanel.xaml.cs
index f8a1655..bfcb9a9 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkInfoPanel.xaml.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkInfoPanel.xaml.cs
@@ -77,7 +77,7 @@ public partial class ApkInfoPanel : UserControl, IAppInfoPanel
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
permissions.ItemsSource = apkInfo.Permissions;
- if (!apkInfo.HasIcon)
+ if (apkInfo.HasIcon)
{
using var stream = new MemoryStream(apkInfo.Logo);
var icon = new BitmapImage();
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkInfo.cs
index f8c2c36..808002a 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkInfo.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkInfo.cs
@@ -1,65 +1,47 @@
-// 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 .
-
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
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 Permissions { get; set; } = [];
- public List IconFileName { get; set; }
+ public string PackageName { get; set; }
+
+ public string MinSdkVersion { get; set; }
+
+ public string Icon { get; set; }
+
+ public Dictionary Icons { get; set; } = [];
public byte[] Logo { get; set; }
- public bool HasIcon { get; set; } = false;
+ public string Label { get; set; }
- public bool SupportSmallScreens { get; set; } = false;
+ public Dictionary Labels { get; set; } = [];
- public bool SupportNormalScreens { get; set; } = false;
-
- public bool SupportLargeScreens { get; set; } = false;
-
- public bool SupportAnyDensity { get; set; } = true;
-
- public Dictionary> ResStrings { get; set; }
-
- public bool IsDebuggable
+ public bool HasIcon
{
get
{
- if (Debuggable == null) return false; // debugabble is not in the manifest
- if (Debuggable.Equals("-1")) return true; // Debuggable == true
- else return false;
+ if (Icons.Count <= 0)
+ {
+ return !string.IsNullOrEmpty(Icon);
+ }
+
+ return true;
}
}
+
+ public List Locales { get; set; } = [];
+
+ public List Densities { get; set; } = [];
+
+ public string LaunchableActivity { get; set; }
}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkManifest.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkManifest.cs
deleted file mode 100644
index 3b456b2..0000000
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkManifest.cs
+++ /dev/null
@@ -1,270 +0,0 @@
-// 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 .
-
-using System;
-
-namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
-
-///
-/// https://github.com/hylander0/Iteedee.ApkReader
-///
-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");
- 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
-}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkParser.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkParser.cs
index 99e77f6..fa9131c 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkParser.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkParser.cs
@@ -25,36 +25,33 @@ public static class ApkParser
{
public static ApkInfo Parse(string path)
{
- byte[] manifestData = null;
- byte[] resourcesData = null;
-
using var zip = new ZipFile(path);
- // AndroidManifest.xml
+ var apkReader = new ApkReader.ApkReader();
+ ApkReader.ApkInfo baseInfo = apkReader.Read(path);
+ ApkInfo info = new()
{
- ZipEntry entry = zip.GetEntry("AndroidManifest.xml");
- using var s = new BinaryReader(zip.GetInputStream(entry));
- manifestData = s.ReadBytes((int)entry.Size);
- }
+ VersionName = baseInfo.VersionName,
+ VersionCode = baseInfo.VersionCode,
+ TargetSdkVersion = baseInfo.TargetSdkVersion,
+ Permissions = baseInfo.Permissions,
+ PackageName = baseInfo.PackageName,
+ MinSdkVersion = baseInfo.MinSdkVersion,
+ Icon = baseInfo.Icon,
+ Icons = baseInfo.Icons,
+ Label = baseInfo.Label,
+ Labels = baseInfo.Labels,
+ Locales = baseInfo.Locales,
+ Densities = baseInfo.Densities,
+ LaunchableActivity = baseInfo.LaunchableActivity,
+ };
- // resources.arsc
+ if (baseInfo.HasIcon)
{
- 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());
+ ZipEntry entry = zip.GetEntry(baseInfo.Icons.Values.LastOrDefault());
using var s = new BinaryReader(zip.GetInputStream(entry));
info.Logo = s.ReadBytes((int)entry.Size);
}
-
return info;
}
}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkReader.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkReader.cs
deleted file mode 100644
index d658a8a..0000000
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkReader.cs
+++ /dev/null
@@ -1,280 +0,0 @@
-// 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 .
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Xml;
-
-namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
-
-///
-/// https://github.com/hylander0/Iteedee.ApkReader
-///
-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 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 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 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 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;
- }
-}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkResourceFinder.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkResourceFinder.cs
deleted file mode 100644
index 446de22..0000000
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/ApkPackageParser/ApkResourceFinder.cs
+++ /dev/null
@@ -1,502 +0,0 @@
-// 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 .
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
-
-namespace QuickLook.Plugin.AppViewer.ApkPackageParser;
-
-///
-/// https://github.com/hylander0/Iteedee.ApkReader
-///
-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 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> responseMap;
-
- private Dictionary> entryMap = [];
-
- public Dictionary> Initialize()
- {
- byte[] data = File.ReadAllBytes("resources.arsc");
- return ProcessResourceTable(data, []);
- }
-
- public Dictionary> ProcessResourceTable(byte[] data, List 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 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 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 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 refKs = [.. refKeys.Keys];
-
- foreach (string refK in refKs)
- {
- List 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;
- }
-}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaInfoPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaInfoPanel.xaml.cs
index ced6af1..8b14ed6 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaInfoPanel.xaml.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaInfoPanel.xaml.cs
@@ -18,7 +18,6 @@
using QuickLook.Common.ExtensionMethods;
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
-using QuickLook.Plugin.AppViewer.ApkPackageParser;
using QuickLook.Plugin.AppViewer.IpaPackageParser;
using System;
using System.Globalization;
@@ -79,7 +78,7 @@ public partial class IpaInfoPanel : UserControl, IAppInfoPanel
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
permissions.ItemsSource = ipaInfo.Permissions;
- if (!ipaInfo.HasIcon)
+ if (ipaInfo.HasIcon)
{
using var stream = new MemoryStream(ipaInfo.Logo);
var icon = new BitmapImage();
diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaPackageParser/IpaReader.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaPackageParser/IpaReader.cs
index 8cfe598..010cd63 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaPackageParser/IpaReader.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/IpaPackageParser/IpaReader.cs
@@ -158,7 +158,7 @@ public class IpaReader
}
}
}
- if (!string.IsNullOrWhiteSpace(IconName))
+ if (string.IsNullOrWhiteSpace(IconName))
{
if (InfoPlistDict.TryGetValue("CFBundleIconFiles", out object iconFilesNode) && iconFilesNode is IList