├── .github └── icons8-archive-80.png ├── .gitignore ├── .travis.yml ├── Configs.xcconfig ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md └── Sources ├── Unxip ├── Runner.swift └── main.swift └── UnxipKit ├── UnxipKit.m └── include ├── PKSignedContainer.h └── UnxipKit.h /.github/icons8-archive-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thii/Unxip/1e344e3d7e9d3932d5aaa3cd9a077f21c7ef75e5/.github/icons8-archive-80.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build 3 | /.build 4 | /Packages 5 | /Unxip.xcodeproj 6 | 7 | ### https://raw.github.com/github/gitignore/3d1f3fa52ae4f9c924ac2f57641d770b5548d005/Global/Xcode.gitignore 8 | 9 | # Xcode 10 | # 11 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 12 | 13 | ## User settings 14 | xcuserdata/ 15 | 16 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 17 | *.xcscmblueprint 18 | *.xccheckout 19 | 20 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 21 | build/ 22 | DerivedData/ 23 | *.moved-aside 24 | *.pbxuser 25 | !default.pbxuser 26 | *.mode1v3 27 | !default.mode1v3 28 | *.mode2v3 29 | !default.mode2v3 30 | *.perspectivev3 31 | !default.perspectivev3 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.4.1 3 | script: 4 | - set -o pipefail 5 | - make package 6 | notifications: 7 | email: false 8 | -------------------------------------------------------------------------------- /Configs.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = "$(inherited)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks" 2 | OTHER_LDFLAGS = -framework PackageKit 3 | 4 | // SwiftPM doesn't seem to pick the executable name specified in Package.swift 5 | // when generating Xcode project 6 | PRODUCT_NAME = unxip 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?=/usr/local 2 | 3 | CD=cd 4 | CP=$(shell whereis cp) -Rf 5 | MKDIR=$(shell whereis mkdir) -p 6 | RM=$(shell whereis rm) -rf 7 | SWIFT=$(shell whereis swift) 8 | ZIP=$(shell whereis zip) -r 9 | 10 | TARGET_PLATFORM=x86_64-apple-macosx10.10 11 | 12 | BINARY_DIRECTORY=$(PREFIX)/bin 13 | BINARY_NAME=unxip 14 | 15 | BUILD_DIRECTORY=$(shell pwd)/.build/$(TARGET_PLATFORM)/release 16 | OUTPUT_EXECUTABLE=$(BUILD_DIRECTORY)/$(BINARY_NAME) 17 | INSTALL_EXECUTABLE_PATH=$(BINARY_DIRECTORY)/$(BINARY_NAME) 18 | 19 | .PHONY: build 20 | build: 21 | $(SWIFT) build -c release -Xswiftc -static-stdlib -Xswiftc -target -Xswiftc $(TARGET_PLATFORM) -Xlinker -F/System/Library/PrivateFrameworks -Xlinker -framework -Xlinker PackageKit 22 | 23 | .PHONY: install 24 | install: build 25 | $(CP) "$(OUTPUT_EXECUTABLE)" "$(BINARY_DIRECTORY)" 26 | 27 | .PHONY: package 28 | package: build 29 | $(CD) "$(BUILD_DIRECTORY)" && $(ZIP) "$(BINARY_NAME).zip" "$(BINARY_NAME)" 30 | 31 | .PHONY: generate-xcodeproj 32 | generate-xcodeproj: 33 | $(SWIFT) package generate-xcodeproj --xcconfig-overrides Configs.xcconfig 34 | 35 | .PHONY: uninstall 36 | uninstall: 37 | $(RM) "$(BINARY_DIRECTORY)/$(BINARY_NAME)" 38 | 39 | .PHONY: clean 40 | clean: 41 | $(SWIFT) package clean 42 | $(RM) Unxip.xcodeproj 43 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | 5 | ] 6 | }, 7 | "version": 1 8 | } 9 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Unxip", 8 | products: [ 9 | .executable( 10 | name: "unxip", 11 | targets: ["Unxip"]), 12 | .library( 13 | name: "UnxipKit", 14 | targets: ["UnxipKit"]), 15 | ], 16 | dependencies: [ 17 | ], 18 | targets: [ 19 | .target( 20 | name: "Unxip", 21 | dependencies: ["UnxipKit"]), 22 | .target( 23 | name: "UnxipKit", 24 | dependencies: []), 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Unxip 4 | 5 |

6 | 7 | # Unxip [![Build Status](https://travis-ci.org/thii/Unxip.svg?branch=master)](https://travis-ci.org/thii/Unxip) 8 | A Swift command line tool to extract signed archives (.xip files). 9 | 10 | ## Installation 11 | ### Homebrew 12 | 13 | $ brew install thii/unxip/unxip 14 | 15 | ### Build from source 16 | 17 | $ make install 18 | 19 | ## Usage 20 | 21 | $ unxip [] 22 | 23 | ## Credits 24 | Icon is provided free by [Icons8](https://icons8.com) under Creative Commons Attribution-NoDerivs 3.0 Unported. 25 | 26 | ## License 27 | MIT 28 | -------------------------------------------------------------------------------- /Sources/Unxip/Runner.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class Runner { 4 | 5 | /// A poor man's mutex. 6 | private var count = 0 7 | 8 | /// Current run loop. 9 | private let runLoop = RunLoop.current 10 | 11 | /// Initializer. 12 | init() {} 13 | 14 | /// Lock the script runner. 15 | func lock() { 16 | count += 1 17 | } 18 | 19 | /// Unlock the script runner. 20 | func unlock() { 21 | count -= 1 22 | } 23 | 24 | /// Wait for all locks to unlock. 25 | func wait() { 26 | while count > 0 && 27 | runLoop.run(mode: .defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) { 28 | // Run, run, run 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Unxip/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(UnxipKit) 4 | import UnxipKit 5 | #endif 6 | 7 | let args = CommandLine.arguments 8 | 9 | if args.count == 1 || args.contains("-h") || args.contains("--help") { 10 | print("Usage: unxip []", terminator: "\n\n") 11 | print(" Extract a signed archive.") 12 | exit(0) 13 | } 14 | 15 | let inputXipFile = args[1] 16 | let outputPath = args.count == 2 ? "." : args.last! 17 | let inputUrl = URL(fileURLWithPath: inputXipFile) 18 | 19 | // Remove 'file://' 20 | let absoluteOutputPath = String(URL(fileURLWithPath: outputPath).absoluteString.dropFirst(7)) 21 | 22 | var runner = Runner() 23 | runner.lock() 24 | 25 | do { 26 | let container = try PKSignedContainer(forReadingFromContainerAt: inputUrl) 27 | 28 | container.startUnarchiving( 29 | atPath: absoluteOutputPath, 30 | notifyOn: .main, 31 | progress: { progress, progressText in 32 | if progress < 0 { 33 | // Verifying digital signature 34 | print(progressText) 35 | } else { 36 | print(progressText + ": \(Int(progress))%", terminator: "\r") 37 | } 38 | }) { _ in 39 | runner.unlock() 40 | } 41 | 42 | runner.wait() 43 | } catch { 44 | print(error.localizedDescription) 45 | exit(1) 46 | } 47 | -------------------------------------------------------------------------------- /Sources/UnxipKit/UnxipKit.m: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Sources/UnxipKit/include/PKSignedContainer.h: -------------------------------------------------------------------------------- 1 | // Generated by class-dump 3.5 (64 bit), with modification 2 | 3 | #import 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface PKSignedContainer : NSObject 8 | 9 | - (nullable instancetype)initForReadingFromContainerAtURL:(NSURL *)url 10 | error:(NSError **)error; 11 | 12 | - (void)startUnarchivingAtPath:(NSString *)path 13 | notifyOnQueue:(dispatch_queue_t)queue 14 | progress:(void (^)(double, NSString *))progressBlock 15 | finish:(void (^)(BOOL))finishBlock; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Sources/UnxipKit/include/UnxipKit.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "PKSignedContainer.h" 4 | --------------------------------------------------------------------------------