feat: support for apkm
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
<key>public.filename-extension</key>
|
<key>public.filename-extension</key>
|
||||||
<array>
|
<array>
|
||||||
<string>apk</string>
|
<string>apk</string>
|
||||||
|
<string>apkm</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<string>com.google.android.apk</string>
|
<string>com.google.android.apk</string>
|
||||||
<string>dyn.ah62d4rv4ge80c6dp</string>
|
<string>dyn.ah62d4rv4ge80c6dp</string>
|
||||||
<string>public.archive.apk</string>
|
<string>public.archive.apk</string>
|
||||||
|
<string>dyn.ah62d4rv4ge80c6dpry</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QLSupportsSearchableItems</key>
|
<key>QLSupportsSearchableItems</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<string>com.google.android.apk</string>
|
<string>com.google.android.apk</string>
|
||||||
<string>dyn.ah62d4rv4ge80c6dp</string>
|
<string>dyn.ah62d4rv4ge80c6dp</string>
|
||||||
<string>public.archive.apk</string>
|
<string>public.archive.apk</string>
|
||||||
|
<string>dyn.ah62d4rv4ge80c6dpry</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QLThumbnailMinimumDimension</key>
|
<key>QLThumbnailMinimumDimension</key>
|
||||||
<integer>16</integer>
|
<integer>16</integer>
|
||||||
|
|||||||
@@ -25,10 +25,24 @@ struct ApkManifest {
|
|||||||
// MARK: - Full Manifest
|
// MARK: - Full Manifest
|
||||||
|
|
||||||
extension MetaInfo {
|
extension MetaInfo {
|
||||||
|
/// `(true, <nested-apk>)` -- if extension is `.apkm`.
|
||||||
|
/// `(false, zipFile!)` -- if extension is `.apk`.
|
||||||
|
private func effectiveApk() -> (Bool, ZipFile) {
|
||||||
|
// .apkm may contain multiple .apk files. (plus "icon.png" and "info.json" files)
|
||||||
|
if self.url.pathExtension.lowercased() == "apkm" {
|
||||||
|
if let pth = try? zipFile!.unzipFileToTempDir("base.apk") {
|
||||||
|
return (true, ZipFile(pth))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// .apk (and derivatives) have their contents structured directly in zip
|
||||||
|
return (false, zipFile!)
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract `AndroidManifest.xml` and parse its content
|
/// Extract `AndroidManifest.xml` and parse its content
|
||||||
func readApkManifest() -> ApkManifest? {
|
func readApkManifest() -> ApkManifest? {
|
||||||
assert(type == .APK)
|
assert(type == .APK)
|
||||||
guard let data = self.readPayloadFile("AndroidManifest.xml", osxSubdir: nil) else {
|
let (isApkm, nestedZip) = effectiveApk()
|
||||||
|
guard let data = nestedZip.unzipFile("AndroidManifest.xml") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let storage = ApkXmlManifestParser()
|
let storage = ApkXmlManifestParser()
|
||||||
@@ -60,8 +74,13 @@ extension MetaInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rv = storage.result
|
var rv = storage.result
|
||||||
|
// if apkm can load png, prefer that over xml-parsing
|
||||||
|
if isApkm, let iconData = zipFile!.unzipFile("icon.png") {
|
||||||
|
rv.appIcon = nil
|
||||||
|
rv.appIconData = iconData
|
||||||
|
}
|
||||||
os_log(.debug, log: log, "[apk] resolving %{public}@", String(describing: rv))
|
os_log(.debug, log: log, "[apk] resolving %{public}@", String(describing: rv))
|
||||||
rv.resolve(zipFile!)
|
rv.resolve(nestedZip)
|
||||||
os_log(.debug, log: log, "[apk] resolved name: \"%{public}@\" icon: %{public}@", rv.appName ?? "", rv.appIcon ?? "-")
|
os_log(.debug, log: log, "[apk] resolved name: \"%{public}@\" icon: %{public}@", rv.appName ?? "", rv.appIcon ?? "-")
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
@@ -124,7 +143,13 @@ extension MetaInfo {
|
|||||||
func readApkIconOnly() -> ApkManifest? {
|
func readApkIconOnly() -> ApkManifest? {
|
||||||
assert(type == .APK)
|
assert(type == .APK)
|
||||||
var rv = ApkManifest()
|
var rv = ApkManifest()
|
||||||
guard let data = self.readPayloadFile("AndroidManifest.xml", osxSubdir: nil) else {
|
let (isApkm, nestedZip) = effectiveApk()
|
||||||
|
if isApkm, let iconData = zipFile!.unzipFile("icon.png") {
|
||||||
|
rv.appIcon = "icon.png"
|
||||||
|
rv.appIconData = iconData
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
guard let data = nestedZip.unzipFile("AndroidManifest.xml") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if let xml = try? AndroidXML.init(data: data) {
|
if let xml = try? AndroidXML.init(data: data) {
|
||||||
@@ -138,7 +163,7 @@ extension MetaInfo {
|
|||||||
// fallback to xml-string parser
|
// fallback to xml-string parser
|
||||||
rv.appIcon = ApkXmlIconParser().run(data)
|
rv.appIcon = ApkXmlIconParser().run(data)
|
||||||
}
|
}
|
||||||
rv.resolve(zipFile!)
|
rv.resolve(nestedZip)
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +191,7 @@ private class ApkXmlIconParser: NSObject, XMLParserDelegate {
|
|||||||
// MARK: - Resolve resource
|
// MARK: - Resolve resource
|
||||||
|
|
||||||
private extension ApkManifest {
|
private extension ApkManifest {
|
||||||
|
/// Reuse `ZipFile` from previous call because that may be an already unpacked `base.apk`
|
||||||
mutating func resolve(_ zip: ZipFile) {
|
mutating func resolve(_ zip: ZipFile) {
|
||||||
guard let data = zip.unzipFile("resources.arsc"),
|
guard let data = zip.unzipFile("resources.arsc"),
|
||||||
let xml = try? AndroidXML.init(data: data), xml.type == .Table else {
|
let xml = try? AndroidXML.init(data: data), xml.type == .Table else {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct MetaInfo {
|
|||||||
var zipFile: ZipFile? = nil
|
var zipFile: ZipFile? = nil
|
||||||
|
|
||||||
switch self.UTI {
|
switch self.UTI {
|
||||||
case "com.apple.itunes.ipa", "com.opa334.trollstore.tipa", "dyn.ah62d4rv4ge81k4puqe":
|
case "com.apple.itunes.ipa", "com.opa334.trollstore.tipa", "dyn.ah62d4rv4ge81k4puqe" /* tipa */:
|
||||||
self.type = FileType.IPA
|
self.type = FileType.IPA
|
||||||
zipFile = ZipFile(self.url.path)
|
zipFile = ZipFile(self.url.path)
|
||||||
case "com.apple.xcode.archive":
|
case "com.apple.xcode.archive":
|
||||||
@@ -47,10 +47,9 @@ struct MetaInfo {
|
|||||||
}
|
}
|
||||||
case "com.apple.application-and-system-extension":
|
case "com.apple.application-and-system-extension":
|
||||||
self.type = FileType.Extension
|
self.type = FileType.Extension
|
||||||
case "com.google.android.apk", "dyn.ah62d4rv4ge80c6dp", "public.archive.apk":
|
case "com.google.android.apk", "dyn.ah62d4rv4ge80c6dp" /* apk */, "public.archive.apk", "dyn.ah62d4rv4ge80c6dpry" /* apkm */:
|
||||||
self.type = FileType.APK
|
self.type = FileType.APK
|
||||||
zipFile = ZipFile(self.url.path)
|
zipFile = ZipFile(self.url.path)
|
||||||
// case "com.google.android.apkm", "dyn.ah62d4rv4ge80c6dpry":
|
|
||||||
default:
|
default:
|
||||||
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
|
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
|
||||||
fatalError()
|
fatalError()
|
||||||
@@ -81,7 +80,7 @@ struct MetaInfo {
|
|||||||
case .IPA:
|
case .IPA:
|
||||||
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
|
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
|
||||||
case .APK:
|
case .APK:
|
||||||
return zipFile!.unzipFile(filename)
|
return nil // not applicable for .apk
|
||||||
case .Archive, .Extension:
|
case .Archive, .Extension:
|
||||||
return try? Data(contentsOf: self.effectiveUrl(osxSubdir, filename))
|
return try? Data(contentsOf: self.effectiveUrl(osxSubdir, filename))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user