49 Commits
v1.1.0 ... main

Author SHA1 Message Date
relikd
15f536cb79 fix: html modify instructions in readme 2025-12-04 19:20:56 +01:00
relikd
ac8b76d3fc chore: bump version 2025-12-01 01:41:20 +01:00
relikd
62d1407d17 fix: .appex for macOS extensions 2025-12-01 01:14:53 +01:00
relikd
eb169ae0a2 ref: rm Thumbnail for .appex (can there even be an icon?) 2025-12-01 01:07:52 +01:00
relikd
abdee3b780 chore: move files around 2025-12-01 01:03:02 +01:00
relikd
38c861442c ref: split ApkManifest into data parser and content 2025-12-01 00:45:09 +01:00
relikd
1ce5f3e069 chore: update log category 2025-11-30 16:53:37 +01:00
relikd
d0da644c26 ref: AndroidSdkMap 2025-11-30 16:41:58 +01:00
relikd
76e7e22b49 ref: template use {{X}} instead of __X__ 2025-11-30 16:28:26 +01:00
relikd
72e395c5da ref: data structures for plist files 2025-11-30 15:36:34 +01:00
relikd
be65aaa19a chore: bump version 2025-11-29 20:32:39 +01:00
relikd
feae5aba3e chore: downgrade package version 2025-11-29 20:30:18 +01:00
relikd
3a587ce730 feat: support for apkm 2025-11-29 20:24:29 +01:00
relikd
6bb62bdf82 ref: ApkManifest 2025-11-29 20:24:01 +01:00
relikd
f233d0e4a2 ref: icon preference rating 2025-11-29 20:11:40 +01:00
relikd
035276dcfc ref: simplify readEntitlements() + unzip to tmp-dir shortcut 2025-11-29 01:59:38 +01:00
relikd
3a16277867 chore: update readme 2025-11-28 13:39:38 +01:00
relikd
591a75dabc feat: support for apk 2025-11-28 13:39:24 +01:00
relikd
cde957b01f ref: remove dead code for non-optional zipFile 2025-11-28 13:21:33 +01:00
relikd
71d1b35aac ref: hide all html tags by default 2025-11-28 13:05:03 +01:00
relikd
5cd7034fc8 chore: support for xcconfig in version bump script 2025-11-06 03:21:39 +01:00
relikd
e0ccba1af8 chore: bump version 2025-11-06 02:00:34 +01:00
relikd
21c21ec059 feat: quit preview gracefully if Info.plist not found 2025-11-06 01:57:33 +01:00
relikd
cfb6b17bc7 feat: not-hidden combination 2025-11-06 01:25:35 +01:00
relikd
2d16cb666b feat: show xcarchive developer notes 2025-11-06 01:06:53 +01:00
relikd
1a6d98a4b2 chore: upgrade to recommended settings 2025-11-06 00:33:49 +01:00
relikd
85c1ae95c1 feat: hide empty entitlements 2025-11-06 00:20:33 +01:00
relikd
fd13f13a3c ref: parentDir() 2025-11-06 00:15:11 +01:00
relikd
8b916829d1 fix: preview of xcarchive for non app-like bundles 2025-11-06 00:10:10 +01:00
relikd
05f30ee755 feat: hide ATS if none are present 2025-11-05 23:53:50 +01:00
relikd
5166a67e48 ref: extract TransportSecurity into own class 2025-11-05 23:30:37 +01:00
relikd
d1aae4cc15 ref: introduce CLASS_VISIBLE 2025-11-05 23:29:37 +01:00
relikd
f38c1f802f feat: make appPlist optional (again) 2025-11-05 18:36:45 +01:00
relikd
af9c398571 feat: support for macOS xcarchive 2025-11-05 18:18:08 +01:00
relikd
36e30a1fdf chore: remove semicolons 2025-11-05 18:02:39 +01:00
relikd
fb8fa41dd0 ref: URL utils class 2025-11-05 17:54:29 +01:00
relikd
33cec015ab ref: static TransportSecurity strings 2025-11-05 02:06:02 +01:00
relikd
6dec6530c5 fix: check for empty entitlements dict 2025-11-05 02:04:28 +01:00
relikd
596ad18412 fix: release compile error 2025-11-04 22:15:10 +01:00
relikd
fe282b445b chore: add changelog 2025-11-04 21:33:12 +01:00
relikd
0ee94bcb0d ref: move html chapters around 2025-11-04 20:39:07 +01:00
relikd
96001e4d40 ref: make app plist required 2025-11-04 20:18:43 +01:00
relikd
6898eeb42c ref: rename template values 2025-11-04 19:26:07 +01:00
relikd
5250f48d38 ref: regexReplace template values 2025-11-04 16:49:31 +01:00
relikd
802daebe56 ref: rename PreviewGenerator 2025-11-02 16:10:08 +01:00
relikd
f49e184dbb feat: customizable html 2025-11-02 00:45:54 +01:00
relikd
d634763eef ref: config file 2025-11-02 00:40:24 +01:00
relikd
5902bf9aa3 fix: CoreUI support via Framework abstraction 2025-11-01 22:17:36 +01:00
relikd
6d91972e97 feat: use xcconfig 2025-10-31 21:36:54 +01:00
62 changed files with 2158 additions and 948 deletions

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -27,6 +27,26 @@
</array> </array>
</dict> </dict>
</dict> </dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.google.android.apk</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.mime-type</key>
<array>
<string>application/vnd.android.package-archive</string>
</array>
<key>public.filename-extension</key>
<array>
<string>apk</string>
<string>apkm</string>
</array>
</dict>
</dict>
</array> </array>
</dict> </dict>
</plist> </plist>

View File

@@ -1,61 +1,55 @@
import Foundation import Foundation
import AppKit // NSImage import AppKit // NSImage
import CoreUI // CUICatalog private import CoreUI // CUICatalog
import os // OSLog private import os // OSLog
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AppIcon+Car") private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AssetCarReader")
// this has been written from scratch but general usage on public class CarReader {
// including the private framework has been taken from: private let catalog: CUICatalog
// https://github.com/showxu/cartools
// also see:
// https://blog.timac.org/2018/1018-reverse-engineering-the-car-file-format/
extension AppIcon { public init?(_ data: Data) {
/// Use `CUICatalog` to extract an image from `Assets.car`
func imageFromAssetsCar(_ imageName: String) -> NSImage? {
guard let data = meta.readPayloadFile("Assets.car") else {
return nil
}
let catalog: CUICatalog
do { do {
catalog = try data.withUnsafeBytes { try CUICatalog(bytes: $0.baseAddress!, length: UInt64(data.count)) } catalog = try data.withUnsafeBytes { try CUICatalog(bytes: $0.baseAddress!, length: UInt64(data.count)) }
} catch { } catch {
os_log(.error, log: log, "[icon-car] ERROR: could not open catalog: %{public}@", error.localizedDescription) os_log(.error, log: log, "[asset-car] ERROR: could not open catalog: %{public}@", error.localizedDescription)
return nil return nil
} }
}
if let validName = carVerifyNameExists(imageName, in: catalog) { /// Use `CUICatalog` to extract an image from `Assets.car`
if let bestImage = carFindHighestResolutionIcon(catalog.images(withName: validName)) { public func imageFromAssetsCar(_ imageName: String) -> NSImage? {
os_log(.debug, log: log, "[icon-car] using Assets.car with key %{public}@", validName) if let validName = verifyNameExists(imageName, in: catalog) {
if let bestImage = findHighestResolutionIcon(catalog.images(withName: validName)) {
os_log(.debug, log: log, "[asset-car] using Assets.car with key %{public}@", validName)
return NSImage(cgImage: bestImage.image, size: bestImage.size) return NSImage(cgImage: bestImage.image, size: bestImage.size)
} }
} }
return nil;
}
// MARK: - Helper: Assets.car
/// Helper method to check available icon names. Will return a valid name or `nil` if no image with that key is found.
func carVerifyNameExists(_ imageName: String, in catalog: CUICatalog) -> String? {
if let availableNames = catalog.allImageNames(), !availableNames.contains(imageName) {
// Theoretically this should never happen. Assuming the image name is found in an image file.
os_log(.info, log: log, "[icon-car] WARN: key '%{public}@' does not match any available key", imageName)
if let alternativeName = carSearchAlternativeName(imageName, inAvailable: availableNames) {
os_log(.info, log: log, "[icon-car] falling back to '%{public}@'", alternativeName)
return alternativeName
}
os_log(.debug, log: log, "[icon-car] available keys: %{public}@", catalog.allImageNames() ?? [])
return nil return nil
} }
return imageName;
// MARK: - Private methods
/// Helper method to check available icon names. Will return a valid name or `nil` if no image with that key is found.
private func verifyNameExists(_ imageName: String, in catalog: CUICatalog) -> String? {
if let availableNames = catalog.allImageNames(), !availableNames.contains(imageName) {
// Theoretically this should never happen. Assuming the image name is found in an image file.
os_log(.info, log: log, "[asset-car] WARN: key '%{public}@' does not match any available key", imageName)
if let alternativeName = searchAlternativeName(imageName, inAvailable: availableNames) {
os_log(.info, log: log, "[asset-car] falling back to '%{public}@'", alternativeName)
return alternativeName
}
os_log(.debug, log: log, "[asset-car] available keys: %{public}@", catalog.allImageNames() ?? [])
return nil
}
return imageName
} }
/// If exact name does not exist in catalog, search for a name that shares the same prefix. /// If exact name does not exist in catalog, search for a name that shares the same prefix.
/// E.g., "AppIcon60x60" may match "AppIcon" or "AppIcon60x60_small" /// E.g., "AppIcon60x60" may match "AppIcon" or "AppIcon60x60_small"
func carSearchAlternativeName(_ originalName: String, inAvailable availableNames: [String]) -> String? { private func searchAlternativeName(_ originalName: String, inAvailable availableNames: [String]) -> String? {
var bestOption: String? = nil var bestOption: String? = nil
var bestDiff: Int = 999 var bestDiff: Int = 999
@@ -72,7 +66,7 @@ extension AppIcon {
} }
/// Given a list of `CUINamedImage`, return the one with the highest resolution. Vector graphics are ignored. /// Given a list of `CUINamedImage`, return the one with the highest resolution. Vector graphics are ignored.
func carFindHighestResolutionIcon(_ availableImages: [CUINamedImage]) -> CUINamedImage? { private func findHighestResolutionIcon(_ availableImages: [CUINamedImage]) -> CUINamedImage? {
var largestWidth: CGFloat = 0 var largestWidth: CGFloat = 0
var largestImage: CUINamedImage? = nil var largestImage: CUINamedImage? = nil
// cast to NSArray is necessary as otherwise this will crash // cast to NSArray is necessary as otherwise this will crash

View File

@@ -0,0 +1,9 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
FRAMEWORK_SEARCH_PATHS = $(PROJECT_DIR)/AssetCarReader/PrivateFrameworks
SYSTEM_FRAMEWORK_SEARCH_PATHS = $(SYSTEM_LIBRARY_DIR)/PrivateFrameworks
SWIFT_INCLUDE_PATHS = $(SRCROOT)/AssetCarReader/PrivateFrameworks/CoreUI.framework
DYLIB_COMPATIBILITY_VERSION = 1
DYLIB_CURRENT_VERSION = 42

60
CHANGELOG.md Normal file
View File

@@ -0,0 +1,60 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.0] 2025-12-01
Fixed:
- `.appex` macOS extensions (`Info.plist` was not found)
- Removed `.appex` from `ThumbnailProvider` (extension probably dont have icons anyway)
Changed:
- Template uses `{{X}}` instead of `__X__`
## [1.4.0] 2025-11-29
Added:
- Support for `.apk` files
- Support for `.apkm` files
## [1.3.0] 2025-11-06
Added:
- Show macOS apps in `.xcarchive`
- Show `.xcarchive` developer notes
Fixed:
- Cancel preview (and allow other plugins to run) if there is no `Info.plist` in `.xcarchive`
Changed:
- Hide Transport Security and Entitlements if they are empty
## [1.2.0] 2025-11-04
Added:
- Customizable HTML template
Fixed:
- Properly handle `Assets.car` files by abstracting relevant code into `.framework`
Changed:
- Updated HTML template
## [1.1.0] 2025-10-30
Added:
- Support for `.tipa` files
## [1.0.0] 2025-10-30
Initial release
[1.5.0]: https://github.com/relikd/QLAppBundle/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/relikd/QLAppBundle/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/relikd/QLAppBundle/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/relikd/QLAppBundle/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/relikd/QLAppBundle/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/relikd/QLAppBundle/compare/9b0761318c85090d1ef22f12d3eab67a9a194882...v1.0.0

6
Config-debug.xcconfig Normal file
View File

@@ -0,0 +1,6 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
#include "Config.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.QLAppBundle.debug

14
Config.xcconfig Normal file
View File

@@ -0,0 +1,14 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Apple Development
ENABLE_HARDENED_RUNTIME = YES
SWIFT_VERSION = 5.0
MACOSX_DEPLOYMENT_TARGET = 10.15
MARKETING_VERSION = 1.5.0
PRODUCT_NAME = QLAppBundle
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.QLAppBundle
CURRENT_PROJECT_VERSION = 2018

View File

@@ -9,9 +9,17 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; }; 5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; };
5405CF652EA1376B00613856 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; }; 5405CF652EA1376B00613856 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; };
540B77D92ED79BBD009E030C /* Apk+Manifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540B77D82ED79BB2009E030C /* Apk+Manifest.swift */; };
540B77DC2ED79CC1009E030C /* AndroidXML in Frameworks */ = {isa = PBXBuildFile; productRef = 540B77DB2ED79CC1009E030C /* AndroidXML */; };
540B77DE2ED79CC8009E030C /* AndroidXML in Frameworks */ = {isa = PBXBuildFile; productRef = 540B77DD2ED79CC8009E030C /* AndroidXML */; };
5412DECE2EBC168600F9040D /* Preview+ArchiveInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */; };
5412DED02EBC283000F9040D /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECF2EBC283000F9040D /* RuntimeError.swift */; };
543899082EDCA223007C02FC /* Apk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543899072EDCA223007C02FC /* Apk.swift */; };
543899092EDCA26D007C02FC /* Apk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543899072EDCA223007C02FC /* Apk.swift */; };
5438990B2EDCA27F007C02FC /* Apk+Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5438990A2EDCA27F007C02FC /* Apk+Icon.swift */; };
5438990E2EDCB126007C02FC /* Apk+Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5438990A2EDCA27F007C02FC /* Apk+Icon.swift */; };
543FE5742EB3BB5E0059F98B /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 543FE5732EB3BB5E0059F98B /* AppIcon.icns */; }; 543FE5742EB3BB5E0059F98B /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 543FE5732EB3BB5E0059F98B /* AppIcon.icns */; };
54442C232E378BAF008A870E /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54442C222E378BAF008A870E /* Quartz.framework */; }; 54442C232E378BAF008A870E /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54442C222E378BAF008A870E /* Quartz.framework */; };
54442C302E378BAF008A870E /* QLPreview.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 54442C202E378BAF008A870E /* QLPreview.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
54442C702E378BDD008A870E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54442C6A2E378BDD008A870E /* AppDelegate.swift */; }; 54442C702E378BDD008A870E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54442C6A2E378BDD008A870E /* AppDelegate.swift */; };
54442C722E378BDD008A870E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54442C6D2E378BDD008A870E /* MainMenu.xib */; }; 54442C722E378BDD008A870E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54442C6D2E378BDD008A870E /* MainMenu.xib */; };
54442C792E378BE0008A870E /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54442C742E378BE0008A870E /* PreviewViewController.swift */; }; 54442C792E378BE0008A870E /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54442C742E378BE0008A870E /* PreviewViewController.swift */; };
@@ -20,27 +28,40 @@
545459C52EA469EA002892E5 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 54D3A6F32EA4603B001EF4F6 /* template.html */; }; 545459C52EA469EA002892E5 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 54D3A6F32EA4603B001EF4F6 /* template.html */; };
54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */; }; 54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */; };
54581FD22EB29A0B0043A0B3 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54442C222E378BAF008A870E /* Quartz.framework */; }; 54581FD22EB29A0B0043A0B3 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54442C222E378BAF008A870E /* Quartz.framework */; };
54581FDA2EB29A0B0043A0B3 /* QLThumbnail.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 54581FCF2EB29A0B0043A0B3 /* QLThumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 54581FDA2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
545820232EB29B4C0043A0B3 /* defaultIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54D3A6F22EA4603B001EF4F6 /* defaultIcon.png */; }; 545820232EB29B4C0043A0B3 /* defaultIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54D3A6F22EA4603B001EF4F6 /* defaultIcon.png */; };
5469E11D2EA5930C00D46CE7 /* Entitlements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469E11C2EA5930C00D46CE7 /* Entitlements.swift */; }; 5469E11D2EA5930C00D46CE7 /* Entitlements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469E11C2EA5930C00D46CE7 /* Entitlements.swift */; };
5478996F2EB38EBB00F96B80 /* CoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D3A6F52EA4610B001EF4F6 /* CoreUI.framework */; };
547899702EB38EBB00F96B80 /* CoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D3A6F52EA4610B001EF4F6 /* CoreUI.framework */; };
547899712EB38F3D00F96B80 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; }; 547899712EB38F3D00F96B80 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; };
547899722EB38F3D00F96B80 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; }; 547899722EB38F3D00F96B80 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; };
547899732EB38F3D00F96B80 /* NSBezierPath+RoundedRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */; }; 547899732EB38F3D00F96B80 /* NSBezierPath+RoundedRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */; };
547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */; }; 547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */; };
547899762EB38FD400F96B80 /* AppIcon+Car.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545459C62EA4773A002892E5 /* AppIcon+Car.swift */; };
547899772EB38FD400F96B80 /* AppIcon+Car.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545459C62EA4773A002892E5 /* AppIcon+Car.swift */; };
547F52DE2EB2C15D002B6D5F /* ExpirationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */; }; 547F52DE2EB2C15D002B6D5F /* ExpirationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */; };
547F52E42EB2C3D8002B6D5F /* Html+iTunesPurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52E32EB2C3D8002B6D5F /* Html+iTunesPurchase.swift */; }; 547F52E42EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52E32EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift */; };
547F52E82EB2C41C002B6D5F /* HtmlGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52E62EB2C41C002B6D5F /* HtmlGenerator.swift */; }; 547F52E82EB2C41C002B6D5F /* PreviewGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52E62EB2C41C002B6D5F /* PreviewGenerator.swift */; };
547F52EB2EB2C672002B6D5F /* Html+FileInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52E92EB2C672002B6D5F /* Html+FileInfo.swift */; }; 547F52EB2EB2C672002B6D5F /* Preview+FileInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52E92EB2C672002B6D5F /* Preview+FileInfo.swift */; };
547F52ED2EB2C822002B6D5F /* Html+AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52EC2EB2C822002B6D5F /* Html+AppInfo.swift */; }; 547F52ED2EB2C822002B6D5F /* Preview+AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52EC2EB2C822002B6D5F /* Preview+AppInfo.swift */; };
547F52EF2EB2C8E8002B6D5F /* Html+Provisioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52EE2EB2C8E8002B6D5F /* Html+Provisioning.swift */; }; 547F52EF2EB2C8E8002B6D5F /* Preview+Provisioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52EE2EB2C8E8002B6D5F /* Preview+Provisioning.swift */; };
547F52F42EB2CA05002B6D5F /* Html+Entitlements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F32EB2CA05002B6D5F /* Html+Entitlements.swift */; }; 547F52F42EB2CA05002B6D5F /* Preview+Entitlements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F32EB2CA05002B6D5F /* Preview+Entitlements.swift */; };
547F52F72EB2CAC7002B6D5F /* Html+Footer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F62EB2CAC7002B6D5F /* Html+Footer.swift */; }; 547F52F72EB2CAC7002B6D5F /* Preview+Footer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F62EB2CAC7002B6D5F /* Preview+Footer.swift */; };
547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F82EB2CBAB002B6D5F /* Date+Format.swift */; }; 547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F82EB2CBAB002B6D5F /* Date+Format.swift */; };
54993B882EDB9AA1008B656D /* Plist+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54993B872EDB9A9B008B656D /* Plist+Info.swift */; };
54993B8A2EDBA596008B656D /* Plist+Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54993B892EDBA596008B656D /* Plist+Icon.swift */; };
54993B8B2EDBA75A008B656D /* Plist+Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54993B892EDBA596008B656D /* Plist+Icon.swift */; };
54993B932EDBB41D008B656D /* Plist+iTunesMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54993B922EDBB419008B656D /* Plist+iTunesMetadata.swift */; };
54993B952EDBC819008B656D /* Plist+MobileProvision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54993B942EDBC813008B656D /* Plist+MobileProvision.swift */; };
54993B972EDC7C65008B656D /* Provisioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54993B962EDC7C61008B656D /* Provisioning.swift */; };
549E3B9F2EBA9D2500ADFF56 /* QLAppBundle Preview Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
549E3BA12EBAE7D300ADFF56 /* URL+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E3BA02EBAE7D300ADFF56 /* URL+File.swift */; };
549E3BA22EBAECD400ADFF56 /* URL+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E3BA02EBAE7D300ADFF56 /* URL+File.swift */; };
549E3BA42EBC021500ADFF56 /* Preview+TransportSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E3BA32EBC021500ADFF56 /* Preview+TransportSecurity.swift */; };
54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */; }; 54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */; };
54B6FFEE2EB6A847007397C0 /* AssetCarReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B6FFEC2EB6A847007397C0 /* AssetCarReader.swift */; };
54B6FFEF2EB6A8E0007397C0 /* CoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D3A6F52EA4610B001EF4F6 /* CoreUI.framework */; };
54B6FFF02EB6AA0F007397C0 /* AssetCarReader.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; };
54B6FFF12EB6AA0F007397C0 /* AssetCarReader.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B6FFF52EB6AA14007397C0 /* AssetCarReader.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; };
54B6FFF62EB6AA14007397C0 /* AssetCarReader.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54CCF59E2EDC9A6800D766F9 /* AndroidSdkMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CCF59D2EDC9A6800D766F9 /* AndroidSdkMap.swift */; };
54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */; }; 54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */; };
54D3A6EE2EA39CC6001EF4F6 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */; }; 54D3A6EE2EA39CC6001EF4F6 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */; };
54D3A6F02EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */; }; 54D3A6F02EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */; };
@@ -63,6 +84,20 @@
remoteGlobalIDString = 54581FCE2EB29A0B0043A0B3; remoteGlobalIDString = 54581FCE2EB29A0B0043A0B3;
remoteInfo = QLThumbnail; remoteInfo = QLThumbnail;
}; };
54B6FFF22EB6AA0F007397C0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 54442BEC2E378B71008A870E /* Project object */;
proxyType = 1;
remoteGlobalIDString = 54352E892EB6A79A0082F61D;
remoteInfo = AssetCarReader;
};
54B6FFF72EB6AA14007397C0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 54442BEC2E378B71008A870E /* Project object */;
proxyType = 1;
remoteGlobalIDString = 54352E892EB6A79A0082F61D;
remoteInfo = AssetCarReader;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@@ -72,49 +107,83 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 13; dstSubfolderSpec = 13;
files = ( files = (
54581FDA2EB29A0B0043A0B3 /* QLThumbnail.appex in Embed Foundation Extensions */, 549E3B9F2EBA9D2500ADFF56 /* QLAppBundle Preview Extension.appex in Embed Foundation Extensions */,
54442C302E378BAF008A870E /* QLPreview.appex in Embed Foundation Extensions */, 54581FDA2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex in Embed Foundation Extensions */,
); );
name = "Embed Foundation Extensions"; name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
54B6FFF42EB6AA0F007397C0 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
54B6FFF12EB6AA0F007397C0 /* AssetCarReader.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
54B6FFF92EB6AA14007397C0 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
54B6FFF62EB6AA14007397C0 /* AssetCarReader.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
5405CF5D2EA1199B00613856 /* MetaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaInfo.swift; sourceTree = "<group>"; }; 5405CF5D2EA1199B00613856 /* MetaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaInfo.swift; sourceTree = "<group>"; };
5405CF642EA1376B00613856 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; }; 5405CF642EA1376B00613856 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
540B77D82ED79BB2009E030C /* Apk+Manifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Apk+Manifest.swift"; sourceTree = "<group>"; };
5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+ArchiveInfo.swift"; sourceTree = "<group>"; };
5412DECF2EBC283000F9040D /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = "<group>"; };
54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AssetCarReader.framework; sourceTree = BUILT_PRODUCTS_DIR; };
543899072EDCA223007C02FC /* Apk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Apk.swift; sourceTree = "<group>"; };
5438990A2EDCA27F007C02FC /* Apk+Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Apk+Icon.swift"; sourceTree = "<group>"; };
543FE5732EB3BB5E0059F98B /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; }; 543FE5732EB3BB5E0059F98B /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; };
543FE5752EB3BC740059F98B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 543FE5752EB3BC740059F98B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
543FE5772EB3DAA20059F98B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
54442BF42E378B71008A870E /* QLAppBundle (debug).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "QLAppBundle (debug).app"; sourceTree = BUILT_PRODUCTS_DIR; }; 54442BF42E378B71008A870E /* QLAppBundle (debug).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "QLAppBundle (debug).app"; sourceTree = BUILT_PRODUCTS_DIR; };
54442C202E378BAF008A870E /* QLPreview.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = QLPreview.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "QLAppBundle Preview Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
54442C222E378BAF008A870E /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; 54442C222E378BAF008A870E /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
54442C6A2E378BDD008A870E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 54442C6A2E378BDD008A870E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
54442C6C2E378BDD008A870E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 54442C6C2E378BDD008A870E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
54442C6E2E378BDD008A870E /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
54442C732E378BE0008A870E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 54442C732E378BE0008A870E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54442C742E378BE0008A870E /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = "<group>"; }; 54442C742E378BE0008A870E /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = "<group>"; };
54442C752E378BE0008A870E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = "<group>"; }; 54442C752E378BE0008A870E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = "<group>"; };
54442C772E378BE0008A870E /* QLPreview.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QLPreview.entitlements; sourceTree = "<group>"; }; 544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AssetCarReader.xcconfig; sourceTree = "<group>"; };
545459C62EA4773A002892E5 /* AppIcon+Car.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppIcon+Car.swift"; sourceTree = "<group>"; }; 54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "QLAppBundle Thumbnail Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
54581FCF2EB29A0B0043A0B3 /* QLThumbnail.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = QLThumbnail.appex; sourceTree = BUILT_PRODUCTS_DIR; };
54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; }; 54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; };
545D9F3D2EB54C6200C38917 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
5469E11C2EA5930C00D46CE7 /* Entitlements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entitlements.swift; sourceTree = "<group>"; }; 5469E11C2EA5930C00D46CE7 /* Entitlements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entitlements.swift; sourceTree = "<group>"; };
547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpirationStatus.swift; sourceTree = "<group>"; }; 547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpirationStatus.swift; sourceTree = "<group>"; };
547F52E32EB2C3D8002B6D5F /* Html+iTunesPurchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Html+iTunesPurchase.swift"; sourceTree = "<group>"; }; 547F52E32EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+iTunesPurchase.swift"; sourceTree = "<group>"; };
547F52E62EB2C41C002B6D5F /* HtmlGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HtmlGenerator.swift; sourceTree = "<group>"; }; 547F52E62EB2C41C002B6D5F /* PreviewGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewGenerator.swift; sourceTree = "<group>"; };
547F52E92EB2C672002B6D5F /* Html+FileInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Html+FileInfo.swift"; sourceTree = "<group>"; }; 547F52E92EB2C672002B6D5F /* Preview+FileInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+FileInfo.swift"; sourceTree = "<group>"; };
547F52EC2EB2C822002B6D5F /* Html+AppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Html+AppInfo.swift"; sourceTree = "<group>"; }; 547F52EC2EB2C822002B6D5F /* Preview+AppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+AppInfo.swift"; sourceTree = "<group>"; };
547F52EE2EB2C8E8002B6D5F /* Html+Provisioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Html+Provisioning.swift"; sourceTree = "<group>"; }; 547F52EE2EB2C8E8002B6D5F /* Preview+Provisioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+Provisioning.swift"; sourceTree = "<group>"; };
547F52F32EB2CA05002B6D5F /* Html+Entitlements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Html+Entitlements.swift"; sourceTree = "<group>"; }; 547F52F32EB2CA05002B6D5F /* Preview+Entitlements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+Entitlements.swift"; sourceTree = "<group>"; };
547F52F62EB2CAC7002B6D5F /* Html+Footer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Html+Footer.swift"; sourceTree = "<group>"; }; 547F52F62EB2CAC7002B6D5F /* Preview+Footer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+Footer.swift"; sourceTree = "<group>"; };
547F52F82EB2CBAB002B6D5F /* Date+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Format.swift"; sourceTree = "<group>"; }; 547F52F82EB2CBAB002B6D5F /* Date+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Format.swift"; sourceTree = "<group>"; };
547F52FB2EB37F10002B6D5F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; 547F52FB2EB37F10002B6D5F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
547F52FC2EB37F3A002B6D5F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; }; 547F52FC2EB37F3A002B6D5F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
5485EE362EB1460C009E3905 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; }; 54993B872EDB9A9B008B656D /* Plist+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Plist+Info.swift"; sourceTree = "<group>"; };
54993B892EDBA596008B656D /* Plist+Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Plist+Icon.swift"; sourceTree = "<group>"; };
54993B922EDBB419008B656D /* Plist+iTunesMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Plist+iTunesMetadata.swift"; sourceTree = "<group>"; };
54993B942EDBC813008B656D /* Plist+MobileProvision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Plist+MobileProvision.swift"; sourceTree = "<group>"; };
54993B962EDC7C61008B656D /* Provisioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provisioning.swift; sourceTree = "<group>"; };
549E3B9E2EBA8FDA00ADFF56 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
549E3BA02EBAE7D300ADFF56 /* URL+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+File.swift"; sourceTree = "<group>"; };
549E3BA32EBC021500ADFF56 /* Preview+TransportSecurity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+TransportSecurity.swift"; sourceTree = "<group>"; };
54AE5BFB2EB3DB1000B4CFC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 54AE5BFB2EB3DB1000B4CFC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54AE5BFC2EB3DB1000B4CFC7 /* QLThumbnail.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QLThumbnail.entitlements; sourceTree = "<group>"; };
54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = "<group>"; }; 54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = "<group>"; };
54B6FFEC2EB6A847007397C0 /* AssetCarReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCarReader.swift; sourceTree = "<group>"; };
54CCF59D2EDC9A6800D766F9 /* AndroidSdkMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidSdkMap.swift; sourceTree = "<group>"; };
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCategories.swift; sourceTree = "<group>"; }; 54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCategories.swift; sourceTree = "<group>"; };
54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIcon.swift; sourceTree = "<group>"; }; 54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIcon.swift; sourceTree = "<group>"; };
54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSBezierPath+RoundedRect.swift"; sourceTree = "<group>"; }; 54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSBezierPath+RoundedRect.swift"; sourceTree = "<group>"; };
@@ -122,18 +191,19 @@
54D3A6F32EA4603B001EF4F6 /* template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = template.html; sourceTree = "<group>"; }; 54D3A6F32EA4603B001EF4F6 /* template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = template.html; sourceTree = "<group>"; };
54D3A6F52EA4610B001EF4F6 /* CoreUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CoreUI.framework; sourceTree = "<group>"; }; 54D3A6F52EA4610B001EF4F6 /* CoreUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = CoreUI.framework; sourceTree = "<group>"; };
54D3A6FA2EA46588001EF4F6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 54D3A6FA2EA46588001EF4F6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
54D3A6FC2EA465A9001EF4F6 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
54D3A6FF2EA465C4001EF4F6 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
54E0874C2EB1488300979D91 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; };
54E0874E2EB1489100979D91 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
54E087512EB148B700979D91 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
54E087532EB148DB00979D91 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; };
54E087552EB148DF00979D91 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
54E087572EB148E700979D91 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
54E087592EB15DD000979D91 /* style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = style.css; sourceTree = "<group>"; }; 54E087592EB15DD000979D91 /* style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = style.css; sourceTree = "<group>"; };
54EA9EB22EB6CCA300AB6027 /* Config-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Config-debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
54352E872EB6A79A0082F61D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
54B6FFEF2EB6A8E0007397C0 /* CoreUI.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
54442BF12E378B71008A870E /* Frameworks */ = { 54442BF12E378B71008A870E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -145,9 +215,10 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
540B77DC2ED79CC1009E030C /* AndroidXML in Frameworks */,
54B6FFF02EB6AA0F007397C0 /* AssetCarReader.framework in Frameworks */,
54D3A6FE2EA465B4001EF4F6 /* CoreGraphics.framework in Frameworks */, 54D3A6FE2EA465B4001EF4F6 /* CoreGraphics.framework in Frameworks */,
54442C232E378BAF008A870E /* Quartz.framework in Frameworks */, 54442C232E378BAF008A870E /* Quartz.framework in Frameworks */,
5478996F2EB38EBB00F96B80 /* CoreUI.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -155,9 +226,10 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
540B77DE2ED79CC8009E030C /* AndroidXML in Frameworks */,
54B6FFF52EB6AA14007397C0 /* AssetCarReader.framework in Frameworks */,
54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */, 54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */,
54581FD22EB29A0B0043A0B3 /* Quartz.framework in Frameworks */, 54581FD22EB29A0B0043A0B3 /* Quartz.framework in Frameworks */,
547899702EB38EBB00F96B80 /* CoreUI.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -167,37 +239,84 @@
541051562E37AFC10083670B /* src */ = { 541051562E37AFC10083670B /* src */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5405CF5D2EA1199B00613856 /* MetaInfo.swift */, 543899122EDD0F38007C02FC /* Common */,
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */, 543899112EDD0EE2007C02FC /* Data - Android */,
54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */, 543899102EDD0ED1007C02FC /* Data - Apple */,
545459C62EA4773A002892E5 /* AppIcon+Car.swift */, 5438990F2EDD0EA1007C02FC /* Preview */,
5469E11C2EA5930C00D46CE7 /* Entitlements.swift */,
547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */,
547F52E62EB2C41C002B6D5F /* HtmlGenerator.swift */,
547F52EC2EB2C822002B6D5F /* Html+AppInfo.swift */,
547F52EE2EB2C8E8002B6D5F /* Html+Provisioning.swift */,
547F52F32EB2CA05002B6D5F /* Html+Entitlements.swift */,
547F52E32EB2C3D8002B6D5F /* Html+iTunesPurchase.swift */,
547F52E92EB2C672002B6D5F /* Html+FileInfo.swift */,
547F52F62EB2CAC7002B6D5F /* Html+Footer.swift */,
5405CF642EA1376B00613856 /* Zip.swift */,
54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */,
547F52F82EB2CBAB002B6D5F /* Date+Format.swift */,
); );
path = src; path = src;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
5438990F2EDD0EA1007C02FC /* Preview */ = {
isa = PBXGroup;
children = (
5412DECF2EBC283000F9040D /* RuntimeError.swift */,
547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */,
547F52F82EB2CBAB002B6D5F /* Date+Format.swift */,
547F52E62EB2C41C002B6D5F /* PreviewGenerator.swift */,
547F52EC2EB2C822002B6D5F /* Preview+AppInfo.swift */,
5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */,
547F52E32EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift */,
549E3BA32EBC021500ADFF56 /* Preview+TransportSecurity.swift */,
547F52F32EB2CA05002B6D5F /* Preview+Entitlements.swift */,
547F52EE2EB2C8E8002B6D5F /* Preview+Provisioning.swift */,
547F52E92EB2C672002B6D5F /* Preview+FileInfo.swift */,
547F52F62EB2CAC7002B6D5F /* Preview+Footer.swift */,
);
path = Preview;
sourceTree = "<group>";
};
543899102EDD0ED1007C02FC /* Data - Apple */ = {
isa = PBXGroup;
children = (
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */,
54993B892EDBA596008B656D /* Plist+Icon.swift */,
54993B872EDB9A9B008B656D /* Plist+Info.swift */,
54993B922EDBB419008B656D /* Plist+iTunesMetadata.swift */,
54993B942EDBC813008B656D /* Plist+MobileProvision.swift */,
54993B962EDC7C61008B656D /* Provisioning.swift */,
5469E11C2EA5930C00D46CE7 /* Entitlements.swift */,
);
path = "Data - Apple";
sourceTree = "<group>";
};
543899112EDD0EE2007C02FC /* Data - Android */ = {
isa = PBXGroup;
children = (
54CCF59D2EDC9A6800D766F9 /* AndroidSdkMap.swift */,
543899072EDCA223007C02FC /* Apk.swift */,
5438990A2EDCA27F007C02FC /* Apk+Icon.swift */,
540B77D82ED79BB2009E030C /* Apk+Manifest.swift */,
);
path = "Data - Android";
sourceTree = "<group>";
};
543899122EDD0F38007C02FC /* Common */ = {
isa = PBXGroup;
children = (
5405CF5D2EA1199B00613856 /* MetaInfo.swift */,
54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */,
549E3BA02EBAE7D300ADFF56 /* URL+File.swift */,
54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */,
5405CF642EA1376B00613856 /* Zip.swift */,
);
path = Common;
sourceTree = "<group>";
};
54442BEB2E378B71008A870E = { 54442BEB2E378B71008A870E = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
545D9F3D2EB54C6200C38917 /* Config.xcconfig */,
54EA9EB22EB6CCA300AB6027 /* Config-debug.xcconfig */,
547F52FC2EB37F3A002B6D5F /* LICENSE */, 547F52FC2EB37F3A002B6D5F /* LICENSE */,
547F52FB2EB37F10002B6D5F /* README.md */, 547F52FB2EB37F10002B6D5F /* README.md */,
54D3A6F62EA4610B001EF4F6 /* PrivateFrameworks */, 549E3B9E2EBA8FDA00ADFF56 /* CHANGELOG.md */,
541051562E37AFC10083670B /* src */, 541051562E37AFC10083670B /* src */,
54D3A6F42EA46069001EF4F6 /* resources */, 54D3A6F42EA46069001EF4F6 /* resources */,
54442C6F2E378BDD008A870E /* App */, 54442C6F2E378BDD008A870E /* App */,
54442C782E378BE0008A870E /* QLPreview */, 54442C782E378BE0008A870E /* QLPreview */,
54AE5BFE2EB3DB1000B4CFC7 /* QLThumbnail */, 54AE5BFE2EB3DB1000B4CFC7 /* QLThumbnail */,
54B6FFED2EB6A847007397C0 /* AssetCarReader */,
54442C212E378BAF008A870E /* Frameworks */, 54442C212E378BAF008A870E /* Frameworks */,
54442BF52E378B71008A870E /* Products */, 54442BF52E378B71008A870E /* Products */,
); );
@@ -207,8 +326,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
54442BF42E378B71008A870E /* QLAppBundle (debug).app */, 54442BF42E378B71008A870E /* QLAppBundle (debug).app */,
54442C202E378BAF008A870E /* QLPreview.appex */, 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */,
54581FCF2EB29A0B0043A0B3 /* QLThumbnail.appex */, 54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */,
54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -216,16 +336,6 @@
54442C212E378BAF008A870E /* Frameworks */ = { 54442C212E378BAF008A870E /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
543FE5772EB3DAA20059F98B /* Foundation.framework */,
54E087572EB148E700979D91 /* WebKit.framework */,
54E087552EB148DF00979D91 /* Security.framework */,
54E087532EB148DB00979D91 /* QuickLook.framework */,
54E087512EB148B700979D91 /* libz.tbd */,
54E0874E2EB1489100979D91 /* CoreFoundation.framework */,
54E0874C2EB1488300979D91 /* ApplicationServices.framework */,
5485EE362EB1460C009E3905 /* Network.framework */,
54D3A6FF2EA465C4001EF4F6 /* AppKit.framework */,
54D3A6FC2EA465A9001EF4F6 /* CoreServices.framework */,
54D3A6FA2EA46588001EF4F6 /* CoreGraphics.framework */, 54D3A6FA2EA46588001EF4F6 /* CoreGraphics.framework */,
54442C222E378BAF008A870E /* Quartz.framework */, 54442C222E378BAF008A870E /* Quartz.framework */,
54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */, 54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */,
@@ -237,7 +347,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
543FE5752EB3BC740059F98B /* Info.plist */, 543FE5752EB3BC740059F98B /* Info.plist */,
54442C6E2E378BDD008A870E /* App.entitlements */,
54442C6A2E378BDD008A870E /* AppDelegate.swift */, 54442C6A2E378BDD008A870E /* AppDelegate.swift */,
54442C6D2E378BDD008A870E /* MainMenu.xib */, 54442C6D2E378BDD008A870E /* MainMenu.xib */,
); );
@@ -248,7 +357,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
54442C732E378BE0008A870E /* Info.plist */, 54442C732E378BE0008A870E /* Info.plist */,
54442C772E378BE0008A870E /* QLPreview.entitlements */,
54442C742E378BE0008A870E /* PreviewViewController.swift */, 54442C742E378BE0008A870E /* PreviewViewController.swift */,
54442C762E378BE0008A870E /* PreviewViewController.xib */, 54442C762E378BE0008A870E /* PreviewViewController.xib */,
); );
@@ -259,12 +367,21 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
54AE5BFB2EB3DB1000B4CFC7 /* Info.plist */, 54AE5BFB2EB3DB1000B4CFC7 /* Info.plist */,
54AE5BFC2EB3DB1000B4CFC7 /* QLThumbnail.entitlements */,
54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */, 54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */,
); );
path = QLThumbnail; path = QLThumbnail;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
54B6FFED2EB6A847007397C0 /* AssetCarReader */ = {
isa = PBXGroup;
children = (
544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */,
54B6FFEC2EB6A847007397C0 /* AssetCarReader.swift */,
54D3A6F62EA4610B001EF4F6 /* PrivateFrameworks */,
);
path = AssetCarReader;
sourceTree = "<group>";
};
54D3A6F42EA46069001EF4F6 /* resources */ = { 54D3A6F42EA46069001EF4F6 /* resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -286,10 +403,40 @@
}; };
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
54352E852EB6A79A0082F61D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
54442BF32E378B71008A870E /* QLAppBundle */ = { 54352E892EB6A79A0082F61D /* AssetCarReader */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 54442C012E378B71008A870E /* Build configuration list for PBXNativeTarget "QLAppBundle" */; buildConfigurationList = 54352E902EB6A79A0082F61D /* Build configuration list for PBXNativeTarget "AssetCarReader" */;
buildPhases = (
54352E852EB6A79A0082F61D /* Headers */,
54352E862EB6A79A0082F61D /* Sources */,
54352E872EB6A79A0082F61D /* Frameworks */,
54352E882EB6A79A0082F61D /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = AssetCarReader;
packageProductDependencies = (
);
productName = AssetCarReader;
productReference = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */;
productType = "com.apple.product-type.framework";
};
54442BF32E378B71008A870E /* App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 54442C012E378B71008A870E /* Build configuration list for PBXNativeTarget "App" */;
buildPhases = ( buildPhases = (
54442BF02E378B71008A870E /* Sources */, 54442BF02E378B71008A870E /* Sources */,
54442BF12E378B71008A870E /* Frameworks */, 54442BF12E378B71008A870E /* Frameworks */,
@@ -302,7 +449,7 @@
54442C2F2E378BAF008A870E /* PBXTargetDependency */, 54442C2F2E378BAF008A870E /* PBXTargetDependency */,
54581FD92EB29A0B0043A0B3 /* PBXTargetDependency */, 54581FD92EB29A0B0043A0B3 /* PBXTargetDependency */,
); );
name = QLAppBundle; name = App;
packageProductDependencies = ( packageProductDependencies = (
); );
productName = QLApps; productName = QLApps;
@@ -316,16 +463,19 @@
54442C1C2E378BAF008A870E /* Sources */, 54442C1C2E378BAF008A870E /* Sources */,
54442C1D2E378BAF008A870E /* Frameworks */, 54442C1D2E378BAF008A870E /* Frameworks */,
54442C1E2E378BAF008A870E /* Resources */, 54442C1E2E378BAF008A870E /* Resources */,
54B6FFF42EB6AA0F007397C0 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
54B6FFF32EB6AA0F007397C0 /* PBXTargetDependency */,
); );
name = QLPreview; name = "QL Preview";
packageProductDependencies = ( packageProductDependencies = (
540B77DB2ED79CC1009E030C /* AndroidXML */,
); );
productName = QLPreview; productName = QLPreview;
productReference = 54442C202E378BAF008A870E /* QLPreview.appex */; productReference = 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */;
productType = "com.apple.product-type.app-extension"; productType = "com.apple.product-type.app-extension";
}; };
54581FCE2EB29A0B0043A0B3 /* QL Thumbnail */ = { 54581FCE2EB29A0B0043A0B3 /* QL Thumbnail */ = {
@@ -335,16 +485,19 @@
54581FCB2EB29A0B0043A0B3 /* Sources */, 54581FCB2EB29A0B0043A0B3 /* Sources */,
54581FCC2EB29A0B0043A0B3 /* Frameworks */, 54581FCC2EB29A0B0043A0B3 /* Frameworks */,
54581FCD2EB29A0B0043A0B3 /* Resources */, 54581FCD2EB29A0B0043A0B3 /* Resources */,
54B6FFF92EB6AA14007397C0 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
54B6FFF82EB6AA14007397C0 /* PBXTargetDependency */,
); );
name = QLThumbnail; name = "QL Thumbnail";
packageProductDependencies = ( packageProductDependencies = (
540B77DD2ED79CC8009E030C /* AndroidXML */,
); );
productName = QLThumbnail; productName = QLThumbnail;
productReference = 54581FCF2EB29A0B0043A0B3 /* QLThumbnail.appex */; productReference = 54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */;
productType = "com.apple.product-type.app-extension"; productType = "com.apple.product-type.app-extension";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@@ -355,7 +508,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640; LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640; LastUpgradeCheck = 2600;
TargetAttributes = { TargetAttributes = {
54442BF32E378B71008A870E = { 54442BF32E378B71008A870E = {
CreatedOnToolsVersion = 16.4; CreatedOnToolsVersion = 16.4;
@@ -377,19 +530,30 @@
); );
mainGroup = 54442BEB2E378B71008A870E; mainGroup = 54442BEB2E378B71008A870E;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = (
54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */,
);
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = 54442BF52E378B71008A870E /* Products */; productRefGroup = 54442BF52E378B71008A870E /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
54442BF32E378B71008A870E /* QLAppBundle */, 54442BF32E378B71008A870E /* App */,
54442C1F2E378BAF008A870E /* QL Preview */, 54442C1F2E378BAF008A870E /* QL Preview */,
54581FCE2EB29A0B0043A0B3 /* QL Thumbnail */, 54581FCE2EB29A0B0043A0B3 /* QL Thumbnail */,
54352E892EB6A79A0082F61D /* AssetCarReader */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
54352E882EB6A79A0082F61D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
54442BF22E378B71008A870E /* Resources */ = { 54442BF22E378B71008A870E /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -421,6 +585,14 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
54352E862EB6A79A0082F61D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
54B6FFEE2EB6A847007397C0 /* AssetCarReader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
54442BF02E378B71008A870E /* Sources */ = { 54442BF02E378B71008A870E /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -433,23 +605,35 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
543899082EDCA223007C02FC /* Apk.swift in Sources */,
549E3BA42EBC021500ADFF56 /* Preview+TransportSecurity.swift in Sources */,
5412DECE2EBC168600F9040D /* Preview+ArchiveInfo.swift in Sources */,
54D3A6F02EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift in Sources */, 54D3A6F02EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift in Sources */,
547F52E42EB2C3D8002B6D5F /* Html+iTunesPurchase.swift in Sources */, 547F52E42EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift in Sources */,
547F52F72EB2CAC7002B6D5F /* Html+Footer.swift in Sources */, 547F52F72EB2CAC7002B6D5F /* Preview+Footer.swift in Sources */,
54993B8A2EDBA596008B656D /* Plist+Icon.swift in Sources */,
54993B972EDC7C65008B656D /* Provisioning.swift in Sources */,
5469E11D2EA5930C00D46CE7 /* Entitlements.swift in Sources */, 5469E11D2EA5930C00D46CE7 /* Entitlements.swift in Sources */,
54442C792E378BE0008A870E /* PreviewViewController.swift in Sources */, 54442C792E378BE0008A870E /* PreviewViewController.swift in Sources */,
547F52EF2EB2C8E8002B6D5F /* Html+Provisioning.swift in Sources */, 547F52EF2EB2C8E8002B6D5F /* Preview+Provisioning.swift in Sources */,
547F52DE2EB2C15D002B6D5F /* ExpirationStatus.swift in Sources */, 547F52DE2EB2C15D002B6D5F /* ExpirationStatus.swift in Sources */,
54D3A6EE2EA39CC6001EF4F6 /* AppIcon.swift in Sources */, 54D3A6EE2EA39CC6001EF4F6 /* AppIcon.swift in Sources */,
547F52E82EB2C41C002B6D5F /* HtmlGenerator.swift in Sources */, 54993B952EDBC819008B656D /* Plist+MobileProvision.swift in Sources */,
547F52EB2EB2C672002B6D5F /* Html+FileInfo.swift in Sources */, 547F52E82EB2C41C002B6D5F /* PreviewGenerator.swift in Sources */,
547F52ED2EB2C822002B6D5F /* Html+AppInfo.swift in Sources */, 54993B882EDB9AA1008B656D /* Plist+Info.swift in Sources */,
547F52F42EB2CA05002B6D5F /* Html+Entitlements.swift in Sources */, 5438990B2EDCA27F007C02FC /* Apk+Icon.swift in Sources */,
547F52EB2EB2C672002B6D5F /* Preview+FileInfo.swift in Sources */,
54CCF59E2EDC9A6800D766F9 /* AndroidSdkMap.swift in Sources */,
547F52ED2EB2C822002B6D5F /* Preview+AppInfo.swift in Sources */,
549E3BA12EBAE7D300ADFF56 /* URL+File.swift in Sources */,
547F52F42EB2CA05002B6D5F /* Preview+Entitlements.swift in Sources */,
540B77D92ED79BBD009E030C /* Apk+Manifest.swift in Sources */,
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */, 5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */,
547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */, 547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */,
54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */, 54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */,
54993B932EDBB41D008B656D /* Plist+iTunesMetadata.swift in Sources */,
5405CF652EA1376B00613856 /* Zip.swift in Sources */, 5405CF652EA1376B00613856 /* Zip.swift in Sources */,
547899762EB38FD400F96B80 /* AppIcon+Car.swift in Sources */, 5412DED02EBC283000F9040D /* RuntimeError.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -461,8 +645,11 @@
547899722EB38F3D00F96B80 /* MetaInfo.swift in Sources */, 547899722EB38F3D00F96B80 /* MetaInfo.swift in Sources */,
547899732EB38F3D00F96B80 /* NSBezierPath+RoundedRect.swift in Sources */, 547899732EB38F3D00F96B80 /* NSBezierPath+RoundedRect.swift in Sources */,
54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */, 54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */,
543899092EDCA26D007C02FC /* Apk.swift in Sources */,
549E3BA22EBAECD400ADFF56 /* URL+File.swift in Sources */,
547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */, 547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */,
547899772EB38FD400F96B80 /* AppIcon+Car.swift in Sources */, 54993B8B2EDBA75A008B656D /* Plist+Icon.swift in Sources */,
5438990E2EDCB126007C02FC /* Apk+Icon.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -479,6 +666,16 @@
target = 54581FCE2EB29A0B0043A0B3 /* QL Thumbnail */; target = 54581FCE2EB29A0B0043A0B3 /* QL Thumbnail */;
targetProxy = 54581FD82EB29A0B0043A0B3 /* PBXContainerItemProxy */; targetProxy = 54581FD82EB29A0B0043A0B3 /* PBXContainerItemProxy */;
}; };
54B6FFF32EB6AA0F007397C0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 54352E892EB6A79A0082F61D /* AssetCarReader */;
targetProxy = 54B6FFF22EB6AA0F007397C0 /* PBXContainerItemProxy */;
};
54B6FFF82EB6AA14007397C0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 54352E892EB6A79A0082F61D /* AssetCarReader */;
targetProxy = 54B6FFF72EB6AA14007397C0 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
@@ -501,8 +698,79 @@
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
54352E8E2EB6A79A0082F61D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.AssetCarReader;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_MODULE = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
54352E8F2EB6A79A0082F61D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.AssetCarReader;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_MODULE = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
54442BFF2E378B71008A870E /* Debug */ = { 54442BFF2E378B71008A870E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 54EA9EB22EB6CCA300AB6027 /* Config-debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -535,13 +803,12 @@
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1120; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UY657LKNHJ; DEVELOPMENT_TEAM = UY657LKNHJ;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/PrivateFrameworks";
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@@ -557,22 +824,19 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.QLAppBundle.debug;
SDKROOT = macosx; SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/PrivateFrameworks/CoreUI.framework";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
}; };
name = Debug; name = Debug;
}; };
54442C002E378B71008A870E /* Release */ = { 54442C002E378B71008A870E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 545D9F3D2EB54C6200C38917 /* Config.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -605,13 +869,12 @@
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1120; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UY657LKNHJ; DEVELOPMENT_TEAM = UY657LKNHJ;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/PrivateFrameworks";
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = ""; GCC_PREPROCESSOR_DEFINITIONS = "";
@@ -622,30 +885,26 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.1.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.QLAppBundle;
SDKROOT = macosx; SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/PrivateFrameworks/CoreUI.framework";
SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
}; };
name = Release; name = Release;
}; };
54442C022E378B71008A870E /* Debug */ = { 54442C022E378B71008A870E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainNibFile = MainMenu; INFOPLIST_KEY_NSMainNibFile = MainMenu;
INFOPLIST_KEY_NSPrincipalClass = NSApplication; INFOPLIST_KEY_NSPrincipalClass = NSApplication;
@@ -653,8 +912,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited)"; PRODUCT_NAME = "$(inherited) (debug)";
PRODUCT_NAME = "$(TARGET_NAME) (debug)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -665,15 +923,15 @@
54442C032E378B71008A870E /* Release */ = { 54442C032E378B71008A870E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainNibFile = MainMenu; INFOPLIST_KEY_NSMainNibFile = MainMenu;
INFOPLIST_KEY_NSPrincipalClass = NSApplication; INFOPLIST_KEY_NSPrincipalClass = NSApplication;
@@ -681,8 +939,6 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited)";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -693,22 +949,22 @@
54442C322E378BAF008A870E /* Debug */ = { 54442C322E378BAF008A870E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLPreview/QLPreview.entitlements; DEAD_CODE_STRIPPING = YES;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLPreview/Info.plist; INFOPLIST_FILE = QLPreview/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QLPreview; INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME) (debug)";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLPreview"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Preview";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(inherited) Preview Extension";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -719,22 +975,22 @@
54442C332E378BAF008A870E /* Release */ = { 54442C332E378BAF008A870E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLPreview/QLPreview.entitlements; DEAD_CODE_STRIPPING = YES;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLPreview/Info.plist; INFOPLIST_FILE = QLPreview/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QLPreview; INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME)";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLPreview"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Preview";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(inherited) Preview Extension";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -745,22 +1001,22 @@
54581FDB2EB29A0B0043A0B3 /* Debug */ = { 54581FDB2EB29A0B0043A0B3 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLThumbnail/QLThumbnail.entitlements; DEAD_CODE_STRIPPING = YES;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLThumbnail/Info.plist; INFOPLIST_FILE = QLThumbnail/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QLThumbnail; INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME) (debug)";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLThumbnail"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Thumbnail";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(inherited) Thumbnail Extension";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -771,22 +1027,22 @@
54581FDC2EB29A0B0043A0B3 /* Release */ = { 54581FDC2EB29A0B0043A0B3 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLThumbnail/QLThumbnail.entitlements; DEAD_CODE_STRIPPING = YES;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ; "DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLThumbnail/Info.plist; INFOPLIST_FILE = QLThumbnail/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QLThumbnail; INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME)";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLThumbnail"; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).Thumbnail";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(inherited) Thumbnail Extension";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -797,6 +1053,15 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
54352E902EB6A79A0082F61D /* Build configuration list for PBXNativeTarget "AssetCarReader" */ = {
isa = XCConfigurationList;
buildConfigurations = (
54352E8E2EB6A79A0082F61D /* Debug */,
54352E8F2EB6A79A0082F61D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
54442BEF2E378B71008A870E /* Build configuration list for PBXProject "QLAppBundle" */ = { 54442BEF2E378B71008A870E /* Build configuration list for PBXProject "QLAppBundle" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@@ -806,7 +1071,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
54442C012E378B71008A870E /* Build configuration list for PBXNativeTarget "QLAppBundle" */ = { 54442C012E378B71008A870E /* Build configuration list for PBXNativeTarget "App" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
54442C022E378B71008A870E /* Debug */, 54442C022E378B71008A870E /* Debug */,
@@ -834,6 +1099,30 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/relikd/AndroidXML";
requirement = {
kind = exactVersion;
version = 0.9.4;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
540B77DB2ED79CC1009E030C /* AndroidXML */ = {
isa = XCSwiftPackageProductDependency;
package = 54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */;
productName = AndroidXML;
};
540B77DD2ED79CC8009E030C /* AndroidXML */ = {
isa = XCSwiftPackageProductDependency;
package = 54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */;
productName = AndroidXML;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 54442BEC2E378B71008A870E /* Project object */; rootObject = 54442BEC2E378B71008A870E /* Project object */;
} }

View File

@@ -0,0 +1,15 @@
{
"originHash" : "c869761611793a3eebb4e2f56e7aebab4faa8db4159e6116b059292c98af7094",
"pins" : [
{
"identity" : "androidxml",
"kind" : "remoteSourceControl",
"location" : "https://github.com/relikd/AndroidXML",
"state" : {
"revision" : "d9fe646bcc3b05548aebbd20b4eee0af675c129f",
"version" : "0.9.4"
}
}
],
"version" : 3
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES" wasCreatedForAppExtension = "YES"
version = "2.0"> version = "2.0">
<BuildAction <BuildAction
@@ -17,7 +17,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442C1F2E378BAF008A870E" BlueprintIdentifier = "54442C1F2E378BAF008A870E"
BuildableName = "QLPreview.appex" BuildableName = "QLAppBundle Preview Extension.appex"
BlueprintName = "QL Preview" BlueprintName = "QL Preview"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
@@ -32,7 +32,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E" BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app" BuildableName = "QLAppBundle.app"
BlueprintName = "QLAppBundle" BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
@@ -63,7 +63,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E" BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app" BuildableName = "QLAppBundle.app"
BlueprintName = "QLAppBundle" BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
@@ -82,7 +82,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E" BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app" BuildableName = "QLAppBundle.app"
BlueprintName = "QLAppBundle" BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "54581FCE2EB29A0B0043A0B3"
BuildableName = "QLAppBundle Thumbnail Extension.appex"
BlueprintName = "QL Thumbnail"
ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app"
BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app"
BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app"
BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -17,7 +17,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E" BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app" BuildableName = "QLAppBundle.app"
BlueprintName = "QLAppBundle" BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
@@ -46,7 +46,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E" BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app" BuildableName = "QLAppBundle.app"
BlueprintName = "QLAppBundle" BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
@@ -63,7 +63,7 @@
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "54442BF32E378B71008A870E" BlueprintIdentifier = "54442BF32E378B71008A870E"
BuildableName = "QLAppBundle.app" BuildableName = "QLAppBundle.app"
BlueprintName = "QLAppBundle" BlueprintName = "App"
ReferencedContainer = "container:QLAppBundle.xcodeproj"> ReferencedContainer = "container:QLAppBundle.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>

View File

@@ -15,6 +15,10 @@
<string>com.apple.xcode.archive</string> <string>com.apple.xcode.archive</string>
<string>com.opa334.trollstore.tipa</string> <string>com.opa334.trollstore.tipa</string>
<string>dyn.ah62d4rv4ge81k4puqe</string> <string>dyn.ah62d4rv4ge81k4puqe</string>
<string>com.google.android.apk</string>
<string>dyn.ah62d4rv4ge80c6dp</string>
<string>public.archive.apk</string>
<string>dyn.ah62d4rv4ge80c6dpry</string>
</array> </array>
<key>QLSupportsSearchableItems</key> <key>QLSupportsSearchableItems</key>
<false/> <false/>

View File

@@ -11,9 +11,24 @@ class PreviewViewController: NSViewController, QLPreviewingController {
return NSNib.Name("PreviewViewController") return NSNib.Name("PreviewViewController")
} }
/// Load resource file either from user documents dir (if exists) or app bundle (default).
func bundleFile(filename: String, ext: String) throws -> String {
if let userFile = URL.UserModDir?.appendingPathComponent(filename + "." + ext, isDirectory: false), userFile.exists() {
return try String(contentsOf: userFile, encoding: .utf8)
// else: do NOT copy! Breaks on future updates
}
// else, load bundle file
let path = Bundle.main.url(forResource: filename, withExtension: ext)
return try String(contentsOf: path!, encoding: .utf8)
}
func preparePreviewOfFile(at url: URL) async throws { func preparePreviewOfFile(at url: URL) async throws {
let meta = MetaInfo(url) let meta = MetaInfo(url)
let html = HtmlGenerator(meta).applyHtmlTemplate() // throws an exception if appPlist not found. Thus allowing another QuickLook plugin to try
let html = try PreviewGenerator(meta).generate(
template: try bundleFile(filename: "template", ext: "html"),
css: try bundleFile(filename: "style", ext: "css"),
)
// sure, we could use `WKWebView`, but that requires the `com.apple.security.network.client` entitlement // sure, we could use `WKWebView`, but that requires the `com.apple.security.network.client` entitlement
//let web = WKWebView(frame: self.view.bounds) //let web = WKWebView(frame: self.view.bounds)
let web = WebView(frame: self.view.bounds) let web = WebView(frame: self.view.bounds)

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -9,10 +9,13 @@
<key>QLSupportedContentTypes</key> <key>QLSupportedContentTypes</key>
<array> <array>
<string>com.apple.itunes.ipa</string> <string>com.apple.itunes.ipa</string>
<string>com.apple.application-and-system-extension</string>
<string>com.apple.xcode.archive</string> <string>com.apple.xcode.archive</string>
<string>com.opa334.trollstore.tipa</string> <string>com.opa334.trollstore.tipa</string>
<string>dyn.ah62d4rv4ge81k4puqe</string> <string>dyn.ah62d4rv4ge81k4puqe</string>
<string>com.google.android.apk</string>
<string>dyn.ah62d4rv4ge80c6dp</string>
<string>public.archive.apk</string>
<string>dyn.ah62d4rv4ge80c6dpry</string>
</array> </array>
<key>QLThumbnailMinimumDimension</key> <key>QLThumbnailMinimumDimension</key>
<integer>16</integer> <integer>16</integer>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -16,13 +16,9 @@ extension QLThumbnailReply {
} }
class ThumbnailProvider: QLThumbnailProvider { class ThumbnailProvider: QLThumbnailProvider {
// TODO: sadly, this does not seem to work for .xcarchive and .appex
// Probably overwritten by Apple somehow
override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) { override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) {
let meta = MetaInfo(request.fileURL) let meta = MetaInfo(request.fileURL)
let img = AppIcon(meta).extractImage(from: meta.readPlistApp()).withRoundCorners() let img = AppIcon(meta).extractImageForThumbnail().withRoundCorners()
// First way: Draw the thumbnail into the current context, set up with UIKit's coordinate system. // First way: Draw the thumbnail into the current context, set up with UIKit's coordinate system.
let reply = QLThumbnailReply(contextSize: request.maximumSize, currentContextDrawing: { () -> Bool in let reply = QLThumbnailReply(contextSize: request.maximumSize, currentContextDrawing: { () -> Bool in

View File

@@ -6,9 +6,10 @@
QLAppBundle QLAppBundle
=========== ===========
A QuickLook plugin for app bundles (`.ipa`, `.tipa`, `.appex`, `.xcarchive`). A QuickLook plugin for app bundles (`.ipa`, `.tipa`, `.appex`, `.xcarchive`, `.apk`, `.apkm`).
![screenshot](screenshot.png) ![QuickLook for IPA file](screenshot.png)
![QuickLook for APK file](screenshot2.png)
## Why? ## Why?
@@ -24,42 +25,32 @@ I merely want things to be done.
Also, I've removed support for provisioning profiles (`.mobileprovision`, `.provisionprofile`) to focus on app bundles. Also, I've removed support for provisioning profiles (`.mobileprovision`, `.provisionprofile`) to focus on app bundles.
## ToDO
- [ ] support for `.apk` files ## ToDo
- [x] support for `.apk` files
## Features
### Customize HTML / CSS
1. Right click on the app and select "Show Package Contents"
2. Go to `PlugIns` and repeat "Show Package Contents" on the Preview extension.
3. Copy `Contents/Resources/template.html` (or `style.css`)
4. Open `~/Library/Containers/de.relikd.QLAppBundle.Preview/Data/Documents/`
5. Paste the previous file and modify it to your liking
6. `QLAppBundle` will use the new file from now on
## Development notes ## Development notes
### Debug ### Debug
You can show Console logs with `subsystem:de.relikd.QLAppBundle` You can show Console logs with `subsystem:de.relikd.QLAppBundle`
### Compile errors
If you encounter compile errors like:
```
Command SwiftEmitModule failed with a nonzero exit code
```
or
```
Could not build Objective-C module 'ExtensionFoundation'
```
or `ThumbnailProvider` is throwing lots of errors for undefined classes:
remove the `SYSTEM_FRAMEWORK_SEARCH_PATHS` attribute from Project > Build Settings then try to compile again (it will fail).
Afterwards, restore the value in the attribute.
Now, the build index should be up-to-date and the app should compile fine.
I havent figured out the exact issue, consider it a workaround.
It should only be necessary once (or if you delete your `DerivedData` folder).
[1]: https://github.com/ealeksandrov/ProvisionQL [1]: https://github.com/ealeksandrov/ProvisionQL
[2]: https://github.com/ealeksandrov/ProvisionQL/pull/54 [2]: https://github.com/ealeksandrov/ProvisionQL/pull/54

View File

@@ -6,7 +6,7 @@ body {
line-height: 1.3; line-height: 1.3;
} }
.hiddenDiv { .hidden, .not- {
display: none; display: none;
} }

View File

@@ -2,82 +2,100 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<style>__CSS__</style> <style>{{CSS}}</style>
</head> </head>
<body> <body>
<div class="app __AppInfoHidden__"> <h1>{{QuickLookTitle}}</h1>
<h1>__AppInfoTitle__</h1>
<div class="floatLeft icon"><img alt="App icon" src="data:image/png;base64,__AppIcon__"/></div> <div class="app {{AppInfoHidden}}">
<div class="floatLeft icon"><img alt="App icon" src="data:image/png;base64,{{AppIcon}}"/></div>
<div class="floatLeft info"> <div class="floatLeft info">
Name: <strong>__CFBundleName__</strong><br /> Name: <strong>{{AppName}}</strong><br />
Version: __CFBundleShortVersionString__ (__CFBundleVersion__)<br /> Version: {{AppVersion}} ({{AppBuildVer}})<br />
BundleId: __CFBundleIdentifier__<br /> BundleId: {{AppId}}<br />
<div class="__ExtensionTypeHidden__"> <div class="{{AppExtensionTypeHidden}}">
Extension type: __ExtensionType__<br /> Extension type: {{AppExtensionType}}<br />
</div> </div>
DeviceFamily: __UIDeviceFamily__<br /> DeviceFamily: {{AppDeviceFamily}}<br />
SDK: __DTSDKName__<br /> SDK: {{AppSDK}}<br />
Minimum OS Version: __MinimumOSVersion__<br /> Minimum OS Version: {{AppMinOS}}<br />
</div> </div>
<br class="clear" /> <br class="clear" />
</div>
<div class="not-{{AppInfoHidden}}">
Could not find any Info.plist
</div>
<div class="{{ArchiveHidden}}">
<h2>Archive Notes</h2>
<pre>{{ArchiveComment}}</pre>
</div>
<div class="{{iTunesHidden}}">
<h2>iTunes Metadata</h2>
iTunesId: {{iTunesId}}<br />
Title: {{iTunesName}}<br />
Genres: {{iTunesGenres}}<br />
Released: {{iTunesReleaseDate}}<br />
<br />
AppleId: {{iTunesAppleId}}<br />
Purchased: {{iTunesPurchaseDate}}<br />
Price: {{iTunesPrice}}<br />
</div>
<div class="{{TransportSecurityHidden}}">
<h2>App Transport Security</h2> <h2>App Transport Security</h2>
__AppTransportSecurityFormatted__ {{TransportSecurityDict}}
</div> </div>
<div class="__ProvisionHidden__"> <div class="{{EntitlementsHidden}}">
<div class="__AppInfoHidden__">
<h2>Provisioning</h2>
Profile name: <strong>__ProfileName__</strong><br />
</div>
<div class="__ProvisionTitleHidden__">
<h1><span class="__ExpStatus__">__ProfileName__</span></h1>
</div>
Profile UUID: __ProfileUUID__<br />
Profile Type: __ProfilePlatform__ __ProfileType__<br />
Team: __TeamName__ (__TeamIds__)<br />
Creation date: __CreationDateFormatted__<br />
Expiration Date: <strong><span class="__ExpStatus__">__ExpirationDateFormatted__</span></strong><br />
</div>
<div>
<h2>Entitlements</h2> <h2>Entitlements</h2>
<div class="__EntitlementsWarningHidden__ warning"> <div class="warning {{EntitlementsWarningHidden}}">
<strong>Entitlements extraction failed.</strong> <strong>Entitlements extraction failed.</strong>
</div> </div>
__EntitlementsFormatted__ {{EntitlementsDict}}
</div> </div>
<div class="__ProvisionHidden__"> <div class="{{ProvisionHidden}}">
<h2>Provisioning</h2>
Profile name: <strong>{{ProvisionProfileName}}</strong><br />
Profile UUID: {{ProvisionProfileId}}<br />
Profile Type: {{ProvisionProfilePlatform}} {{ProvisionProfileType}}<br />
Team: {{ProvisionTeamName}} ({{ProvisionTeamIds}})<br />
Creation date: {{ProvisionCreateDate}}<br />
Expiration Date: <strong><span class="{{ProvisionExpireStatus}}">{{ProvisionExpireDate}}</span></strong><br />
<h2>Developer Certificates</h2> <h2>Developer Certificates</h2>
__DeveloperCertificatesFormatted__ {{ProvisionDevelopCertificates}}
<h2>Devices ({{ProvisionDeviceCount}})</h2>
{{ProvisionDeviceIds}}
</div> </div>
<div class="__ProvisionHidden__"> <div class="{{ApkFeaturesRequiredHidden}}">
<h2>Devices (__ProvisionedDevicesCount__)</h2> <h2>Features (required)</h2>
__ProvisionedDevicesFormatted__ {{ApkFeaturesRequiredList}}
</div> </div>
<div class="__iTunesHidden__"> <div class="{{ApkFeaturesOptionalHidden}}">
<h2>iTunes Metadata</h2> <h2>Features (optional)</h2>
iTunesId: __iTunesId__<br /> {{ApkFeaturesOptionalList}}
Title: __iTunesName__<br /> </div>
Genres: __iTunesGenres__<br />
Released: __iTunesReleaseDate__<br /> <div class="{{ApkPermissionsHidden}}">
<br /> <h2>Permissions</h2>
AppleId: __iTunesAppleId__<br /> {{ApkPermissionsList}}
Purchased: __iTunesPurchaseDate__<br />
Price: __iTunesPrice__<br />
</div> </div>
<div> <div>
<h2>File info</h2> <h2>File info</h2>
__FileName__<br /> {{FileName}}<br />
__FileInfo__<br /> {{FileSize}}, Modified {{FileModified}}<br />
</div> </div>
<div class="footer"> <div class="footer">
<p>__SrcAppName__ v__BundleShortVersionString__ (__BundleVersion__) (Github: <a href="__SrcLinkUrl__">__SrcLinkName__</a>)</p> <p>{{SrcAppName}} v{{SrcVersion}} ({{SrcBuildVer}}) (Github: <a href="{{SrcLinkUrl}}">{{SrcLinkName}}</a>)</p>
</div> </div>
</body> </body>
</html> </html>

BIN
screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View File

@@ -1,5 +1,6 @@
import Foundation import Foundation
import AppKit // NSImage import AppKit // NSImage
import AssetCarReader // CarReader
import os // OSLog import os // OSLog
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AppIcon") private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AppIcon")
@@ -12,24 +13,44 @@ struct AppIcon {
self.meta = meta self.meta = meta
} }
/// Convenience getter to extract app icon regardless of bundle-type.
func extractImageForThumbnail() -> NSImage {
switch meta.type {
case .IPA, .Archive, .Extension:
extractImage(from: meta.readPlist_Icon()?.filenames)
case .APK:
extractImage(from: meta.readApk_Icon())
}
}
/// Extract image from Android app bundle.
func extractImage(from apkIcon: Apk_Icon?) -> NSImage {
if let data = apkIcon?.data, let img = NSImage(data: data) {
return img
}
return defaultIcon()
}
/// Try multiple methods to extract image. /// Try multiple methods to extract image.
/// This method will always return an image even if none is found, in which case it returns the default image. /// This method will always return an image even if none is found, in which case it returns the default image.
func extractImage(from appPlist: PlistDict?) -> NSImage { func extractImage(from plistIcons: [String]?) -> NSImage {
// no need to unwrap the plist, and most .ipa should include the Artwork anyway // no need to unwrap the plist, and most .ipa should include the Artwork anyway
if meta.type == .IPA { if meta.type == .IPA {
if let data = meta.zipFile!.unzipFile("iTunesArtwork") { if let data = meta.zipFile!.unzipFile("iTunesArtwork") {
os_log(.debug, log: log, "[icon] using iTunesArtwork.") os_log(.debug, log: log, "[icon] using iTunesArtwork.")
return NSImage(data: data)! return NSImage(data: data)!
} }
// else, fallthrough
} }
// Extract image name from app plist // Extract image name from app plist
var plistImgNames = iconNamesFromPlist(appPlist) var plistImgNames = plistIcons ?? []
os_log(.debug, log: log, "[icon] icon names in plist: %{public}@", plistImgNames) os_log(.debug, log: log, "[icon] icon names in plist: %{public}@", plistImgNames)
// If no previous filename works (or empty), try default icon names // If no previous filename works (or empty), try default icon names
plistImgNames.append("Icon") plistImgNames.append("Icon")
plistImgNames.append("icon") plistImgNames.append("icon")
plistImgNames.append("AppIcon")
// First, try if an image file with that name exists. // First, try if an image file with that name exists.
if let actualName = expandImageName(plistImgNames) { if let actualName = expandImageName(plistImgNames) {
@@ -47,55 +68,36 @@ struct AppIcon {
} }
// Fallback to default icon // Fallback to default icon
return defaultIcon()
}
/// Return the bundled default icon `"defaultIcon.png"`
private func defaultIcon() -> NSImage {
let iconURL = Bundle.main.url(forResource: "defaultIcon", withExtension: "png")! let iconURL = Bundle.main.url(forResource: "defaultIcon", withExtension: "png")!
return NSImage(contentsOf: iconURL)! return NSImage(contentsOf: iconURL)!
} }
/// Extract an image from `Assets.car`
func imageFromAssetsCar(_ imageName: String) -> NSImage? {
guard let data = meta.readPayloadFile("Assets.car", osxSubdir: "Resources") else {
return nil
}
return CarReader(data)?.imageFromAssetsCar(imageName)
}
} }
// MARK: - Plist // MARK: - Plist
extension AppIcon { extension AppIcon {
/// Parse app plist to find the bundle icon filename.
/// @param appPlist If `nil`, will load plist on the fly (used for thumbnail)
/// @return Filenames which do not necessarily exist on filesystem. This may include `@2x` and/or no file extension.
private func iconNamesFromPlist(_ appPlist: PlistDict?) -> [String] {
let appPlist = appPlist == nil ? meta.readPlistApp()! : appPlist!
// Check for CFBundleIcons (since 5.0)
if let icons = unpackNameListFromPlistDict(appPlist["CFBundleIcons"]), !icons.isEmpty {
return icons
}
// iPad-only apps
if let icons = unpackNameListFromPlistDict(appPlist["CFBundleIcons~ipad"]), !icons.isEmpty {
return icons
}
// Check for CFBundleIconFiles (since 3.2)
if let icons = appPlist["CFBundleIconFiles"] as? [String], !icons.isEmpty {
return icons
}
// key found on iTunesU app
if let icons = appPlist["Icon files"] as? [String], !icons.isEmpty {
return icons
}
// Check for CFBundleIconFile (legacy, before 3.2)
if let icon = appPlist["CFBundleIconFile"] as? String { // may be nil
return [icon]
}
return [] // [self sortedByResolution:icons];
}
/// Given a filename, search Bundle or Filesystem for files that match. Select the filename with the highest resolution. /// Given a filename, search Bundle or Filesystem for files that match. Select the filename with the highest resolution.
private func expandImageName(_ iconList: [String]) -> String? { private func expandImageName(_ iconList: [String]) -> String? {
var matches: [String] = [] var matches: [String] = []
switch meta.type { switch meta.type {
case .IPA: case .IPA:
guard let zipFile = meta.zipFile else {
// in case unzip in memory is not available, fallback to pattern matching with dynamic suffix
return "Payload/*.app/\(iconList.first!)*"
}
for iconPath in iconList { for iconPath in iconList {
let zipPath = "Payload/*.app/\(iconPath)*" let zipPath = "Payload/*.app/\(iconPath)*"
for zip in zipFile.filesMatching(zipPath) { for zip in meta.zipFile!.filesMatching(zipPath) {
if zip.sizeUncompressed > 0 { if zip.sizeUncompressed > 0 {
matches.append(zip.filepath) matches.append(zip.filepath)
} }
@@ -105,11 +107,13 @@ extension AppIcon {
} }
} }
case .APK:
return nil // handled in `extractImage()`
case .Archive, .Extension: case .Archive, .Extension:
let basePath = meta.effectiveUrl ?? meta.url
for iconPath in iconList { for iconPath in iconList {
let fileName = iconPath.components(separatedBy: "/").last! let fileName = iconPath.components(separatedBy: "/").last!
let parentDir = basePath.appendingPathComponent(iconPath, isDirectory: false).deletingLastPathComponent().path let parentDir = meta.effectiveUrl("Resources", iconPath).parentDir().path
guard let files = try? FileManager.default.contentsOfDirectory(atPath: parentDir) else { guard let files = try? FileManager.default.contentsOfDirectory(atPath: parentDir) else {
continue continue
} }
@@ -131,21 +135,6 @@ extension AppIcon {
return matches.isEmpty ? nil : sortedByResolution(matches).first return matches.isEmpty ? nil : sortedByResolution(matches).first
} }
/// Deep select icons from plist key `CFBundleIcons` and `CFBundleIcons~ipad`
private func unpackNameListFromPlistDict(_ bundleDict: Any?) -> [String]? {
if let bundleDict = bundleDict as? PlistDict {
if let primaryDict = bundleDict["CFBundlePrimaryIcon"] as? PlistDict {
if let icons = primaryDict["CFBundleIconFiles"] as? [String] {
return icons
}
if let name = primaryDict["CFBundleIconName"] as? String { // key found on a .tipa file
return [name]
}
}
}
return nil
}
/// @return lower index means higher resolution. /// @return lower index means higher resolution.
private func resolutionIndex(_ iconName: String) -> Int { private func resolutionIndex(_ iconName: String) -> Int {
let lower = iconName.lowercased() let lower = iconName.lowercased()
@@ -210,7 +199,6 @@ extension NSImage {
/// Convert image to PNG and encode with base64 to be embeded in html output. /// Convert image to PNG and encode with base64 to be embeded in html output.
func asBase64() -> String { func asBase64() -> String {
// appIcon = [self roundCorners:appIcon];
let imageData = tiffRepresentation! let imageData = tiffRepresentation!
let imageRep = NSBitmapImageRep(data: imageData)! let imageRep = NSBitmapImageRep(data: imageData)!
let imageDataPNG = imageRep.representation(using: .png, properties: [:])! let imageDataPNG = imageRep.representation(using: .png, properties: [:])!

134
src/Common/MetaInfo.swift Normal file
View File

@@ -0,0 +1,134 @@
import Foundation
import os // OSLog
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "MetaInfo")
typealias PlistDict = [String: Any] // basically an untyped Dict
// Init QuickLook Type
enum FileType {
case IPA
case Archive
case Extension
case APK
}
struct MetaInfo {
let UTI: String
let url: URL
private let effectiveUrl: URL // if set, will point to the app inside of an archive
let type: FileType
let zipFile: ZipFile? // only set for zipped file types
let isOSX: Bool
/// Use file url and UTI type to generate an info object to pass around.
init(_ url: URL) {
self.url = url
self.UTI = try! url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier ?? "Unknown"
var isOSX = false
var effective: URL? = nil
var zipFile: ZipFile? = nil
switch self.UTI {
case "com.apple.itunes.ipa", "com.opa334.trollstore.tipa", "dyn.ah62d4rv4ge81k4puqe" /* tipa */:
self.type = FileType.IPA
zipFile = ZipFile(self.url.path)
case "com.apple.xcode.archive":
self.type = FileType.Archive
let productsDir = url.appendingPathComponent("Products", isDirectory: true)
if productsDir.exists(), let bundleDir = recursiveSearchInfoPlist(productsDir) {
isOSX = bundleDir.appendingPathComponent("MacOS").exists() && bundleDir.lastPathComponent == "Contents"
effective = bundleDir
} else {
effective = productsDir // this is wrong but dont use `url` either because that will find the `Info.plist` of the archive itself
}
case "com.apple.application-and-system-extension":
self.type = FileType.Extension
if let bundleDir = recursiveSearchInfoPlist(url) {
isOSX = bundleDir.appendingPathComponent("MacOS").exists() && bundleDir.lastPathComponent == "Contents"
effective = bundleDir
}
case "com.google.android.apk", "dyn.ah62d4rv4ge80c6dp" /* apk */, "public.archive.apk", "dyn.ah62d4rv4ge80c6dpry" /* apkm */:
self.type = FileType.APK
zipFile = ZipFile(self.url.path)
default:
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
fatalError()
}
self.isOSX = isOSX
self.zipFile = zipFile
self.effectiveUrl = effective ?? url
}
/// Evaluate path with `osxSubdir` and `filename`
func effectiveUrl(_ osxSubdir: String?, _ filename: String) -> URL {
switch self.type {
case .IPA, .APK:
return effectiveUrl
case .Archive, .Extension:
if isOSX, let osxSubdir {
return effectiveUrl
.appendingPathComponent(osxSubdir, isDirectory: true)
.appendingPathComponent(filename, isDirectory: false)
}
return effectiveUrl.appendingPathComponent(filename, isDirectory: false)
}
}
/// Load a file from bundle into memory. Either by file path or via unzip.
func readPayloadFile(_ filename: String, osxSubdir: String?) -> Data? {
switch self.type {
case .IPA:
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
case .APK:
return nil // not applicable for .apk
case .Archive, .Extension:
return try? Data(contentsOf: self.effectiveUrl(osxSubdir, filename))
}
}
}
// MARK: - Plist
extension Data {
/// Helper for optional chaining.
func asPlistOrNil() -> PlistDict? {
if self.isEmpty {
return nil
}
// var format: PropertyListSerialization.PropertyListFormat = .xml
do {
return try PropertyListSerialization.propertyList(from: self, format: nil) as? PlistDict
} catch {
os_log(.error, log: log, "ERROR reading plist %{public}@", error.localizedDescription)
return nil
}
}
}
// MARK: - helper methods
/// breadth-first search for `Info.plist`
private func recursiveSearchInfoPlist(_ url: URL) -> URL? {
var queue: [URL] = [url]
while !queue.isEmpty {
let current = queue.removeLast()
if current.pathExtension == "framework" {
continue // do not evaluate bundled frameworks
}
if let subfiles = try? FileManager.default.contentsOfDirectory(at: current, includingPropertiesForKeys: []) {
for fname in subfiles {
if fname.lastPathComponent == "Info.plist" {
return fname.parentDir()
}
}
queue.append(contentsOf: subfiles)
}
}
return nil
}

17
src/Common/URL+File.swift Normal file
View File

@@ -0,0 +1,17 @@
import Foundation
extension URL {
/// Folder where user can mofifications to html template
static let UserModDir: URL? =
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
/// Returns `true` if file or folder exists.
@inlinable func exists() -> Bool {
FileManager.default.fileExists(atPath: self.path)
}
/// Returns URL by deleting last path component
@inlinable func parentDir() -> URL {
self.deletingLastPathComponent()
}
}

View File

@@ -224,9 +224,9 @@ private func listZip(_ path: String) -> [ZipEntry] {
} }
guard let endRecord = findCentralDirectory(fp), endRecord.sizeOfCentralDirectory > 0 else { guard let endRecord = findCentralDirectory(fp), endRecord.sizeOfCentralDirectory > 0 else {
return []; return []
} }
return listDirectoryEntries(fp, endRecord); return listDirectoryEntries(fp, endRecord)
} }
/// Find signature for central directory. /// Find signature for central directory.
@@ -354,12 +354,23 @@ struct ZipFile {
/// Unzip file to filesystem. /// Unzip file to filesystem.
/// @param filePath File path inside zip file. /// @param filePath File path inside zip file.
/// @param targetDir Directory in which to unzip the file. /// @param targetDir Directory in which to unzip the file.
func unzipFile(_ filePath: String, toDir targetDir: String) throws { @discardableResult
if let data = self.unzipFile(filePath) { func unzipFile(_ filePath: String, toDir targetDir: String) throws -> String? {
guard let data = self.unzipFile(filePath) else {
return nil
}
let filename = filePath.components(separatedBy: "/").last! let filename = filePath.components(separatedBy: "/").last!
let outputPath = targetDir.appending("/" + filename) let outputPath = targetDir.appending("/" + filename)
os_log(.debug, log: log, "[unzip] write to %{public}@", outputPath) os_log(.debug, log: log, "[unzip] write to %{public}@", outputPath)
try data.write(to: URL(fileURLWithPath: outputPath), options: .atomic) try data.write(to: URL(fileURLWithPath: outputPath), options: .atomic)
} return outputPath
}
/// Extract selected `filePath` inside zip to a new temporary directory and return path to that file.
/// @return Path to extracted data. Returns `nil` or throws exception if data could not be extracted.
func unzipFileToTempDir(_ filePath: String) throws -> String? {
let tmpPath = NSTemporaryDirectory() + "/" + UUID().uuidString
try! FileManager.default.createDirectory(atPath: tmpPath, withIntermediateDirectories: true)
return try unzipFile(filePath, toDir: tmpPath)
} }
} }

View File

@@ -0,0 +1,45 @@
// see https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table
let AndroidSdkMap: [Int: String] = [
1: "1.0",
2: "1.1",
3: "1.5",
4: "1.6",
5: "2.0",
6: "2.0.1",
7: "2.1.x",
8: "2.2.x",
9: "2.3, 2.3.1, 2.3.2",
10: "2.3.3, 2.3.4",
11: "3.0.x",
12: "3.1.x",
13: "3.2",
14: "4.0, 4.0.1, 4.0.2",
15: "4.0.3, 4.0.4",
16: "4.1, 4.1.1",
17: "4.2, 4.2.2",
18: "4.3",
19: "4.4",
20: "4.4W",
21: "5.0",
22: "5.1",
23: "6.0",
24: "7.0",
25: "7.1, 7.1.1",
26: "8.0",
27: "8.1",
28: "9",
29: "10",
30: "11",
31: "12",
32: "12",
33: "13",
34: "14",
35: "15",
36: "16",
// can we assume new versions will stick to this scheme?
37: "17",
38: "18",
39: "19",
40: "20",
]

View File

@@ -0,0 +1,74 @@
import Foundation
import AndroidXML
extension MetaInfo {
/// Read `AndroidManifest.xml` but only extract `appIcon`.
func readApk_Icon() -> Apk_Icon? {
assert(type == .APK)
var apk = Apk(self)
return Apk_Icon(&apk)
}
}
// MARK: - Apk_Icon
/// Representation of `AndroidManifest.xml` (containing only the icon extractor).
/// Seperate from main class because everything else is not needed for `ThumbnailProvider`
struct Apk_Icon {
let path: String
let data: Data
init?(_ apk: inout Apk, iconRef: String? = nil) {
if apk.isApkm, let iconData = apk.mainZip.unzipFile("icon.png") {
path = "icon.png"
data = iconData
return
}
guard let manifest = apk.manifest else {
return nil
}
var ref = iconRef
// no need to parse xml if reference already supplied
if ref == nil {
if let xml = try? AndroidXML.init(data: manifest) {
let parser = xml.parseXml()
try? parser.iterElements({ startTag, attributes in
if startTag == "application" {
ref = try? attributes.get("android:icon")?.resolve(parser.stringPool)
}
}) {_ in}
} else {
// fallback to xml-string parser
ref = ApkXmlIconParser().run(manifest)
}
}
guard let img = apk.resolveIcon(&ref) else {
return nil
}
path = ref!
data = img
}
}
/// Shorter form of `ApkXmlManifestParser` to only exctract the icon reference (used for Thumbnail Provider)
private class ApkXmlIconParser: NSObject, XMLParserDelegate {
var result: String? = nil
func run(_ data: Data) -> String? {
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
return result
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attrs: [String : String] = [:]) {
if elementName == "application" {
result = attrs["android:icon"]
parser.abortParsing()
}
}
}

View File

@@ -0,0 +1,123 @@
import Foundation
import AndroidXML
extension MetaInfo {
/// Read `AndroidManifest.xml` and parse its content
func readApk_Manifest() -> Apk_Manifest? {
assert(type == .APK)
var apk = Apk(self)
return Apk_Manifest.from(&apk)
}
}
// MARK: - Apk_Manifest
/// Representation of `AndroidManifest.xml`.
/// See: <https://developer.android.com/guide/topics/manifest/manifest-element>
struct Apk_Manifest {
var packageId: String? = nil
var appName: String? = nil
var icon: Apk_Icon? = nil
/// Computed property
var appIconData: Data? = nil
var versionName: String? = nil
var versionCode: String? = nil
var sdkVerMin: Int? = nil
var sdkVerTarget: Int? = nil
var featuresRequired: [String] = []
var featuresOptional: [String] = []
var permissions: [String] = []
static func from(_ apk: inout Apk) -> Self? {
guard let manifest = apk.manifest else {
return nil
}
let storage = ApkXmlManifestParser()
if let xml = try? AndroidXML.init(data: manifest) {
let parser = xml.parseXml()
let ignore = XMLParser()
try? parser.iterElements({ startTag, attributes in
if ALLOWED_TAGS.contains(startTag) {
storage.parser(ignore, didStartElement: startTag, namespaceURI: nil, qualifiedName: nil, attributes: try attributes.asDictStr())
}
}) { endTag in
if ALLOWED_TAGS.contains(endTag) {
storage.parser(ignore, didEndElement: endTag, namespaceURI: nil, qualifiedName: nil)
}
}
} else {
// fallback to xml-string parser
let parser = XMLParser(data: manifest)
parser.delegate = storage
parser.parse()
}
var rv = storage.result
apk.resolveName(&rv.appName)
rv.icon = Apk_Icon(&apk, iconRef: storage.iconRef)
return rv
}
}
// keep in sync with `ApkXmlManifestParser` below
private let ALLOWED_TAGS = [
"manifest",
"application",
"uses-feature",
"uses-permission",
"uses-permission-sdk-23",
"uses-sdk",
]
/// Wrapper to use same code for binary-xml and string-xml parsing
private class ApkXmlManifestParser: NSObject, XMLParserDelegate {
private var _scope: [String] = []
var result = Apk_Manifest()
var iconRef: String? = nil
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attrs: [String : String] = [:]) {
// keep in sync with `ALLOWED_TAGS` above
switch elementName {
case "manifest":
if _scope == [] {
result.packageId = attrs["package"] // "org.bundle.id"
result.versionName = attrs["android:versionName"] // "7.62.3"
result.versionCode = attrs["android:versionCode"] // "160700"
// attrs["platformBuildVersionCode"] // "35"
// attrs["platformBuildVersionName"] // "15"
}
case "application":
if _scope == ["manifest"] {
result.appName = attrs["android:label"] // @resource-ref
iconRef = attrs["android:icon"] // @resource-ref
}
case "uses-permission", "uses-permission-sdk-23":
// no "permission" because that will produce duplicates with "uses-permission"
if _scope == ["manifest"], let name = attrs["android:name"] {
result.permissions.append(name)
}
case "uses-feature":
if _scope == ["manifest"], let name = attrs["android:name"] {
if attrs["android:required"] == "false" {
result.featuresOptional.append(name)
} else {
result.featuresRequired.append(name)
}
}
case "uses-sdk":
if _scope == ["manifest"] {
result.sdkVerMin = Int(attrs["android:minSdkVersion"] ?? "1") // "21"
result.sdkVerTarget = Int(attrs["android:targetSdkVersion"] ?? "-1") // "35"
}
default: break // ignore
}
_scope.append(elementName)
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
_scope.removeLast()
}
}

View File

@@ -0,0 +1,130 @@
import Foundation
import AndroidXML
/// Data structure for processing the content of `.apk` files.
struct Apk {
let isApkm: Bool
let mainZip: ZipFile
init(_ meta: MetaInfo) {
isApkm = meta.url.pathExtension.lowercased() == "apkm"
mainZip = meta.zipFile!
}
/// Unzip `AndroidManifest.xml` (once). Data is cached until deconstructor.
lazy var manifest: Data? = { effectiveZip?.unzipFile("AndroidManifest.xml") }()
/// Select zip-file depending on `.apk` or `.apkm` extension
private lazy var effectiveZip: ZipFile? = { isApkm ? nestedZip : mainZip }()
/// `.apkm` may contain multiple `.apk` files. (plus "icon.png" and "info.json" files)
private lazy var nestedZip: ZipFile? = {
if isApkm, let pth = try? mainZip.unzipFileToTempDir("base.apk") {
return ZipFile(pth)
}
return nil
}()
/// Shared instance for resolving resources
private lazy var resourceParser: Tbl_Parser? = {
guard let data = effectiveZip?.unzipFile("resources.arsc"),
let xml = try? AndroidXML.init(data: data), xml.type == .Table else {
return nil
}
return xml.parseTable()
}()
/// Lookup app bundle name / label
mutating func resolveName(_ name: inout String?) {
if let val = name, let ref = try? TblTableRef(val), let parser = resourceParser {
name = parser.getName(ref)
}
}
/// Lookup image path and image data
mutating func resolveIcon(_ iconRef: inout String?) -> Data? {
if let val = iconRef, let ref = try? TblTableRef(val), let parser = resourceParser {
if let iconPath = parser.getIconDirect(ref) ?? parser.getIconIndirect(ref) {
iconRef = iconPath
return effectiveZip?.unzipFile(iconPath)
}
}
return nil
}
}
private extension Tbl_Parser {
func getName(_ ref: TblTableRef) -> String? {
// why the heck are these even allowed?
// apparently there can be references onto references
var ref = ref
while let res = try? self.getResource(ref) {
guard let val = res.entries.first?.entry.value else {
return nil
}
switch val.dataType {
case .Reference: ref = val.asTableRef // and continue
case .String: return val.resolve(self.stringPool)
default: return nil
}
}
return nil
}
/// Lookup resource with matching id. Choose the icon with the highest density.
func getIconDirect(_ ref: TblTableRef) -> String? {
guard let res = try? self.getResource(ref) else {
return nil
}
var best: ResValue? = nil
var bestScore: UInt16 = 0
for e in res.entries {
switch e.config.screenType.density {
case .Default, .any, .None: continue
case let density:
if density.rawValue > bestScore, let val = e.entry.value {
bestScore = density.rawValue
best = val
}
}
}
return best?.resolve(self.stringPool)
}
/// Iterate over all entries and choose best-rated icon file.
/// Rating prefers files which have an attribute name `"app_icon"` or `"ic_launcher"`.
func getIconIndirect(_ ref: TblTableRef) -> String? {
// sadly we cannot just `getResource()` because that can point to an app banner
guard let pkg = try? self.getPackage(ref.package),
var pool = pkg.stringPool(for: .Keys),
let (_, types) = try? pkg.getType(ref.type) else {
return nil
}
// density is 120-640
let rates: [String: UInt16] = [
"app_icon": 1000,
"ic_launcher": 800,
"ic_launcher_foreground": 200,
]
var best: ResValue? = nil
var bestScore: UInt16 = 0
for typ in types {
switch typ.config.screenType.density {
case .any, .None: continue
case let density:
try? typ.iterValues {
if let val = $1.value {
let attrName = pool.getStringCached($1.key)
let score = density.rawValue + (rates[attrName] ?? 0)
if score > bestScore {
bestScore = score
best = val
}
}
}
}
}
return best?.resolve(self.stringPool)
}
}

View File

@@ -1,5 +1,3 @@
import Foundation
/* /*
#!/usr/bin/env python3 #!/usr/bin/env python3
# download: https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/genres # download: https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/genres

View File

@@ -67,6 +67,8 @@ struct Entitlements {
// MARK: - SecCode in-memory reader // MARK: - SecCode in-memory reader
// Same as system call:
// `codesign -d ./binary --entitlements - --xml` or: `codesign -d ./binary --entitlements :-`
/// use in-memory `SecCode` for entitlement extraction /// use in-memory `SecCode` for entitlement extraction
private func getSecCodeEntitlements() -> PlistDict? { private func getSecCodeEntitlements() -> PlistDict? {
let url = URL(fileURLWithPath: self.binaryPath) let url = URL(fileURLWithPath: self.binaryPath)
@@ -84,13 +86,13 @@ struct Entitlements {
// if 'entitlements-dict' key exists, use that one // if 'entitlements-dict' key exists, use that one
os_log(.debug, log: log, "[entitlements] read SecCode 'entitlements-dict' key") os_log(.debug, log: log, "[entitlements] read SecCode 'entitlements-dict' key")
if let plist = requirementInfo[kSecCodeInfoEntitlementsDict as String] as? PlistDict { if let plist = requirementInfo[kSecCodeInfoEntitlementsDict as String] as? PlistDict, !plist.isEmpty {
return plist return plist
} }
// else, fallback to parse data from 'entitlements' key // else, fallback to parse data from 'entitlements' key
os_log(.debug, log: log, "[entitlements] read SecCode 'entitlements' key") os_log(.debug, log: log, "[entitlements] read SecCode 'entitlements' key")
guard let data = requirementInfo[kSecCodeInfoEntitlements as String] as? Data else { guard let data = requirementInfo[kSecCodeInfoEntitlements as String] as? Data, !data.isEmpty else {
return nil return nil
} }
@@ -107,7 +109,10 @@ struct Entitlements {
os_log(.error, log: log, "[entitlements] unpack error for FADE7171 size %lu != %lu", data.count, size) os_log(.error, log: log, "[entitlements] unpack error for FADE7171 size %lu != %lu", data.count, size)
// but try anyway // but try anyway
} }
return data.subdata(in: 8..<data.count).asPlistOrNil() guard let rv = data.subdata(in: 8..<data.count).asPlistOrNil(), !rv.isEmpty else {
return nil
}
return rv
} }

View File

@@ -0,0 +1,65 @@
extension MetaInfo {
/// Read `Info.plist`. (used for `ThumbnailProvider`)
func readPlist_Icon() -> Plist_Icon? {
if let x = self.readPayloadFile("Info.plist", osxSubdir: nil)?.asPlistOrNil() {
return Plist_Icon(x)
}
return nil
}
}
// MARK: - Plist_Icon
/// Representation of `Info.plist` (containing only the icon extractor).
/// Seperate from main class because everything else is not needed for `ThumbnailProvider`
struct Plist_Icon {
let filenames: [String]
init(_ plist: PlistDict) {
filenames = parseIconNames(plist)
}
}
/// Find icon filenames.
/// @return Filenames which do not necessarily exist on filesystem. This may include `@2x` and/or no file extension.
private func parseIconNames(_ plist: PlistDict) -> [String] {
// Check for CFBundleIcons (since 5.0)
if let icons = unpackNameList(plist["CFBundleIcons"]) {
return icons
}
// iPad-only apps
if let icons = unpackNameList(plist["CFBundleIcons~ipad"]) {
return icons
}
// Check for CFBundleIconFiles (since 3.2)
if let icons = plist["CFBundleIconFiles"] as? [String], !icons.isEmpty {
return icons
}
// key found on iTunesU app
if let icons = plist["Icon files"] as? [String], !icons.isEmpty {
return icons
}
// Check for CFBundleIconFile (legacy, before 3.2)
if let icon = plist["CFBundleIconFile"] as? String { // may be nil
return [icon]
}
return []
}
/// Deep select icons from plist key `CFBundleIcons` and `CFBundleIcons~ipad`
/// @return Guarantees a non-empty array (or `nil`)
private func unpackNameList(_ bundleDict: Any?) -> [String]? {
if let bundleDict = bundleDict as? PlistDict {
if let primaryDict = bundleDict["CFBundlePrimaryIcon"] as? PlistDict {
if let icons = primaryDict["CFBundleIconFiles"] as? [String], !icons.isEmpty {
return icons
}
if let name = primaryDict["CFBundleIconName"] as? String { // key found on a .tipa file
return [name]
}
}
}
return nil
}

View File

@@ -0,0 +1,70 @@
import Foundation
extension MetaInfo {
/// Read `Info.plist`. (used for `PreviewProvider`)
func readPlist_Info() -> Plist_Info? {
if let x = self.readPayloadFile("Info.plist", osxSubdir: nil)?.asPlistOrNil() {
return Plist_Info(x, isOSX: isOSX)
}
return nil
}
}
// MARK: - Plist_Info
/// Representation of `Info.plist` of an `.ipa` bundle
struct Plist_Info {
let bundleId: String?
let name: String?
let version: String?
let buildVersion: String?
let exePath: String?
let sdkVersion: String?
let minOS: String?
let extensionType: String?
let icons: [String]
let deviceFamily: [String]
let transportSecurity: PlistDict?
init(_ plist: PlistDict, isOSX: Bool) {
bundleId = plist["CFBundleIdentifier"] as? String
name = plist["CFBundleDisplayName"] as? String ?? plist["CFBundleName"] as? String
version = plist["CFBundleShortVersionString"] as? String
buildVersion = plist["CFBundleVersion"] as? String
exePath = plist["CFBundleExecutable"] as? String
sdkVersion = plist["DTSDKName"] as? String
minOS = plist[isOSX ? "LSMinimumSystemVersion" : "MinimumOSVersion"] as? String
extensionType = (plist["NSExtension"] as? PlistDict)?["NSExtensionPointIdentifier"] as? String
icons = Plist_Icon(plist).filenames
deviceFamily = parseDeviceFamily(plist, isOSX: isOSX)
transportSecurity = plist["NSAppTransportSecurity"] as? PlistDict
}
}
private func parseDeviceFamily(_ plist: PlistDict, isOSX: Bool) -> [String] {
if isOSX {
return plist["CFBundleSupportedPlatforms"] as? [String] ?? ["macOS"]
}
if let platforms = (plist["UIDeviceFamily"] as? [Int])?.compactMap({
switch $0 {
case 1: "iPhone"
case 2: "iPad"
case 3: "TV"
case 4: "Watch"
default: nil
}
}), platforms.count > 0 {
return platforms
}
if let minVersion = plist["MinimumOSVersion"] as? String {
if minVersion.hasPrefix("1.") || minVersion.hasPrefix("2.") || minVersion.hasPrefix("3.") {
return ["iPhone"]
}
}
return []
}

View File

@@ -0,0 +1,61 @@
import Foundation
extension MetaInfo {
/// Read `embedded.mobileprovision` (if available) and decode with CMS decoder.
func readPlist_MobileProvision() -> Plist_MobileProvision? {
guard let provisionData = self.readPayloadFile("embedded.mobileprovision", osxSubdir: nil),
let plist = provisionData.decodeCMS().asPlistOrNil() else {
return nil
}
return Plist_MobileProvision(plist, isOSX: self.isOSX)
}
}
// MARK: - Plist_MobileProvision
/// Representation of `embedded.mobileprovision`
struct Plist_MobileProvision {
let creationDate: Date?
let expireDate: Date?
let profileId: String?
let profileName: String?
/// Something like "Development" or "Distribution (App Store)".
let profileType: String
/// Either "Mac" or "iOS"
let profilePlatform: String
let teamName: String?
let teamIds: [String]
let devices: [String]
let certificates: [ProvisioningCertificate]
let entitlements: PlistDict?
init(_ plist: PlistDict, isOSX: Bool) {
creationDate = plist["CreationDate"] as? Date
expireDate = plist["ExpirationDate"] as? Date
profileId = plist["UUID"] as? String
profileName = plist["Name"] as? String
profileType = parseProfileType(plist, isOSX: isOSX)
profilePlatform = isOSX ? "Mac" : "iOS"
teamName = plist["TeamName"] as? String
teamIds = plist["TeamIdentifier"] as? [String] ?? []
devices = plist["ProvisionedDevices"] as? [String] ?? []
certificates = (plist["DeveloperCertificates"] as? [Data] ?? []).compactMap {
ProvisioningCertificate($0)
}
entitlements = plist["Entitlements"] as? PlistDict
}
}
/// Returns provision type string like "Development" or "Distribution (App Store)".
private func parseProfileType(_ plist: PlistDict, isOSX: Bool) -> String {
let hasDevices = plist["ProvisionedDevices"] is [Any]
if isOSX {
return hasDevices ? "Development" : "Distribution (App Store)"
}
if hasDevices {
let getTaskAllow = (plist["Entitlements"] as? PlistDict)?["get-task-allow"] as? Bool ?? false
return getTaskAllow ? "Development" : "Distribution (Ad Hoc)"
}
let isEnterprise = plist["ProvisionsAllDevices"] as? Bool ?? false
return isEnterprise ? "Enterprise" : "Distribution (App Store)"
}

View File

@@ -0,0 +1,77 @@
import Foundation
extension MetaInfo {
/// Read `iTunesMetadata.plist` (if available)
func readPlist_iTunesMetadata() -> Plist_iTunesMetadata? {
assert(type == .IPA)
// not `readPayloadFile` because plist is in root dir
guard let plist = self.zipFile!.unzipFile("iTunesMetadata.plist")?.asPlistOrNil() else {
return nil
}
return Plist_iTunesMetadata(plist)
}
}
// MARK: - Plist_iTunesMetadata
/// Representation of `iTunesMetadata.plist`
struct Plist_iTunesMetadata {
let appId: Int?
let appName: String?
let price: String?
let genres: [String]
// purchase info
let releaseDate: Date?
let purchaseDate: Date?
// account info
let appleId: String?
let firstName: String?
let lastName: String?
init(_ plist: PlistDict) {
appId = plist["itemId"] as? Int
appName = plist["itemName"] as? String
price = plist["priceDisplay"] as? String
genres = formattedGenres(plist)
// download info
let downloadInfo = plist["com.apple.iTunesStore.downloadInfo"] as? PlistDict
purchaseDate = Date.parseAny(downloadInfo?["purchaseDate"] ?? plist["purchaseDate"])
releaseDate = Date.parseAny(downloadInfo?["releaseDate"] ?? plist["releaseDate"])
// AppleId & purchaser name
let accountInfo = downloadInfo?["accountInfo"] as? PlistDict ?? [:]
appleId = accountInfo["AppleID"] as? String ?? plist["appleId"] as? String
firstName = accountInfo["FirstName"] as? String
lastName = accountInfo["LastName"] as? String
}
/// Returns `"<firstName> <lastName> (<appleId>)"` (with empty values omitted)
var purchaserName: String? {
let fn = firstName ?? ""
let ln = lastName ?? ""
let aid = appleId ?? ""
switch (fn.isEmpty, ln.isEmpty, aid.isEmpty) {
case (true, true, true): return nil
case (true, true, false): return "\(aid)"
case (_, _, false): return "\(fn) \(ln) (\(aid))"
case (_, _, true): return "\(fn) \(ln)"
}
}
}
/// Concatenate all (sub)genres into flat list.
private func formattedGenres(_ plist: PlistDict) -> [String] {
var genres: [String] = []
let genreId = plist["genreId"] as? Int ?? 0
if let mainGenre = AppCategories[genreId] ?? plist["genre"] as? String {
genres.append(mainGenre)
}
for subgenre in plist["subgenres"] as? [PlistDict] ?? [] {
let subgenreId = subgenre["genreId"] as? Int ?? 0
if let subgenreStr = AppCategories[subgenreId] ?? subgenre["genre"] as? String {
genres.append(subgenreStr)
}
}
return genres
}

View File

@@ -0,0 +1,58 @@
import Foundation
import os // OSLog
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Provisioning")
extension Data {
/// In-memory decode of `embedded.mobileprovision`
func decodeCMS() -> Data {
var decoder: CMSDecoder? = nil
CMSDecoderCreate(&decoder)
return self.withUnsafeBytes { ptr in
CMSDecoderUpdateMessage(decoder!, ptr.baseAddress!, self.count)
CMSDecoderFinalizeMessage(decoder!)
var dataRef: CFData?
CMSDecoderCopyContent(decoder!, &dataRef)
return Data(referencing: dataRef!)
}
}
}
struct ProvisioningCertificate {
let subject: String
let expiration: Date?
/// Parse subject and expiration date from certificate.
init?(_ data: Data) {
guard let cert = SecCertificateCreateWithData(nil, data as CFData) else {
return nil
}
guard let subj = SecCertificateCopySubjectSummary(cert) as? String else {
os_log(.error, log: log, "Could not get subject from certificate")
return nil
}
subject = subj
expiration = parseInvalidityDate(cert, subject: subj)
}
}
/// Process a single certificate. Extract invalidity / expiration date.
/// @param subject just used for printing error logs.
private func parseInvalidityDate(_ certificate: SecCertificate, subject: String) -> Date? {
var error: Unmanaged<CFError>?
guard let outerDict = SecCertificateCopyValues(certificate, [kSecOIDInvalidityDate] as CFArray, &error) as? PlistDict else {
os_log(.error, log: log, "Could not get values in '%{public}@' certificate, error = %{public}@", subject, error?.takeUnretainedValue().localizedDescription ?? "unknown error")
return nil
}
guard let innerDict = outerDict[kSecOIDInvalidityDate as String] as? PlistDict else {
os_log(.error, log: log, "No invalidity values in '%{public}@' certificate, dictionary = %{public}@", subject, outerDict)
return nil
}
// NOTE: the invalidity date type of kSecPropertyTypeDate is documented as a CFStringRef in the "Certificate, Key, and Trust Services Reference".
// In reality, it's a __NSTaggedDate (presumably a tagged pointer representing an NSDate.) But to be sure, we'll check:
guard let dateString = innerDict[kSecPropertyKeyValue as String] else {
os_log(.error, log: log, "No invalidity date in '%{public}@' certificate, dictionary = %{public}@", subject, innerDict)
return nil
}
return Date.parseAny(dateString)
}

View File

@@ -1,103 +0,0 @@
import Foundation
/// Print recursive tree of key-value mappings.
private func recursiveDict(_ dictionary: [String: Any], withReplacements replacements: [String: String] = [:], _ level: Int = 0) -> String {
var output = ""
for (key, value) in dictionary {
let localizedKey = replacements[key] ?? key
for _ in 0..<level {
output += (level == 1) ? "- " : "&nbsp;&nbsp;"
}
if let subDict = value as? [String: Any] {
output += "\(localizedKey):<div class=\"list\">\n"
output += recursiveDict(subDict, withReplacements: replacements, level + 1)
output += "</div>\n"
} else if let number = value as? NSNumber {
output += "\(localizedKey): \(number.boolValue ? "YES" : "NO")<br />"
} else {
output += "\(localizedKey): \(value)<br />"
}
}
return output
}
extension HtmlGenerator {
/// @return List of ATS flags.
private func formattedAppTransportSecurity(_ appPlist: PlistDict) -> String {
if let value = appPlist["NSAppTransportSecurity"] as? PlistDict {
let localizedKeys = [
"NSAllowsArbitraryLoads": "Allows Arbitrary Loads",
"NSAllowsArbitraryLoadsForMedia": "Allows Arbitrary Loads for Media",
"NSAllowsArbitraryLoadsInWebContent": "Allows Arbitrary Loads in Web Content",
"NSAllowsLocalNetworking": "Allows Local Networking",
"NSExceptionDomains": "Exception Domains",
"NSIncludesSubdomains": "Includes Subdomains",
"NSRequiresCertificateTransparency": "Requires Certificate Transparency",
"NSExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
"NSThirdPartyExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSThirdPartyExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSThirdPartyExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
]
return "<div class=\"list\">\(recursiveDict(value, withReplacements: localizedKeys))</div>"
}
let sdkName = appPlist["DTSDKName"] as? String ?? "0"
let sdkNumber = Double(sdkName.trimmingCharacters(in: .letters)) ?? 0
if sdkNumber < 9.0 {
return "Not applicable before iOS 9.0"
}
return "No exceptions"
}
/// Process info stored in `Info.plist`
mutating func procAppInfo(_ appPlist: PlistDict?) {
guard let appPlist else {
self.apply([
"AppInfoHidden": "hiddenDiv",
"ProvisionTitleHidden": "",
])
return
}
var platforms = (appPlist["UIDeviceFamily"] as? [Int])?.compactMap({
switch $0 {
case 1: return "iPhone"
case 2: return "iPad"
case 3: return "TV"
case 4: return "Watch"
default: return nil
}
}).joined(separator: ", ")
let minVersion = appPlist["MinimumOSVersion"] as? String ?? ""
if platforms?.isEmpty ?? true, minVersion.hasPrefix("1.") || minVersion.hasPrefix("2.") || minVersion.hasPrefix("3.") {
platforms = "iPhone"
}
let extensionType = (appPlist["NSExtension"] as? PlistDict)?["NSExtensionPointIdentifier"] as? String
self.apply([
"AppInfoHidden": "",
"ProvisionTitleHidden": "hiddenDiv",
"CFBundleName": appPlist["CFBundleDisplayName"] as? String ?? appPlist["CFBundleName"] as? String ?? "",
"CFBundleShortVersionString": appPlist["CFBundleShortVersionString"] as? String ?? "",
"CFBundleVersion": appPlist["CFBundleVersion"] as? String ?? "",
"CFBundleIdentifier": appPlist["CFBundleIdentifier"] as? String ?? "",
"ExtensionTypeHidden": extensionType != nil ? "" : "hiddenDiv",
"ExtensionType": extensionType ?? "",
"UIDeviceFamily": platforms ?? "",
"DTSDKName": appPlist["DTSDKName"] as? String ?? "",
"MinimumOSVersion": minVersion,
"AppTransportSecurityFormatted": formattedAppTransportSecurity(appPlist),
])
}
}

View File

@@ -1,36 +0,0 @@
import Foundation
extension HtmlGenerator {
/// Search for app binary and run `codesign` on it.
private func readEntitlements(_ meta: MetaInfo, _ bundleExecutable: String?) -> Entitlements {
guard let bundleExecutable else {
return Entitlements.withoutBinary()
}
switch meta.type {
case .IPA:
let tmpPath = NSTemporaryDirectory() + "/" + UUID().uuidString
try! FileManager.default.createDirectory(atPath: tmpPath, withIntermediateDirectories: true)
defer {
try? FileManager.default.removeItem(atPath: tmpPath)
}
try! meta.zipFile!.unzipFile("Payload/*.app/\(bundleExecutable)", toDir: tmpPath)
return Entitlements(forBinary: tmpPath + "/" + bundleExecutable)
case .Archive:
return Entitlements(forBinary: meta.effectiveUrl!.path + "/" + bundleExecutable)
case .Extension:
return Entitlements(forBinary: meta.url.path + "/" + bundleExecutable)
}
}
/// Process compiled binary and provision plist to extract `Entitlements`
mutating func procEntitlements(_ meta: MetaInfo, _ appPlist: PlistDict?, _ provisionPlist: PlistDict?) {
var entitlements = readEntitlements(meta, appPlist?["CFBundleExecutable"] as? String)
entitlements.applyFallbackIfNeeded(provisionPlist?["Entitlements"] as? PlistDict)
self.apply([
"EntitlementsWarningHidden": entitlements.hasError ? "" : "hiddenDiv",
"EntitlementsFormatted": entitlements.html ?? "No Entitlements",
])
}
}

View File

@@ -1,160 +0,0 @@
import Foundation
import os // OSLog
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Html+Certificates")
extension MetaInfo {
/// Read `embedded.mobileprovision` file and decode with CMS decoder.
func readPlistProvision() -> PlistDict? {
guard let provisionData = self.readPayloadFile("embedded.mobileprovision") else {
os_log(.info, log: log, "No embedded.mobileprovision file for %{public}@", self.url.path)
return nil
}
var decoder: CMSDecoder? = nil
CMSDecoderCreate(&decoder)
let data = provisionData.withUnsafeBytes { ptr in
CMSDecoderUpdateMessage(decoder!, ptr.baseAddress!, provisionData.count)
CMSDecoderFinalizeMessage(decoder!)
var dataRef: CFData?
CMSDecoderCopyContent(decoder!, &dataRef)
return Data(referencing: dataRef!)
}
return data.asPlistOrNil()
}
}
extension HtmlGenerator {
// MARK: - Certificates
/// Process a single certificate. Extract invalidity / expiration date.
/// @param subject just used for printing error logs.
private func getCertificateInvalidityDate(_ certificate: SecCertificate, subject: String) -> Date? {
var error: Unmanaged<CFError>?
guard let outerDict = SecCertificateCopyValues(certificate, [kSecOIDInvalidityDate] as CFArray, &error) as? PlistDict else {
os_log(.error, log: log, "Could not get values in '%{public}@' certificate, error = %{public}@", subject, error?.takeUnretainedValue().localizedDescription ?? "unknown error")
return nil
}
guard let innerDict = outerDict[kSecOIDInvalidityDate as String] as? PlistDict else {
os_log(.error, log: log, "No invalidity values in '%{public}@' certificate, dictionary = %{public}@", subject, outerDict)
return nil
}
// NOTE: the invalidity date type of kSecPropertyTypeDate is documented as a CFStringRef in the "Certificate, Key, and Trust Services Reference".
// In reality, it's a __NSTaggedDate (presumably a tagged pointer representing an NSDate.) But to be sure, we'll check:
guard let dateString = innerDict[kSecPropertyKeyValue as String] else {
os_log(.error, log: log, "No invalidity date in '%{public}@' certificate, dictionary = %{public}@", subject, innerDict)
return nil
}
return Date.parseAny(dateString);
}
/// Process list of all certificates. Return a two column table with subject and expiration date.
private func getCertificateList(_ provisionPlist: PlistDict) -> [TableRow] {
guard let certs = provisionPlist["DeveloperCertificates"] as? [Data] else {
return []
}
return certs.compactMap {
guard let cert = SecCertificateCreateWithData(nil, $0 as CFData) else {
return nil
}
guard let subject = SecCertificateCopySubjectSummary(cert) as? String else {
os_log(.error, log: log, "Could not get subject from certificate")
return nil
}
let expiration: String
if let invalidityDate = getCertificateInvalidityDate(cert, subject: subject) {
expiration = invalidityDate.relativeExpirationDateString()
} else {
expiration = "<span class='warning'>No invalidity date in certificate</span>"
}
return TableRow([subject, expiration])
}.sorted { $0[0] < $1[0] }
}
// MARK: - Provisioning
/// Returns provision type string like "Development" or "Distribution (App Store)".
private func stringForProfileType(_ provisionPlist: PlistDict, isOSX: Bool) -> String {
let hasDevices = provisionPlist["ProvisionedDevices"] is [Any]
if isOSX {
return hasDevices ? "Development" : "Distribution (App Store)"
}
if hasDevices {
let getTaskAllow = (provisionPlist["Entitlements"] as? PlistDict)?["get-task-allow"] as? Bool ?? false
return getTaskAllow ? "Development" : "Distribution (Ad Hoc)"
}
let isEnterprise = provisionPlist["ProvisionsAllDevices"] as? Bool ?? false
return isEnterprise ? "Enterprise" : "Distribution (App Store)"
}
/// Enumerate all entries from provison plist with key `ProvisionedDevices`
private func getDeviceList(_ provisionPlist: PlistDict) -> [TableRow] {
guard let devArr = provisionPlist["ProvisionedDevices"] as? [String] else {
return []
}
var currentPrefix: String? = nil
return devArr.sorted().map { device in
// compute the prefix for the first column of the table
let displayPrefix: String
let devicePrefix = String(device.prefix(1))
if currentPrefix != devicePrefix {
currentPrefix = devicePrefix
displayPrefix = "\(devicePrefix)"
} else {
displayPrefix = ""
}
return [displayPrefix, device]
}
}
/// Process info stored in `embedded.mobileprovision`
mutating func procProvision(_ provisionPlist: PlistDict?, isOSX: Bool) {
guard let provisionPlist else {
self.apply(["ProvisionHidden": "hiddenDiv"])
return
}
let creationDate = provisionPlist["CreationDate"] as? Date
let expireDate = provisionPlist["ExpirationDate"] as? Date
let devices = getDeviceList(provisionPlist)
let certs = getCertificateList(provisionPlist)
self.apply([
"ProvisionHidden": "",
"ProfileName": provisionPlist["Name"] as? String ?? "",
"ProfileUUID": provisionPlist["UUID"] as? String ?? "",
"TeamName": provisionPlist["TeamName"] as? String ?? "<em>Team name not available</em>",
"TeamIds": (provisionPlist["TeamIdentifier"] as? [String])?.joined(separator: ", ") ?? "<em>Team ID not available</em>",
"CreationDateFormatted": creationDate?.formattedCreationDate() ?? "",
"ExpirationDateFormatted": expireDate?.formattedExpirationDate() ?? "",
"ExpStatus": ExpirationStatus(expireDate).cssClass(),
"ProfilePlatform": isOSX ? "Mac" : "iOS",
"ProfileType": stringForProfileType(provisionPlist, isOSX: isOSX),
"ProvisionedDevicesCount": devices.isEmpty ? "No Devices" : "\(devices.count) Device\(devices.count == 1 ? "" : "s")",
"ProvisionedDevicesFormatted": devices.isEmpty ? "Distribution Profile" : formatAsTable(devices, header: ["", "UDID"]),
"DeveloperCertificatesFormatted": certs.isEmpty ? "No Developer Certificates" : formatAsTable(certs),
])
}
}
private typealias TableRow = [String]
/// Print html table with arbitrary number of columns
/// @param header If set, start the table with a `tr` column row.
private func formatAsTable(_ data: [[String]], header: TableRow? = nil) -> String {
var table = "<table>\n"
if let header = header {
table += "<tr><th>\(header.joined(separator: "</th><th>"))</th></tr>\n"
}
for row in data {
table += "<tr><td>\(row.joined(separator: "</td><td>"))</td></tr>\n"
}
return table + "</table>\n"
}

View File

@@ -1,70 +0,0 @@
import Foundation
extension MetaInfo {
/// Read `iTunesMetadata.plist` if available
func readPlistItunes() -> PlistDict? {
switch self.type {
case .IPA:
// not `readPayloadFile` because plist is in root dir
return self.zipFile!.unzipFile("iTunesMetadata.plist")?.asPlistOrNil()
case .Archive, .Extension:
return nil
}
}
}
extension HtmlGenerator {
/// Concatenate all (sub)genres into a comma separated list.
private func formattedGenres(_ itunesPlist: PlistDict) -> String {
var genres: [String] = []
let genreId = itunesPlist["genreId"] as? Int ?? 0
if let mainGenre = AppCategories[genreId] ?? itunesPlist["genre"] as? String {
genres.append(mainGenre)
}
for subgenre in itunesPlist["subgenres"] as? [PlistDict] ?? [] {
let subgenreId = subgenre["genreId"] as? Int ?? 0
if let subgenreStr = AppCategories[subgenreId] ?? subgenre["genre"] as? String {
genres.append(subgenreStr)
}
}
return genres.joined(separator: ", ")
}
/// Process info stored in `iTunesMetadata.plist`
mutating func procItunesMeta(_ itunesPlist: PlistDict?) {
guard let itunesPlist else {
self.apply(["iTunesHidden": "hiddenDiv"])
return
}
let downloadInfo = itunesPlist["com.apple.iTunesStore.downloadInfo"] as? PlistDict
let accountInfo = downloadInfo?["accountInfo"] as? PlistDict ?? [:]
let purchaseDate = Date.parseAny(downloadInfo?["purchaseDate"] ?? itunesPlist["purchaseDate"])
let releaseDate = Date.parseAny(downloadInfo?["releaseDate"] ?? itunesPlist["releaseDate"])
// AppleId & purchaser name
let appleId = accountInfo["AppleID"] as? String ?? itunesPlist["appleId"] as? String ?? ""
let firstName = accountInfo["FirstName"] as? String ?? ""
let lastName = accountInfo["LastName"] as? String ?? ""
let name: String
if !firstName.isEmpty || !lastName.isEmpty {
name = "\(firstName) \(lastName) (\(appleId))"
} else {
name = appleId
}
self.apply([
"iTunesHidden": "",
"iTunesId": (itunesPlist["itemId"] as? Int)?.description ?? "", // description]
"iTunesName": itunesPlist["itemName"] as? String ?? "",
"iTunesGenres": formattedGenres(itunesPlist),
"iTunesReleaseDate": releaseDate?.mediumFormat() ?? "",
"iTunesAppleId": name,
"iTunesPurchaseDate": purchaseDate?.mediumFormat() ?? "",
"iTunesPrice": itunesPlist["priceDisplay"] as? String ?? "",
])
}
}

View File

@@ -1,71 +0,0 @@
import Foundation
struct HtmlGenerator {
var data: [String: String] = [:] // used for TAG replacements
let meta: MetaInfo
init(_ meta: MetaInfo) {
self.meta = meta
let plistApp = meta.readPlistApp()
let plistItunes = meta.readPlistItunes()
let plistProvision = meta.readPlistProvision()
data["AppInfoTitle"] = stringForFileType(meta)
procAppInfo(plistApp)
procItunesMeta(plistItunes)
procProvision(plistProvision, isOSX: meta.isOSX)
procEntitlements(meta, plistApp, plistProvision)
procFileInfo(meta.url)
procFooterInfo()
// App Icon (last, because the image uses a lot of memory)
data["AppIcon"] = AppIcon(meta).extractImage(from: plistApp).withRoundCorners().asBase64()
// insert CSS styles
let cssURL = Bundle.main.url(forResource: "style", withExtension: "css")!
data["CSS"] = try! String(contentsOf: cssURL, encoding: .utf8)
}
mutating func apply(_ values: [String: String]) {
data.merge(values) { (_, new) in new }
}
/// Title of the preview window
private func stringForFileType(_ meta: MetaInfo) -> String {
switch meta.type {
case .IPA: return "App info"
case .Archive: return "Archive info"
case .Extension: return "App extension info"
}
}
/// prepare html, replace values
func applyHtmlTemplate() -> String {
let templateURL = Bundle.main.url(forResource: "template", withExtension: "html")!
let html = try! String(contentsOf: templateURL, encoding: .utf8)
// this is less efficient
// for (key, value) in templateValues {
// html = html.replacingOccurrences(of: "__\(key)__", with: value)
// }
var rv = ""
var prevLoc = html.startIndex
let regex = try! NSRegularExpression(pattern: "__[^ _]{1,40}?__")
regex.enumerateMatches(in: html, range: NSRange(location: 0, length: html.count), using: { match, flags, stop in
let start = html.index(html.startIndex, offsetBy: match!.range.lowerBound)
let key = String(html[html.index(start, offsetBy: 2) ..< html.index(start, offsetBy: match!.range.length - 2)])
// append unrelated text up to this key
rv.append(contentsOf: html[prevLoc ..< start])
prevLoc = html.index(start, offsetBy: match!.range.length)
// append key if exists (else remove template-key)
if let value = data[key] {
rv.append(value)
} else {
// os_log(.debug, log: log, "unknown template key: %{public}@", key)
}
})
// append remaining text
rv.append(contentsOf: html[prevLoc ..< html.endIndex])
return rv
}
}

View File

@@ -1,102 +0,0 @@
import Foundation
import os // OSLog
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "MetaInfo")
typealias PlistDict = [String: Any] // basically an untyped Dict
// Init QuickLook Type
enum FileType {
case IPA
case Archive
case Extension
}
struct MetaInfo {
let UTI: String
let url: URL
let effectiveUrl: URL? // if set, will point to the app inside of an archive
let type: FileType
let zipFile: ZipFile? // only set for zipped file types
let isOSX = false // relict of the past when ProvisionQL also processed provision profiles
/// Use file url and UTI type to generate an info object to pass around.
init(_ url: URL) {
self.url = url
self.UTI = try! url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier ?? "Unknown"
var effective: URL? = nil
var zipFile: ZipFile? = nil
switch self.UTI {
case "com.apple.itunes.ipa", "com.opa334.trollstore.tipa", "dyn.ah62d4rv4ge81k4puqe":
self.type = FileType.IPA;
zipFile = ZipFile(self.url.path);
case "com.apple.xcode.archive":
self.type = FileType.Archive;
effective = appPathForArchive(self.url);
case "com.apple.application-and-system-extension":
self.type = FileType.Extension;
default:
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
fatalError()
}
self.zipFile = zipFile
self.effectiveUrl = effective
}
/// Load a file from bundle into memory. Either by file path or via unzip.
func readPayloadFile(_ filename: String) -> Data? {
switch (self.type) {
case .IPA:
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
case .Archive:
return try? Data(contentsOf: effectiveUrl!.appendingPathComponent(filename))
case .Extension:
return try? Data(contentsOf: url.appendingPathComponent(filename))
}
}
/// Read app default `Info.plist`. (used for both, Preview and Thumbnail)
func readPlistApp() -> PlistDict? {
switch self.type {
case .IPA, .Archive, .Extension:
return self.readPayloadFile("Info.plist")?.asPlistOrNil()
}
}
}
// MARK: - Plist
extension Data {
/// Helper for optional chaining.
func asPlistOrNil() -> PlistDict? {
if self.isEmpty {
return nil
}
// var format: PropertyListSerialization.PropertyListFormat = .xml
do {
return try PropertyListSerialization.propertyList(from: self, format: nil) as? PlistDict
} catch {
os_log(.error, log: log, "ERROR reading plist %{public}@", error.localizedDescription)
return nil
}
}
}
// MARK: - Meta data for QuickLook
/// Search an archive for the .app or .ipa bundle.
private func appPathForArchive(_ url: URL) -> URL? {
let appsDir = url.appendingPathComponent("Products/Applications/")
if FileManager.default.fileExists(atPath: appsDir.path) {
if let x = try? FileManager.default.contentsOfDirectory(at: appsDir, includingPropertiesForKeys: nil), !x.isEmpty {
return x.first
}
}
return nil;
}

View File

@@ -0,0 +1,55 @@
import Foundation
extension PreviewGenerator {
/// Process info stored in `Info.plist`
mutating func procAppInfoApple(_ appPlist: Plist_Info) {
self.apply([
"AppInfoHidden": CLASS_VISIBLE,
"AppName": appPlist.name ?? "",
"AppVersion": appPlist.version ?? "",
"AppBuildVer": appPlist.buildVersion ?? "",
"AppId": appPlist.bundleId ?? "",
"AppExtensionTypeHidden": appPlist.extensionType != nil ? CLASS_VISIBLE : CLASS_HIDDEN,
"AppExtensionType": appPlist.extensionType ?? "",
"AppDeviceFamily": appPlist.deviceFamily.joined(separator: ", "),
"AppSDK": appPlist.sdkVersion ?? "",
"AppMinOS": appPlist.minOS ?? "",
])
}
/// Process info stored in `AndroidManifest.xml`
mutating func procAppInfoAndroid(_ manifest: Apk_Manifest) {
let featReq = manifest.featuresRequired
let featOpt = manifest.featuresOptional
let perms = manifest.permissions
func asList(_ list: [String]) -> String {
"<pre>\(list.joined(separator: "\n"))</pre>"
}
func resolveSDK(_ sdk: Int?) -> String {
sdk == nil ? "" : "\(sdk!) (Android \(AndroidSdkMap[sdk!] ?? "?"))"
}
self.apply([
"AppInfoHidden": CLASS_VISIBLE,
"AppName": manifest.appName ?? "",
"AppVersion": manifest.versionName ?? "",
"AppBuildVer": manifest.versionCode ?? "",
"AppId": manifest.packageId ?? "",
"ApkFeaturesRequiredHidden": featReq.isEmpty ? CLASS_HIDDEN : CLASS_VISIBLE,
"ApkFeaturesRequiredList": asList(featReq),
"ApkFeaturesOptionalHidden": featOpt.isEmpty ? CLASS_HIDDEN : CLASS_VISIBLE,
"ApkFeaturesOptionalList": asList(featOpt),
"ApkPermissionsHidden": perms.isEmpty ? CLASS_HIDDEN : CLASS_VISIBLE,
"ApkPermissionsList": asList(perms),
"AppDeviceFamily": "Android",
"AppSDK": resolveSDK(manifest.sdkVerTarget),
"AppMinOS": resolveSDK(manifest.sdkVerMin),
])
}
}

View File

@@ -0,0 +1,28 @@
import Foundation
extension MetaInfo {
/// Read `Info.plist` if type `.Archive`
func readPlistXCArchive() -> PlistDict? {
switch self.type {
case .Archive:
// not `readPayloadFile` because plist is in root dir
return try? Data(contentsOf: self.url.appendingPathComponent("Info.plist", isDirectory: false)).asPlistOrNil()
case .IPA, .Extension, .APK:
return nil
}
}
}
extension PreviewGenerator {
/// Process info of `.xcarchive` stored in root `Info.plist`
mutating func procArchiveInfo(_ archivePlist: PlistDict?) {
guard let archivePlist, let comment = archivePlist["Comment"] as? String else {
return
}
self.apply([
"ArchiveHidden": CLASS_VISIBLE,
"ArchiveComment": comment,
])
}
}

View File

@@ -0,0 +1,39 @@
import Foundation
extension PreviewGenerator {
/// Search for app binary and run `codesign` on it.
private func readEntitlements(_ meta: MetaInfo, _ bundleExecutable: String?) -> Entitlements {
if let exe = bundleExecutable {
switch meta.type {
case .IPA:
if let tmpPath = try? meta.zipFile!.unzipFileToTempDir("Payload/*.app/\(exe)") {
defer {
try? FileManager.default.removeItem(atPath: tmpPath)
}
return Entitlements(forBinary: tmpPath + "/" + exe)
}
case .Archive, .Extension:
return Entitlements(forBinary: meta.effectiveUrl("MacOS", exe).path)
case .APK:
break // not applicable for Android
}
}
return Entitlements.withoutBinary()
}
/// Process compiled binary and provision plist to extract `Entitlements`
mutating func procEntitlements(_ meta: MetaInfo, _ appPlist: Plist_Info?, _ provisionPlist: Plist_MobileProvision?) {
var entitlements = readEntitlements(meta, appPlist?.exePath)
entitlements.applyFallbackIfNeeded(provisionPlist?.entitlements)
if entitlements.html == nil && !entitlements.hasError {
return
}
self.apply([
"EntitlementsHidden" : CLASS_VISIBLE,
"EntitlementsWarningHidden": entitlements.hasError ? CLASS_VISIBLE : CLASS_HIDDEN,
"EntitlementsDict": entitlements.html ?? "No Entitlements",
])
}
}

View File

@@ -1,32 +1,12 @@
import Foundation import Foundation
extension HtmlGenerator { extension PreviewGenerator {
/// Calculate file / folder size.
private func getFileSize(_ path: String) -> Int64 {
var isDir: ObjCBool = false
FileManager.default.fileExists(atPath: path, isDirectory: &isDir)
if !isDir.boolValue {
return try! FileManager.default.attributesOfItem(atPath: path)[.size] as! Int64
}
var fileSize: Int64 = 0
for child in try! FileManager.default.subpathsOfDirectory(atPath: path) {
fileSize += try! FileManager.default.attributesOfItem(atPath: path + "/" + child)[.size] as! Int64
}
return fileSize
}
/// Process meta information about the file itself. Like file size and last modification. /// Process meta information about the file itself. Like file size and last modification.
mutating func procFileInfo(_ url: URL) { mutating func procFileInfo(_ url: URL) {
let formattedValue : String
if let attrs = try? FileManager.default.attributesOfItem(atPath: url.path) {
let size = ByteCountFormatter.string(fromByteCount: getFileSize(url.path), countStyle: .file)
formattedValue = "\(size), Modified \((attrs[.modificationDate] as! Date).mediumFormat())"
} else {
formattedValue = ""
}
self.apply([ self.apply([
"FileName": escapeXML(url.lastPathComponent), "FileName": escapeXML(url.lastPathComponent),
"FileInfo": formattedValue, "FileSize": url.fileSizeHuman(),
"FileModified": url.modificationDate()?.mediumFormat() ?? "",
]) ])
} }
} }
@@ -41,3 +21,32 @@ private func escapeXML(_ stringToEscape: String) -> String {
.replacingOccurrences(of: "<", with: "&lt;") .replacingOccurrences(of: "<", with: "&lt;")
.replacingOccurrences(of: ">", with: "&gt;") .replacingOccurrences(of: ">", with: "&gt;")
} }
extension URL {
/// Last modification date of file (or folder)
@inlinable func modificationDate() -> Date? {
(try? FileManager.default.attributesOfItem(atPath: self.path))?[.modificationDate] as? Date
}
/// Calls `fileSize()`. Will convert `Int` to human readable `String`.
func fileSizeHuman() -> String {
ByteCountFormatter.string(fromByteCount: self.fileSize(), countStyle: .file)
}
// MARK: - private methods
/// Calculate file or folder size.
private func fileSize() -> Int64 {
var isDir: ObjCBool = false
FileManager.default.fileExists(atPath: self.path, isDirectory: &isDir)
if !isDir.boolValue {
return try! FileManager.default.attributesOfItem(atPath: self.path)[.size] as! Int64
}
var fileSize: Int64 = 0
for child in try! FileManager.default.subpathsOfDirectory(atPath: self.path) {
fileSize += try! FileManager.default.attributesOfItem(atPath: self.path + "/" + child)[.size] as! Int64
}
return fileSize
}
}

View File

@@ -1,14 +1,14 @@
import Foundation import Foundation
extension HtmlGenerator { extension PreviewGenerator {
/// Process meta information about the plugin. Like version and debug flag. /// Process meta information about the plugin. Like version and debug flag.
mutating func procFooterInfo() { mutating func procFooterInfo() {
self.apply([ self.apply([
"SrcAppName": "QLAppBundle", "SrcAppName": "QLAppBundle",
"SrcLinkUrl": "https://github.com/relikd/QLAppBundle", "SrcLinkUrl": "https://github.com/relikd/QLAppBundle",
"SrcLinkName": "relikd/QLAppBundle", "SrcLinkName": "relikd/QLAppBundle",
"BundleShortVersionString": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "", "SrcVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "",
"BundleVersion": Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "", "SrcBuildVer": Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "",
]) ])
#if DEBUG #if DEBUG
self.data["SrcAppName"]! += " (debug)" self.data["SrcAppName"]! += " (debug)"

View File

@@ -0,0 +1,68 @@
import Foundation
extension PreviewGenerator {
/// Process info stored in `embedded.mobileprovision`
mutating func procProvision(_ provisionPlist: Plist_MobileProvision?) {
guard let provisionPlist else {
return
}
let deviceCount = provisionPlist.devices.count
self.apply([
"ProvisionHidden": CLASS_VISIBLE,
"ProvisionProfileName": provisionPlist.profileName ?? "",
"ProvisionProfileId": provisionPlist.profileId ?? "",
"ProvisionTeamName": provisionPlist.teamName ?? "<em>Team name not available</em>",
"ProvisionTeamIds": provisionPlist.teamIds.isEmpty ? "<em>Team ID not available</em>" : provisionPlist.teamIds.joined(separator: ", "),
"ProvisionCreateDate": provisionPlist.creationDate?.formattedCreationDate() ?? "",
"ProvisionExpireDate": provisionPlist.expireDate?.formattedExpirationDate() ?? "",
"ProvisionExpireStatus": ExpirationStatus(provisionPlist.expireDate).cssClass(),
"ProvisionProfilePlatform": provisionPlist.profilePlatform,
"ProvisionProfileType": provisionPlist.profileType,
"ProvisionDeviceCount": deviceCount == 0 ? "No Devices" : "\(deviceCount) Device\(deviceCount == 1 ? "" : "s")",
"ProvisionDeviceIds": deviceCount == 0 ? "Distribution Profile" : formatAsTable(groupDevices(provisionPlist.devices), header: ["", "UDID"]),
"ProvisionDevelopCertificates": provisionPlist.certificates.isEmpty ? "No Developer Certificates"
: formatAsTable(
provisionPlist.certificates
.sorted { $0.subject < $1.subject }
.map {TableRow([$0.subject, $0.expiration?.relativeExpirationDateString() ?? "<span class='warning'>No invalidity date in certificate</span>"])}
),
])
}
}
/// Group device ids by first letter (`d -> device02`)
private func groupDevices(_ devices: [String]) -> [TableRow] {
var currentPrefix: String? = nil
return devices.sorted().map { device in
// compute the prefix for the first column of the table
let displayPrefix: String
let devicePrefix = String(device.prefix(1))
if currentPrefix != devicePrefix {
currentPrefix = devicePrefix
displayPrefix = "\(devicePrefix)"
} else {
displayPrefix = ""
}
return [displayPrefix, device]
}
}
private typealias TableRow = [String]
/// Print html table with arbitrary number of columns
/// @param header If set, start the table with a `tr` column row.
private func formatAsTable(_ data: [[String]], header: TableRow? = nil) -> String {
var table = "<table>\n"
if let header = header {
table += "<tr><th>\(header.joined(separator: "</th><th>"))</th></tr>\n"
}
for row in data {
table += "<tr><td>\(row.joined(separator: "</td><td>"))</td></tr>\n"
}
return table + "</table>\n"
}

View File

@@ -0,0 +1,56 @@
import Foundation
private let TransportSecurityLocalizedKeys = [
"NSAllowsArbitraryLoads": "Allows Arbitrary Loads",
"NSAllowsArbitraryLoadsForMedia": "Allows Arbitrary Loads for Media",
"NSAllowsArbitraryLoadsInWebContent": "Allows Arbitrary Loads in Web Content",
"NSAllowsLocalNetworking": "Allows Local Networking",
"NSExceptionDomains": "Exception Domains",
"NSIncludesSubdomains": "Includes Subdomains",
"NSRequiresCertificateTransparency": "Requires Certificate Transparency",
"NSExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
"NSThirdPartyExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSThirdPartyExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSThirdPartyExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
]
/// Print recursive tree of key-value mappings.
private func recursiveTransportSecurity(_ dictionary: PlistDict, _ level: Int = 0) -> String {
var output = ""
for (key, value) in dictionary {
let localizedKey = TransportSecurityLocalizedKeys[key] ?? key
for _ in 0..<level {
output += (level == 1) ? "- " : "&nbsp;&nbsp;"
}
if let subDict = value as? [String: Any] {
output += "\(localizedKey):<div class=\"list\">\n"
output += recursiveTransportSecurity(subDict, level + 1)
output += "</div>\n"
} else if let number = value as? NSNumber {
output += "\(localizedKey): \(number.boolValue ? "YES" : "NO")<br />"
} else {
output += "\(localizedKey): \(value)<br />"
}
}
return output
}
extension PreviewGenerator {
/// Process ATS info in `Info.plist`
mutating func procTransportSecurity(_ appPlist: Plist_Info?) {
guard let value = appPlist?.transportSecurity else {
return
}
self.apply([
"TransportSecurityHidden": CLASS_VISIBLE,
"TransportSecurityDict": "<div class=\"list\">\(recursiveTransportSecurity(value))</div>",
])
}
}

View File

@@ -0,0 +1,21 @@
import Foundation
extension PreviewGenerator {
/// Process info stored in `iTunesMetadata.plist`
mutating func procItunesMeta(_ itunesPlist: Plist_iTunesMetadata?) {
guard let itunesPlist else {
return
}
self.apply([
"iTunesHidden": CLASS_VISIBLE,
"iTunesId": itunesPlist.appId?.description ?? "",
"iTunesName": itunesPlist.appName ?? "",
"iTunesGenres": itunesPlist.genres.joined(separator: ", "),
"iTunesReleaseDate": itunesPlist.releaseDate?.mediumFormat() ?? "",
"iTunesAppleId": itunesPlist.purchaserName ?? "",
"iTunesPurchaseDate": itunesPlist.purchaseDate?.mediumFormat() ?? "",
"iTunesPrice": itunesPlist.price ?? "",
])
}
}

View File

@@ -0,0 +1,105 @@
import Foundation
let CLASS_HIDDEN = "hidden"
let CLASS_VISIBLE = ""
struct PreviewGenerator {
/// Used for TAG replacements
var data: [String: String] = [
// default: hide everything
"AppInfoHidden": CLASS_HIDDEN,
"AppExtensionTypeHidden": CLASS_HIDDEN,
"ArchiveHidden": CLASS_HIDDEN,
"iTunesHidden": CLASS_HIDDEN,
"TransportSecurityHidden": CLASS_HIDDEN,
"EntitlementsHidden": CLASS_HIDDEN,
"EntitlementsWarningHidden": CLASS_HIDDEN,
"ProvisionHidden": CLASS_HIDDEN,
"ApkFeaturesRequiredHidden": CLASS_HIDDEN,
"ApkFeaturesOptionalHidden": CLASS_HIDDEN,
"ApkPermissionsHidden": CLASS_HIDDEN,
]
let meta: MetaInfo
init(_ meta: MetaInfo) throws {
self.meta = meta
switch meta.type {
case .IPA, .Archive, .Extension:
guard let plistApp = meta.readPlist_Info() else {
throw RuntimeError("Info.plist not found")
}
procAppInfoApple(plistApp)
if meta.type == .IPA {
procItunesMeta(meta.readPlist_iTunesMetadata())
} else if meta.type == .Archive {
procArchiveInfo(meta.readPlistXCArchive())
}
procTransportSecurity(plistApp)
let plistProvision = meta.readPlist_MobileProvision()
procEntitlements(meta, plistApp, plistProvision)
procProvision(plistProvision)
// App Icon (last, because the image uses a lot of memory)
data["AppIcon"] = AppIcon(meta).extractImage(from: plistApp.icons).withRoundCorners().asBase64()
case .APK:
guard let manifest = meta.readApk_Manifest() else {
throw RuntimeError("AndroidManifest.xml not found")
}
procAppInfoAndroid(manifest)
// App Icon (last, because the image uses a lot of memory)
data["AppIcon"] = AppIcon(meta).extractImage(from: manifest.icon).withRoundCorners().asBase64()
}
data["QuickLookTitle"] = stringForFileType(meta)
procFileInfo(meta.url)
procFooterInfo()
}
mutating func apply(_ values: [String: String]) {
data.merge(values) { (_, new) in new }
}
/// Title of the preview window
private func stringForFileType(_ meta: MetaInfo) -> String {
switch meta.type {
case .IPA, .APK: return "App info"
case .Archive: return "Archive info"
case .Extension: return "App extension info"
}
}
/// prepare html, replace values
func generate(template html: String, css: String) -> String {
let templateValues = data.merging(["CSS": css]) { (_, new) in new }
return html.regexReplace("\\{\\{([^ }]{1,40}?)\\}\\}") { templateValues[$0] }
}
}
extension String {
/// Replace regex-pattern with custom replacement.
/// @param pattern must include a regex group. (e.g. "a(b)c")
func regexReplace(_ pattern: String, with fn: (_ match: String) -> String?) -> String {
var rv = ""
var prevLoc = self.startIndex
let regex = try! NSRegularExpression(pattern: pattern)
regex.enumerateMatches(in: self, range: NSRange(location: 0, length: self.count), using: { match, flags, stop in
let start = self.index(self.startIndex, offsetBy: match!.range.lowerBound)
// append unrelated text up to this key
rv.append(contentsOf: self[prevLoc ..< start])
prevLoc = self.index(start, offsetBy: match!.range.length)
// append key if exists (else remove template-key)
let key = String(self[Range(match!.range(at: 1), in: self)!])
if let value = fn(key) {
rv.append(value)
} else {
// do not append anything -> removes all template keys from template
// os_log(.debug, log: log, "unknown template key: %{public}@", key)
}
})
// append remaining text
rv.append(contentsOf: self[prevLoc ..< self.endIndex])
return rv
}
}

View File

@@ -0,0 +1,14 @@
import Foundation
// used to quit QuickLook generation without returning a valid preview
struct RuntimeError: LocalizedError {
let description: String
init(_ description: String) {
self.description = description
}
var errorDescription: String? {
description
}
}