├── example.gif ├── QMCDecode ├── Assets.xcassets │ ├── Contents.json │ ├── Success.imageset │ │ ├── Success.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ ├── QMCDecode16.png │ │ ├── QMCDecode32.png │ │ ├── QMCDecode64.png │ │ ├── QMCDecode1024.png │ │ ├── QMCDecode128.png │ │ ├── QMCDecode256-1.png │ │ ├── QMCDecode256.png │ │ ├── QMCDecode32-1.png │ │ ├── QMCDecode512-1.png │ │ ├── QMCDecode512.png │ │ └── Contents.json ├── QMCDecode.entitlements ├── AppDelegate.swift ├── WindowController.swift ├── Info.plist ├── TeaCipher.swift ├── Constants.swift ├── QMCKeyDecoder.swift ├── QMDecoder.swift ├── QMCipher.swift ├── ViewController.swift └── Base.lproj │ └── Main.storyboard ├── QMCDecode.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── QMCDecode.xcscheme └── project.pbxproj ├── README.md ├── QMCDecodeTests ├── Info.plist └── QMCDecodeTests.swift ├── LICENSE └── .gitignore /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/example.gif -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/Success.imageset/Success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/Success.imageset/Success.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode16.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode32.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode64.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode1024.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode128.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode256-1.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode256.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode32-1.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode512-1.png -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongjiehong/QMCDecode/HEAD/QMCDecode/Assets.xcassets/AppIcon.appiconset/QMCDecode512.png -------------------------------------------------------------------------------- /QMCDecode/QMCDecode.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /QMCDecode.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QMCDecode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/Success.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Success.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QMCDecode 2 | 3 | QQ音乐加密格式转换为普通格式,仅支持macOS,可自动识别到QQ音乐下载目录,默认转换结果存储到~/Music/QMCConvertOutput 4 | 5 | 可自定义需要转换的文件和输出路径 6 | 7 | 转换后tag不对可以使用[kid3](https://prdownloads.sourceforge.net/kid3/kid3-3.8.0-Darwin.dmg?download)进行修改, 支持批量 8 | 9 | 支持的格式如下 10 | 11 | * .qmcflac to flac 12 | * .qmc0 to mp3 13 | * .qmc2 to ogg 14 | * .qmc3 to mp3 15 | * .qmflac to flac 16 | * .mgg to ogg 17 | * .mgg1 to ogg 18 | * .qmcogg to ogg 19 | * .mflac to flac 20 | * .mflac0 to flac 21 | * .bkcmp3 to mp3 22 | * .bkcflac to flac 23 | 24 | ---- 25 | 示例 26 | 27 | ![示例图片](example.gif) 28 | 29 | 协议与许可 30 | 31 | MIT -------------------------------------------------------------------------------- /QMCDecode/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2019/8/26. 6 | // Copyright © 2019 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 13 | func applicationDidFinishLaunching(_ aNotification: Notification) { 14 | // Insert code here to initialize your application 15 | } 16 | 17 | func applicationWillTerminate(_ aNotification: Notification) { 18 | // Insert code here to tear down your application 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /QMCDecode/WindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowController.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2019/8/26. 6 | // Copyright © 2019 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class WindowController: NSWindowController { 12 | 13 | override func windowDidLoad() { 14 | super.windowDidLoad() 15 | 16 | self.window?.delegate = WindowDelegate.default 17 | } 18 | } 19 | 20 | class WindowDelegate: NSObject, NSWindowDelegate { 21 | static let `default`: WindowDelegate = { 22 | return WindowDelegate() 23 | }() 24 | 25 | func windowWillClose(_ notification: Notification) { 26 | NSApp.terminate(self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /QMCDecodeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /QMCDecodeTests/QMCDecodeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QMCDecodeTests.swift 3 | // QMCDecodeTests 4 | // 5 | // Created by 龚杰洪 on 2019/8/26. 6 | // Copyright © 2019 龚杰洪. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import QMCDecode 11 | 12 | class QMCDecodeTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 程序猿老龚 gjh.me 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QMCDecode/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2019 龚杰洪. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /QMCDecode/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "QMCDecode16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "QMCDecode32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "QMCDecode32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "QMCDecode64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "QMCDecode128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "QMCDecode256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "QMCDecode256-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "QMCDecode512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "QMCDecode512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "QMCDecode1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /QMCDecode/TeaCipher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeaCipher.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2022/5/19. 6 | // Copyright © 2022 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum TeaCipherError: Error { 12 | case keySizeInvalid 13 | case oddNumberOfRoundsSpecified 14 | } 15 | 16 | class TeaCipher { 17 | let blockSize = 8 18 | let keySize = 16 19 | let delta: UInt32 = 0x9e3779b9 20 | let numRounds = 64 21 | 22 | var key0: UInt32 23 | var key1: UInt32 24 | var key2: UInt32 25 | var key3: UInt32 26 | var rounds: UInt32 27 | 28 | /// 初始化 29 | /// - Parameters: 30 | /// - Key: 原始key 31 | /// - rounds: 轮数,默认64 32 | init(key: [UInt8], rounds: UInt32 = 64) throws { 33 | if key.count != 16 { 34 | throw TeaCipherError.keySizeInvalid 35 | } 36 | 37 | if (rounds & 1) != 0 { 38 | throw TeaCipherError.oddNumberOfRoundsSpecified 39 | } 40 | 41 | self.rounds = rounds 42 | 43 | 44 | self.key0 = key[0..<16].withUnsafeBytes({ 45 | $0.load(as: UInt32.self).bigEndian 46 | }) 47 | 48 | self.key1 = key[4..<16].withUnsafeBytes({ 49 | $0.load(as: UInt32.self).bigEndian 50 | }) 51 | 52 | self.key2 = key[8..<16].withUnsafeBytes({ 53 | $0.load(as: UInt32.self).bigEndian 54 | }) 55 | 56 | self.key3 = key[12..<16].withUnsafeBytes({ 57 | $0.load(as: UInt32.self).bigEndian 58 | }) 59 | } 60 | 61 | /// 加密数据 62 | /// - Parameter src: 原始数据 63 | /// - Returns: 加密后的数据 64 | func encrypt(src: [UInt8]) -> [UInt8] { 65 | var v0 = src[0..>5) &+ key1)) 78 | v1 = v1 &+ (((v0<<4) &+ key2) ^ (v0 &+ sum) ^ ((v0>>5) &+ key3)) 79 | } 80 | 81 | // 强行转换为大端数据后读取为byte数组 82 | v0 = CFSwapInt32HostToBig(v0) 83 | v1 = CFSwapInt32HostToBig(v1) 84 | 85 | var result = [UInt8]() 86 | let v0Data = Data(bytes: &v0, count: MemoryLayout.size(ofValue: v0)) 87 | result += [UInt8](v0Data) 88 | 89 | let v1Data = Data(bytes: &v1, count: MemoryLayout.size(ofValue: v1)) 90 | result += [UInt8](v1Data) 91 | return result 92 | } 93 | 94 | /// 解密数据 95 | /// - Parameter src: 需要解密的原始数据 96 | /// - Returns: 解密后的数据 97 | func decrypt(src: [UInt8]) -> [UInt8] { 98 | var v0 = src[0..>5) &+ key3)) 110 | v0 = v0 &- (((v1<<4) &+ key0) ^ (v1 &+ sum) ^ ((v1>>5) &+ key1)) 111 | sum = sum &- delta 112 | } 113 | 114 | // 强行转换为大端数据后读取为byte数组 115 | v0 = CFSwapInt32HostToBig(v0) 116 | v1 = CFSwapInt32HostToBig(v1) 117 | 118 | var result = [UInt8]() 119 | let v0Data = Data(bytes: &v0, count: MemoryLayout.size(ofValue: v0)) 120 | result += [UInt8](v0Data) 121 | 122 | let v1Data = Data(bytes: &v1, count: MemoryLayout.size(ofValue: v1)) 123 | result += [UInt8](v1Data) 124 | return result 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /QMCDecode.xcodeproj/xcshareddata/xcschemes/QMCDecode.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 64 | 70 | 71 | 72 | 73 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /QMCDecode/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2022/1/5. 6 | // Copyright © 2022 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ExtensionAndVersion { 12 | enum EncryptVersion { 13 | case v1 14 | case v2 15 | } 16 | var ext: String 17 | var version: EncryptVersion 18 | 19 | init(ext: String, version: ExtensionAndVersion.EncryptVersion) { 20 | self.ext = ext 21 | self.version = version 22 | } 23 | } 24 | 25 | let encryptExtDictionary: [String: ExtensionAndVersion] = [ 26 | "mgg": ExtensionAndVersion(ext: "ogg", version: ExtensionAndVersion.EncryptVersion.v2), 27 | "mgg1": ExtensionAndVersion(ext: "ogg", version: ExtensionAndVersion.EncryptVersion.v2), 28 | "mflac": ExtensionAndVersion(ext: "flac", version: ExtensionAndVersion.EncryptVersion.v2), 29 | "mflac0": ExtensionAndVersion(ext: "flac", version: ExtensionAndVersion.EncryptVersion.v2), 30 | 31 | "qmcflac": ExtensionAndVersion(ext: "flac", version: ExtensionAndVersion.EncryptVersion.v2), 32 | "qmcogg": ExtensionAndVersion(ext: "ogg", version: ExtensionAndVersion.EncryptVersion.v2), 33 | 34 | "qmc0": ExtensionAndVersion(ext: "mp3", version: ExtensionAndVersion.EncryptVersion.v1), 35 | "qmc2": ExtensionAndVersion(ext: "ogg", version: ExtensionAndVersion.EncryptVersion.v1), 36 | "qmc3": ExtensionAndVersion(ext: "mp3", version: ExtensionAndVersion.EncryptVersion.v1), 37 | "bkcmp3": ExtensionAndVersion(ext: "mp3", version: ExtensionAndVersion.EncryptVersion.v1), 38 | "bkcflac": ExtensionAndVersion(ext: "flac", version: ExtensionAndVersion.EncryptVersion.v1), 39 | "tkm": ExtensionAndVersion(ext: "m4a", version: ExtensionAndVersion.EncryptVersion.v1), 40 | "666c6163": ExtensionAndVersion(ext: "flac", version: ExtensionAndVersion.EncryptVersion.v1), 41 | "6d7033": ExtensionAndVersion(ext: "mp3", version: ExtensionAndVersion.EncryptVersion.v1), 42 | "6f6767": ExtensionAndVersion(ext: "ogg", version: ExtensionAndVersion.EncryptVersion.v1), 43 | "6d3461": ExtensionAndVersion(ext: "m4a", version: ExtensionAndVersion.EncryptVersion.v1), 44 | "776176": ExtensionAndVersion(ext: "wav", version: ExtensionAndVersion.EncryptVersion.v1), 45 | ] 46 | 47 | let privateKey256: [UInt8] = [ 48 | 0x77, 0x48, 0x32, 0x73, 0xDE, 0xF2, 0xC0, 0xC8, 49 | 0x95, 0xEC, 0x30, 0xB2, 0x51, 0xC3, 0xE1, 0xA0, 50 | 0x9E, 0xE6, 0x9D, 0xCF, 0xFA, 0x7F, 0x14, 0xD1, 51 | 0xCE, 0xB8, 0xDC, 0xC3, 0x4A, 0x67, 0x93, 0xD6, 52 | 53 | 0x28, 0xC2, 0x91, 0x70, 0xCA, 0x8D, 0xA2, 0xA4, 54 | 0xF0, 0x08, 0x61, 0x90, 0x7E, 0x6F, 0xA2, 0xE0, 55 | 0xEB, 0xAE, 0x3E, 0xB6, 0x67, 0xC7, 0x92, 0xF4, 56 | 0x91, 0xB5, 0xF6, 0x6C, 0x5E, 0x84, 0x40, 0xF7, 57 | 58 | 0xF3, 0x1B, 0x02, 0x7F, 0xD5, 0xAB, 0x41, 0x89, 59 | 0x28, 0xF4, 0x25, 0xCC, 0x52, 0x11, 0xAD, 0x43, 60 | 0x68, 0xA6, 0x41, 0x8B, 0x84, 0xB5, 0xFF, 0x2C, 61 | 0x92, 0x4A, 0x26, 0xD8, 0x47, 0x6A, 0x7C, 0x95, 62 | 63 | 0x61, 0xCC, 0xE6, 0xCB, 0xBB, 0x3F, 0x47, 0x58, 64 | 0x89, 0x75, 0xC3, 0x75, 0xA1, 0xD9, 0xAF, 0xCC, 65 | 0x08, 0x73, 0x17, 0xDC, 0xAA, 0x9A, 0xA2, 0x16, 66 | 0x41, 0xD8, 0xA2, 0x06, 0xC6, 0x8B, 0xFC, 0x66, 67 | 68 | 0x34, 0x9F, 0xCF, 0x18, 0x23, 0xA0, 0x0A, 0x74, 69 | 0xE7, 0x2B, 0x27, 0x70, 0x92, 0xE9, 0xAF, 0x37, 70 | 0xE6, 0x8C, 0xA7, 0xBC, 0x62, 0x65, 0x9C, 0xC2, 71 | 0x08, 0xC9, 0x88, 0xB3, 0xF3, 0x43, 0xAC, 0x74, 72 | 73 | 0x2C, 0x0F, 0xD4, 0xAF, 0xA1, 0xC3, 0x01, 0x64, 74 | 0x95, 0x4E, 0x48, 0x9F, 0xF4, 0x35, 0x78, 0x95, 75 | 0x7A, 0x39, 0xD6, 0x6A, 0xA0, 0x6D, 0x40, 0xE8, 76 | 0x4F, 0xA8, 0xEF, 0x11, 0x1D, 0xF3, 0x1B, 0x3F, 77 | 78 | 0x3F, 0x07, 0xDD, 0x6F, 0x5B, 0x19, 0x30, 0x19, 79 | 0xFB, 0xEF, 0x0E, 0x37, 0xF0, 0x0E, 0xCD, 0x16, 80 | 0x49, 0xFE, 0x53, 0x47, 0x13, 0x1A, 0xBD, 0xA4, 81 | 0xF1, 0x40, 0x19, 0x60, 0x0E, 0xED, 0x68, 0x09, 82 | 83 | 0x06, 0x5F, 0x4D, 0xCF, 0x3D, 0x1A, 0xFE, 0x20, 84 | 0x77, 0xE4, 0xD9, 0xDA, 0xF9, 0xA4, 0x2B, 0x76, 85 | 0x1C, 0x71, 0xDB, 0x00, 0xBC, 0xFD, 0x0C, 0x6C, 86 | 0xA5, 0x47, 0xF7, 0xF6, 0x00, 0x79, 0x4A, 0x11, 87 | ] 88 | -------------------------------------------------------------------------------- /QMCDecode/QMCKeyDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QMCKeyDec.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2022/5/19. 6 | // Copyright © 2022 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum QMCKeyDecoderError: Error { 12 | case inBufferSizeInvalidWithBlockSize 13 | case inBufferSizeToSmall 14 | case zeroCheckFailed 15 | case canNotConstructBase64Key 16 | case keyLengthTooShort 17 | case invalidPaddingLength 18 | } 19 | 20 | class QMCKeyDecoder { 21 | let saltLength = 2 22 | let zeroLength = 7 23 | 24 | /// 查找key 25 | /// - Parameter rawKey: 原始key 26 | /// - Returns: 计算后的key 27 | func deriveKey(_ rawKey: [UInt8]) throws -> [UInt8] { 28 | let base64Key = Data(bytes: rawKey, count: rawKey.count) 29 | guard let base64DecodedKey = Data(base64Encoded: base64Key) else { 30 | throw QMCKeyDecoderError.canNotConstructBase64Key 31 | } 32 | 33 | if base64DecodedKey.count < 16 { 34 | throw QMCKeyDecoderError.keyLengthTooShort 35 | } 36 | 37 | let simpleKey = simpleMakeKey(seed: 106, length: 8) 38 | var teaKey = [UInt8](repeating: 0, count: 16) 39 | for index in 0..<8 { 40 | teaKey[index << 1] = simpleKey[index] 41 | teaKey[(index << 1) + 1] = base64DecodedKey[index] 42 | } 43 | 44 | let inBuffer = [UInt8](base64DecodedKey[8.. [UInt8] { 58 | var result = [UInt8](repeating: 0, count: length) 59 | for index in 0.. [UInt8] { 71 | if inBuffer.count % 8 != 0 { 72 | throw QMCKeyDecoderError.inBufferSizeInvalidWithBlockSize 73 | } 74 | 75 | if inBuffer.count < 16 { 76 | throw QMCKeyDecoderError.inBufferSizeToSmall 77 | } 78 | 79 | let teaCipher = try TeaCipher(key: key, rounds: 32) 80 | 81 | var tempBuffer = teaCipher.decrypt(src: inBuffer) 82 | 83 | let paddingLength = Int(tempBuffer[0] & 0x7) 84 | let outputLength = inBuffer.count - 1 - paddingLength - saltLength - zeroLength 85 | 86 | if paddingLength + saltLength != 8 { 87 | throw QMCKeyDecoderError.invalidPaddingLength 88 | } 89 | 90 | var outputBuffer = [UInt8](repeating: 0, count: outputLength) 91 | 92 | var ivPrevious = [UInt8](repeating: 0, count: 8) 93 | var ivCruuent = [UInt8](inBuffer[0...7]) 94 | var inBufferPosition = 8 95 | 96 | var tempIndex = 1 + paddingLength 97 | 98 | // CBC IV 99 | func cryptBlock() { 100 | ivPrevious = ivCruuent 101 | ivCruuent = [UInt8](inBuffer[inBufferPosition...inBufferPosition+7]) 102 | for j in 0..<8 { 103 | tempBuffer[j] ^= ivCruuent[j] 104 | } 105 | 106 | tempBuffer = teaCipher.decrypt(src: tempBuffer) 107 | inBufferPosition += 8 108 | tempIndex = 0 109 | } 110 | 111 | // 不处理salt 112 | var saltIndex = 1 113 | while saltIndex <= saltLength { 114 | if tempIndex < 8 { 115 | tempIndex += 1 116 | saltIndex += 1 117 | } else { 118 | cryptBlock() 119 | } 120 | } 121 | 122 | // 解密为原文 123 | var outputBufferPosition = 0 124 | 125 | while outputBufferPosition < outputLength { 126 | if tempIndex < 8 { 127 | outputBuffer[outputBufferPosition] = tempBuffer[tempIndex] ^ ivPrevious[tempIndex] 128 | outputBufferPosition += 1 129 | tempIndex += 1 130 | } else { 131 | cryptBlock() 132 | } 133 | } 134 | 135 | // 验证应为0的位置是否为0 136 | for _ in 1...zeroLength { 137 | if tempBuffer[tempIndex] != ivPrevious[tempIndex] { 138 | throw QMCKeyDecoderError.zeroCheckFailed 139 | } 140 | } 141 | return outputBuffer 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /QMCDecode/QMDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QMCDecoder.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2022/1/10. 6 | // Copyright © 2022 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class QMDecoder { 12 | enum DecoderError: Error { 13 | case unsupportFileExtension(ext: String) 14 | case canNotReadFile 15 | case canNotReadFileByStream 16 | case canNotGetFileLength 17 | case canNotReadSizeBuffer 18 | case canNotReadRawKeyBuffer 19 | case searchRawKeyFailed 20 | } 21 | 22 | private let commaASCIICode: UInt8 = Character(",").asciiValue ?? 44 23 | 24 | 25 | private let originFilePath: String 26 | private let outputDirectory: String 27 | private let readStream: InputStream 28 | private let originFileLength: Int 29 | 30 | private var realAudioSize: Int = 0 31 | 32 | private var cipher: QMCipher? 33 | 34 | init(originFilePath: String, outputDirectory: String) throws { 35 | self.originFilePath = originFilePath 36 | self.outputDirectory = outputDirectory 37 | guard let fileStream = InputStream(fileAtPath: originFilePath) else { 38 | throw DecoderError.canNotReadFileByStream 39 | } 40 | self.readStream = fileStream 41 | 42 | let fileAttributes = try FileManager.default.attributesOfItem(atPath: originFilePath) 43 | guard let fileLength = fileAttributes[FileAttributeKey.size] as? Int else { 44 | throw DecoderError.canNotGetFileLength 45 | } 46 | self.originFileLength = fileLength 47 | 48 | try searchKey() 49 | } 50 | 51 | func decryptAndWriteToFile() throws { 52 | let fileURL = URL(fileURLWithPath: originFilePath) 53 | let fileExtension = fileURL.pathExtension 54 | if fileExtension.count > 0, let extAndVersion = encryptExtDictionary[fileExtension], let cipher = self.cipher { 55 | let fileHandle = FileHandle(forReadingAtPath: originFilePath) 56 | if let fileData = try fileHandle?.read(upToCount: self.realAudioSize) { 57 | let decodeData = cipher.qmDecrypt(data: fileData, offset: 0) 58 | var outputURL = URL(fileURLWithPath: self.outputDirectory) 59 | outputURL.appendPathComponent(fileURL.lastPathComponent) 60 | outputURL.deletePathExtension() 61 | outputURL.appendPathExtension(extAndVersion.ext) 62 | try decodeData.write(to: outputURL, options: Data.WritingOptions.atomic) 63 | } else { 64 | throw DecoderError.canNotReadFile 65 | } 66 | } else { 67 | throw DecoderError.unsupportFileExtension(ext: fileExtension) 68 | } 69 | } 70 | 71 | func matchingDecoder(_ extAndVersion: ExtensionAndVersion) throws { 72 | if extAndVersion.version == .v2 { 73 | 74 | } else { 75 | 76 | } 77 | } 78 | 79 | func searchKey() throws { 80 | guard let fileHandle = FileHandle(forReadingAtPath: originFilePath) else { 81 | throw DecoderError.canNotReadFile 82 | } 83 | defer { 84 | try? fileHandle.close() 85 | } 86 | 87 | try fileHandle.seek(toOffset: UInt64(self.originFileLength - 4)) 88 | guard let lastFourBytes = try fileHandle.readToEnd() else { 89 | throw DecoderError.canNotReadFile 90 | } 91 | 92 | // 移动端下载的用,以QTag结尾 93 | if String(bytes: lastFourBytes, encoding: String.Encoding.utf8) == "QTag" { 94 | // 读取key长度 95 | try fileHandle.seek(toOffset: UInt64(self.originFileLength - 8)) 96 | guard let sizeBuffer = try fileHandle.read(upToCount: 4) else { 97 | throw DecoderError.canNotReadFile 98 | } 99 | let keySize = sizeBuffer.withUnsafeBytes { 100 | $0.load(as: UInt32.self).bigEndian 101 | } 102 | 103 | // 计算真实音频长度 104 | self.realAudioSize = self.originFileLength - Int(keySize) - 8 105 | 106 | // 读取原始key 107 | try fileHandle.seek(toOffset: UInt64(self.realAudioSize)) 108 | guard let rawKey = try fileHandle.read(upToCount: Int(keySize)) else { 109 | throw DecoderError.canNotReadRawKeyBuffer 110 | } 111 | 112 | // 通过逗号找到key结束位置 113 | guard let keyEndIndex = rawKey.firstIndex(of: commaASCIICode) else { 114 | throw DecoderError.searchRawKeyFailed 115 | } 116 | 117 | // 通过原始key和key结束位置组装解码器 118 | try setCipher(keyBuffer: [UInt8]([UInt8](rawKey)[0.. 300 { 147 | self.cipher = try QMRC4Cipher(originKey: decodedKey) 148 | } else { 149 | self.cipher = try QMMapCipher(originKey: decodedKey) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /QMCDecode/QMCipher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QMCDecoder.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2019/8/26. 6 | // Copyright © 2019 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// 解码器协议,通过原始数据和偏移量进行解密 12 | public protocol QMCipher { 13 | func qmDecrypt(data: Data, offset: Int) -> Data 14 | init(originKey: [UInt8]) throws 15 | } 16 | 17 | public enum QMCipherError: Error { 18 | case invalidKeyLength 19 | } 20 | 21 | /// QMStaticCipher 固定key解码器 22 | public class QMStaticCipher: QMCipher { 23 | var key: [UInt8] 24 | var keyLength: Int 25 | 26 | required public init(originKey: [UInt8]) throws { 27 | if originKey.count == 0 { 28 | throw QMCipherError.invalidKeyLength 29 | } 30 | self.key = originKey 31 | self.keyLength = key.count 32 | } 33 | 34 | public func qmDecrypt(data: Data, offset: Int) -> Data { 35 | var resultArray = [UInt8](repeating: 0, count: data.count) 36 | for (index, byte) in data.enumerated() { 37 | resultArray[index] = byte ^ getMask(offset: offset + index) 38 | } 39 | return Data(resultArray) 40 | } 41 | 42 | public func getMask(offset: Int) -> UInt8 { 43 | let temp = offset > 0x7FFF ? (offset % 0x7FFF) : offset 44 | let index = (temp * temp + 27) & 0xFF 45 | return key[index] 46 | } 47 | } 48 | 49 | /// QMMapCipher 翻转解码 50 | public class QMMapCipher: QMCipher { 51 | var key: [UInt8] 52 | var keyLength: Int 53 | 54 | required public init(originKey: [UInt8]) throws { 55 | if originKey.count == 0 { 56 | throw QMCipherError.invalidKeyLength 57 | } 58 | self.key = originKey 59 | self.keyLength = key.count 60 | } 61 | 62 | public func qmDecrypt(data: Data, offset: Int) -> Data { 63 | var resultArray = [UInt8](repeating: 0, count: data.count) 64 | for (index, byte) in data.enumerated() { 65 | resultArray[index] = byte ^ getMask(offset: offset + index) 66 | } 67 | return Data(resultArray) 68 | } 69 | 70 | public func getMask(offset: Int) -> UInt8 { 71 | let temp = offset > 0x7FFF ? (offset % 0x7FFF) : offset 72 | let index = (temp * temp + 71_214) & 0xFF 73 | return rotate(value: key[index], bits: index & 0x7) 74 | } 75 | 76 | func rotate(value: UInt8, bits: Int) -> UInt8 { 77 | let rotate = (bits + 4) % 8 78 | let left = value << rotate 79 | let right = value >> rotate 80 | return (left | right) & 0xff 81 | } 82 | } 83 | 84 | public class QMRC4Cipher: QMCipher { 85 | private let firstSegmentSize: Int = 0x80 // 128 86 | private let segmentSize: Int = 0x1_400 //5_120 87 | 88 | let originKey: [UInt8] 89 | let originKeyLength: Int 90 | let seedBox: [UInt8] 91 | var hashValue: UInt32 92 | 93 | required public init(originKey: [UInt8]) throws { 94 | if originKey.count == 0 { 95 | throw QMCipherError.invalidKeyLength 96 | } 97 | 98 | self.originKey = originKey 99 | 100 | let keyLength = originKey.count 101 | self.originKeyLength = keyLength 102 | 103 | var seedBox: [UInt8] = [UInt8](repeating: 0, count: keyLength) 104 | for index in 0.. Int { 133 | let seed = self.originKey[index % self.originKeyLength] 134 | let resultValue = Int(floor(((Double(self.hashValue) / (Double(index + 1) * Double(seed))) * 100))) 135 | return resultValue % self.originKeyLength 136 | } 137 | 138 | public func qmDecrypt(data: Data, offset: Int) -> Data { 139 | let size = data.count 140 | var toProcess = size 141 | var processed = 0 142 | var newOffset = offset 143 | 144 | @discardableResult 145 | func postProcessed(length: Int) -> Bool { 146 | toProcess -= length 147 | processed += length 148 | newOffset += length 149 | return toProcess == 0 150 | } 151 | 152 | // 初始片段 153 | var resultArray = [UInt8](data) 154 | if newOffset < firstSegmentSize { 155 | let processLength = min(size, firstSegmentSize - newOffset) 156 | var tempBuffer = [UInt8](resultArray[0.. segmentSize { 177 | var tempBuffer = [UInt8](resultArray[processed.. 0 { 185 | var tempBuffer = [UInt8](resultArray[processed..= 0 { 213 | let seedValue = Int(newSeedBox[left]) + Int(newSeedBox[right]) 214 | data[index] ^= newSeedBox[seedValue % self.originKeyLength] 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /QMCDecode/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // QMCDecode 4 | // 5 | // Created by 龚杰洪 on 2019/8/26. 6 | // Copyright © 2019 龚杰洪. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | enum QMCDecodeError: Error { 12 | case inputFileIsInvalid 13 | case outputDirectoryIsInvalid 14 | case decodeFailed 15 | case readFileToStreamFailed 16 | case outputFileStreamInvalid 17 | case notError 18 | } 19 | 20 | class ViewController: NSViewController { 21 | @IBOutlet weak var openFolderButton: NSButton! 22 | @IBOutlet weak var inputFilesTable: NSTableView! 23 | @IBOutlet weak var outputPathButton: NSButton! 24 | @IBOutlet weak var ouputPathLabel: NSTextField! 25 | @IBOutlet weak var currentFolderLabel: NSTextField! 26 | 27 | @IBOutlet weak var startButton: NSButton! 28 | @IBOutlet weak var progressView: NSProgressIndicator! 29 | 30 | lazy var outputFolderURL: URL = { 31 | let path = NSHomeDirectory() + "/Music/QMCConvertOutput/" 32 | let url = URL(fileURLWithPath: path) 33 | do { 34 | let filemanager = FileManager.default 35 | var isDirectory = ObjCBool(false) 36 | let fileExists = filemanager.fileExists(atPath: path, isDirectory: &isDirectory) 37 | if fileExists { 38 | if isDirectory.boolValue { 39 | // do nothing 40 | } else { 41 | try filemanager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) 42 | } 43 | } else { 44 | try filemanager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) 45 | } 46 | } catch { 47 | print(error) 48 | } 49 | return url 50 | }() 51 | 52 | var dataSource: [URL] = [] 53 | 54 | override func viewDidLoad() { 55 | super.viewDidLoad() 56 | 57 | openFolderButton.target = self 58 | openFolderButton.action = #selector(openInputFolder(_:)) 59 | 60 | outputPathButton.target = self 61 | outputPathButton.action = #selector(openOutputFolder(_:)) 62 | 63 | startButton.target = self 64 | startButton.action = #selector(startConvert(_:)) 65 | 66 | setupTableView() 67 | 68 | loadDefaultPath() 69 | 70 | ouputPathLabel.stringValue = outputFolderURL.path 71 | } 72 | 73 | func loadDefaultPath() { 74 | var path = NSHomeDirectory() 75 | path += "/Library/Containers/com.tencent.QQMusicMac/Data/Library/Application Support/QQMusicMac/iQmc/" 76 | let fileManager = FileManager.default 77 | do { 78 | let filesPaths = try fileManager.contentsOfDirectory(atPath: path) 79 | for filePath in filesPaths { 80 | if encryptExtDictionary.keys.contains(URL(fileURLWithPath: filePath).pathExtension) { 81 | let url = URL(fileURLWithPath: path + filePath) 82 | dataSource.append(url) 83 | } 84 | } 85 | } catch { 86 | print(error) 87 | } 88 | 89 | self.currentFolderLabel.stringValue = path 90 | 91 | self.inputFilesTable.reloadData() 92 | } 93 | 94 | func setupTableView() { 95 | inputFilesTable.dataSource = self 96 | inputFilesTable.delegate = self 97 | } 98 | 99 | override var representedObject: Any? { 100 | didSet { 101 | 102 | } 103 | } 104 | 105 | @objc func openInputFolder(_ sender: Any) { 106 | dataSource.removeAll() 107 | inputFilesTable.reloadData() 108 | 109 | let panel = NSOpenPanel() 110 | panel.canChooseFiles = true 111 | panel.canChooseDirectories = true 112 | panel.allowsMultipleSelection = true 113 | 114 | let finded = panel.runModal() 115 | 116 | if finded == .OK { 117 | var directoryArray = [String]() 118 | for url in panel.urls { 119 | var isDirectory: ObjCBool = ObjCBool(false) 120 | FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) 121 | if isDirectory.boolValue == true { 122 | do { 123 | let filesPaths = try FileManager.default.contentsOfDirectory(atPath: url.path) 124 | for filePath in filesPaths { 125 | if encryptExtDictionary.keys.contains(URL(fileURLWithPath: filePath).pathExtension) { 126 | let fileURL = url.appendingPathComponent(filePath) 127 | dataSource.append(fileURL) 128 | } 129 | } 130 | } catch { 131 | print(error) 132 | } 133 | directoryArray.append(url.path) 134 | } else { 135 | if encryptExtDictionary.keys.contains(url.pathExtension.lowercased()) { 136 | dataSource.append(url) 137 | } 138 | } 139 | } 140 | self.currentFolderLabel.stringValue = directoryArray.joined(separator: "\n") 141 | inputFilesTable.reloadData() 142 | } else { 143 | inputFilesTable.reloadData() 144 | } 145 | } 146 | 147 | @objc func openOutputFolder(_ sender: Any) { 148 | let panel = NSOpenPanel() 149 | panel.canChooseFiles = false 150 | panel.canChooseDirectories = true 151 | panel.allowsMultipleSelection = false 152 | panel.canCreateDirectories = true 153 | 154 | 155 | let finded = panel.runModal() 156 | 157 | if finded == .OK { 158 | for url in panel.urls { 159 | self.outputFolderURL = url 160 | self.ouputPathLabel.stringValue = url.path 161 | break 162 | } 163 | } else { 164 | } 165 | } 166 | 167 | @objc func startConvert(_ sender: Any) { 168 | if dataSource.count == 0 { 169 | let alert = NSAlert(error: QMCDecodeError.inputFileIsInvalid) 170 | alert.messageText = "没有可供转换的数据" 171 | alert.icon = NSImage(named: NSImage.Name()) 172 | alert.beginSheetModal(for: self.view.window!, completionHandler: { (response) in 173 | 174 | }) 175 | } 176 | 177 | self.startButton.isEnabled = false 178 | 179 | errorCount = 0 180 | 181 | succeedCount = 0 182 | 183 | let coreCount = ProcessInfo().processorCount 184 | 185 | for index in 0.. Int { 251 | return dataSource.count 252 | } 253 | 254 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 255 | return 44.0 256 | } 257 | 258 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 259 | switch tableColumn?.title { 260 | case "路径": 261 | return dataSource[row].path 262 | case "歌曲名称": 263 | return dataSource[row].lastPathComponent 264 | default: 265 | return nil 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /QMCDecode.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FE7B2936283645700051960F /* QMCKeyDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE7B2935283645700051960F /* QMCKeyDecoder.swift */; }; 11 | FE7B293D2836500C0051960F /* TeaCipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE7B293C2836500C0051960F /* TeaCipher.swift */; }; 12 | FE80112C278541B7008D5667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE80112B278541B7008D5667 /* Constants.swift */; }; 13 | FE8BFD0F2313DC490072B33B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8BFD0E2313DC490072B33B /* AppDelegate.swift */; }; 14 | FE8BFD112313DC490072B33B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8BFD102313DC490072B33B /* ViewController.swift */; }; 15 | FE8BFD132313DC4A0072B33B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FE8BFD122313DC4A0072B33B /* Assets.xcassets */; }; 16 | FE8BFD162313DC4A0072B33B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FE8BFD142313DC4A0072B33B /* Main.storyboard */; }; 17 | FE8BFD222313DC4A0072B33B /* QMCDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8BFD212313DC4A0072B33B /* QMCDecodeTests.swift */; }; 18 | FE8BFD2F2313DCF70072B33B /* QMCipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8BFD2E2313DCF70072B33B /* QMCipher.swift */; }; 19 | FE8BFD312313EDBE0072B33B /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8BFD302313EDBE0072B33B /* WindowController.swift */; }; 20 | FE968022278C38E50096F9FB /* QMDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE968021278C38E50096F9FB /* QMDecoder.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | FE8BFD1E2313DC4A0072B33B /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = FE8BFD032313DC490072B33B /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = FE8BFD0A2313DC490072B33B; 29 | remoteInfo = QMCDecode; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | FE7B2935283645700051960F /* QMCKeyDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QMCKeyDecoder.swift; sourceTree = ""; }; 35 | FE7B293C2836500C0051960F /* TeaCipher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeaCipher.swift; sourceTree = ""; }; 36 | FE80112B278541B7008D5667 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 37 | FE8BFD0B2313DC490072B33B /* QMCDecode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QMCDecode.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | FE8BFD0E2313DC490072B33B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | FE8BFD102313DC490072B33B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 40 | FE8BFD122313DC4A0072B33B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | FE8BFD152313DC4A0072B33B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | FE8BFD172313DC4A0072B33B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | FE8BFD182313DC4A0072B33B /* QMCDecode.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QMCDecode.entitlements; sourceTree = ""; }; 44 | FE8BFD1D2313DC4A0072B33B /* QMCDecodeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QMCDecodeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | FE8BFD212313DC4A0072B33B /* QMCDecodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QMCDecodeTests.swift; sourceTree = ""; }; 46 | FE8BFD232313DC4A0072B33B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | FE8BFD2E2313DCF70072B33B /* QMCipher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QMCipher.swift; sourceTree = ""; }; 48 | FE8BFD302313EDBE0072B33B /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; 49 | FE968021278C38E50096F9FB /* QMDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QMDecoder.swift; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | FE8BFD082313DC490072B33B /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | FE8BFD1A2313DC4A0072B33B /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | FE8BFD022313DC490072B33B = { 71 | isa = PBXGroup; 72 | children = ( 73 | FE8BFD0D2313DC490072B33B /* QMCDecode */, 74 | FE8BFD202313DC4A0072B33B /* QMCDecodeTests */, 75 | FE8BFD0C2313DC490072B33B /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | FE8BFD0C2313DC490072B33B /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | FE8BFD0B2313DC490072B33B /* QMCDecode.app */, 83 | FE8BFD1D2313DC4A0072B33B /* QMCDecodeTests.xctest */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | FE8BFD0D2313DC490072B33B /* QMCDecode */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | FE8BFD0E2313DC490072B33B /* AppDelegate.swift */, 92 | FE80112B278541B7008D5667 /* Constants.swift */, 93 | FE8BFD302313EDBE0072B33B /* WindowController.swift */, 94 | FE8BFD102313DC490072B33B /* ViewController.swift */, 95 | FE8BFD2E2313DCF70072B33B /* QMCipher.swift */, 96 | FE968021278C38E50096F9FB /* QMDecoder.swift */, 97 | FE7B2935283645700051960F /* QMCKeyDecoder.swift */, 98 | FE7B293C2836500C0051960F /* TeaCipher.swift */, 99 | FE8BFD122313DC4A0072B33B /* Assets.xcassets */, 100 | FE8BFD142313DC4A0072B33B /* Main.storyboard */, 101 | FE8BFD172313DC4A0072B33B /* Info.plist */, 102 | FE8BFD182313DC4A0072B33B /* QMCDecode.entitlements */, 103 | ); 104 | path = QMCDecode; 105 | sourceTree = ""; 106 | }; 107 | FE8BFD202313DC4A0072B33B /* QMCDecodeTests */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | FE8BFD212313DC4A0072B33B /* QMCDecodeTests.swift */, 111 | FE8BFD232313DC4A0072B33B /* Info.plist */, 112 | ); 113 | path = QMCDecodeTests; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXNativeTarget section */ 119 | FE8BFD0A2313DC490072B33B /* QMCDecode */ = { 120 | isa = PBXNativeTarget; 121 | buildConfigurationList = FE8BFD262313DC4A0072B33B /* Build configuration list for PBXNativeTarget "QMCDecode" */; 122 | buildPhases = ( 123 | FE8BFD072313DC490072B33B /* Sources */, 124 | FE8BFD082313DC490072B33B /* Frameworks */, 125 | FE8BFD092313DC490072B33B /* Resources */, 126 | ); 127 | buildRules = ( 128 | ); 129 | dependencies = ( 130 | ); 131 | name = QMCDecode; 132 | productName = QMCDecode; 133 | productReference = FE8BFD0B2313DC490072B33B /* QMCDecode.app */; 134 | productType = "com.apple.product-type.application"; 135 | }; 136 | FE8BFD1C2313DC4A0072B33B /* QMCDecodeTests */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = FE8BFD292313DC4A0072B33B /* Build configuration list for PBXNativeTarget "QMCDecodeTests" */; 139 | buildPhases = ( 140 | FE8BFD192313DC4A0072B33B /* Sources */, 141 | FE8BFD1A2313DC4A0072B33B /* Frameworks */, 142 | FE8BFD1B2313DC4A0072B33B /* Resources */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | FE8BFD1F2313DC4A0072B33B /* PBXTargetDependency */, 148 | ); 149 | name = QMCDecodeTests; 150 | productName = QMCDecodeTests; 151 | productReference = FE8BFD1D2313DC4A0072B33B /* QMCDecodeTests.xctest */; 152 | productType = "com.apple.product-type.bundle.unit-test"; 153 | }; 154 | /* End PBXNativeTarget section */ 155 | 156 | /* Begin PBXProject section */ 157 | FE8BFD032313DC490072B33B /* Project object */ = { 158 | isa = PBXProject; 159 | attributes = { 160 | LastSwiftUpdateCheck = 1020; 161 | LastUpgradeCheck = 1020; 162 | ORGANIZATIONNAME = "龚杰洪"; 163 | TargetAttributes = { 164 | FE8BFD0A2313DC490072B33B = { 165 | CreatedOnToolsVersion = 10.2.1; 166 | LastSwiftMigration = 1020; 167 | SystemCapabilities = { 168 | com.apple.Sandbox = { 169 | enabled = 0; 170 | }; 171 | }; 172 | }; 173 | FE8BFD1C2313DC4A0072B33B = { 174 | CreatedOnToolsVersion = 10.2.1; 175 | TestTargetID = FE8BFD0A2313DC490072B33B; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = FE8BFD062313DC490072B33B /* Build configuration list for PBXProject "QMCDecode" */; 180 | compatibilityVersion = "Xcode 9.3"; 181 | developmentRegion = en; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = FE8BFD022313DC490072B33B; 188 | productRefGroup = FE8BFD0C2313DC490072B33B /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | FE8BFD0A2313DC490072B33B /* QMCDecode */, 193 | FE8BFD1C2313DC4A0072B33B /* QMCDecodeTests */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | FE8BFD092313DC490072B33B /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | FE8BFD132313DC4A0072B33B /* Assets.xcassets in Resources */, 204 | FE8BFD162313DC4A0072B33B /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | FE8BFD1B2313DC4A0072B33B /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXResourcesBuildPhase section */ 216 | 217 | /* Begin PBXSourcesBuildPhase section */ 218 | FE8BFD072313DC490072B33B /* Sources */ = { 219 | isa = PBXSourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | FE7B2936283645700051960F /* QMCKeyDecoder.swift in Sources */, 223 | FE7B293D2836500C0051960F /* TeaCipher.swift in Sources */, 224 | FE80112C278541B7008D5667 /* Constants.swift in Sources */, 225 | FE8BFD112313DC490072B33B /* ViewController.swift in Sources */, 226 | FE8BFD312313EDBE0072B33B /* WindowController.swift in Sources */, 227 | FE8BFD2F2313DCF70072B33B /* QMCipher.swift in Sources */, 228 | FE968022278C38E50096F9FB /* QMDecoder.swift in Sources */, 229 | FE8BFD0F2313DC490072B33B /* AppDelegate.swift in Sources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | FE8BFD192313DC4A0072B33B /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | FE8BFD222313DC4A0072B33B /* QMCDecodeTests.swift in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXSourcesBuildPhase section */ 242 | 243 | /* Begin PBXTargetDependency section */ 244 | FE8BFD1F2313DC4A0072B33B /* PBXTargetDependency */ = { 245 | isa = PBXTargetDependency; 246 | target = FE8BFD0A2313DC490072B33B /* QMCDecode */; 247 | targetProxy = FE8BFD1E2313DC4A0072B33B /* PBXContainerItemProxy */; 248 | }; 249 | /* End PBXTargetDependency section */ 250 | 251 | /* Begin PBXVariantGroup section */ 252 | FE8BFD142313DC4A0072B33B /* Main.storyboard */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | FE8BFD152313DC4A0072B33B /* Base */, 256 | ); 257 | name = Main.storyboard; 258 | sourceTree = ""; 259 | }; 260 | /* End PBXVariantGroup section */ 261 | 262 | /* Begin XCBuildConfiguration section */ 263 | FE8BFD242313DC4A0072B33B /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_ANALYZER_NONNULL = YES; 268 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | CODE_SIGN_IDENTITY = "Mac Developer"; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = dwarf; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | ENABLE_TESTABILITY = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu11; 301 | GCC_DYNAMIC_NO_PIC = NO; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_OPTIMIZATION_LEVEL = 0; 304 | GCC_PREPROCESSOR_DEFINITIONS = ( 305 | "DEBUG=1", 306 | "$(inherited)", 307 | ); 308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 310 | GCC_WARN_UNDECLARED_SELECTOR = YES; 311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 312 | GCC_WARN_UNUSED_FUNCTION = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | MACOSX_DEPLOYMENT_TARGET = 10.14; 315 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 316 | MTL_FAST_MATH = YES; 317 | ONLY_ACTIVE_ARCH = YES; 318 | SDKROOT = macosx; 319 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 320 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 321 | }; 322 | name = Debug; 323 | }; 324 | FE8BFD252313DC4A0072B33B /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_ANALYZER_NONNULL = YES; 329 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 331 | CLANG_CXX_LIBRARY = "libc++"; 332 | CLANG_ENABLE_MODULES = YES; 333 | CLANG_ENABLE_OBJC_ARC = YES; 334 | CLANG_ENABLE_OBJC_WEAK = YES; 335 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 336 | CLANG_WARN_BOOL_CONVERSION = YES; 337 | CLANG_WARN_COMMA = YES; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 341 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 342 | CLANG_WARN_EMPTY_BODY = YES; 343 | CLANG_WARN_ENUM_CONVERSION = YES; 344 | CLANG_WARN_INFINITE_RECURSION = YES; 345 | CLANG_WARN_INT_CONVERSION = YES; 346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 347 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 348 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 350 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 351 | CLANG_WARN_STRICT_PROTOTYPES = YES; 352 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 353 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | CODE_SIGN_IDENTITY = "Mac Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 359 | ENABLE_NS_ASSERTIONS = NO; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu11; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | MACOSX_DEPLOYMENT_TARGET = 10.14; 370 | MTL_ENABLE_DEBUG_INFO = NO; 371 | MTL_FAST_MATH = YES; 372 | SDKROOT = macosx; 373 | SWIFT_COMPILATION_MODE = wholemodule; 374 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 375 | }; 376 | name = Release; 377 | }; 378 | FE8BFD272313DC4A0072B33B /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 382 | CLANG_ENABLE_MODULES = YES; 383 | CODE_SIGN_IDENTITY = "-"; 384 | CODE_SIGN_STYLE = Manual; 385 | COMBINE_HIDPI_IMAGES = YES; 386 | CURRENT_PROJECT_VERSION = 20210325.144915; 387 | DEVELOPMENT_TEAM = ""; 388 | INFOPLIST_FILE = QMCDecode/Info.plist; 389 | LD_RUNPATH_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "@executable_path/../Frameworks", 392 | ); 393 | MACOSX_DEPLOYMENT_TARGET = 10.15.4; 394 | MARKETING_VERSION = 1.0.4; 395 | PRODUCT_BUNDLE_IDENTIFIER = cxylg.app.macos.QMCDecode; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | PROVISIONING_PROFILE_SPECIFIER = ""; 398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 399 | SWIFT_VERSION = 5.0; 400 | }; 401 | name = Debug; 402 | }; 403 | FE8BFD282313DC4A0072B33B /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 407 | CLANG_ENABLE_MODULES = YES; 408 | CODE_SIGN_IDENTITY = "-"; 409 | CODE_SIGN_STYLE = Manual; 410 | COMBINE_HIDPI_IMAGES = YES; 411 | CURRENT_PROJECT_VERSION = 20210325.144915; 412 | DEVELOPMENT_TEAM = ""; 413 | INFOPLIST_FILE = QMCDecode/Info.plist; 414 | LD_RUNPATH_SEARCH_PATHS = ( 415 | "$(inherited)", 416 | "@executable_path/../Frameworks", 417 | ); 418 | MACOSX_DEPLOYMENT_TARGET = 10.15.4; 419 | MARKETING_VERSION = 1.0.4; 420 | PRODUCT_BUNDLE_IDENTIFIER = cxylg.app.macos.QMCDecode; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | PROVISIONING_PROFILE_SPECIFIER = ""; 423 | SWIFT_VERSION = 5.0; 424 | }; 425 | name = Release; 426 | }; 427 | FE8BFD2A2313DC4A0072B33B /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 431 | BUNDLE_LOADER = "$(TEST_HOST)"; 432 | CODE_SIGN_STYLE = Automatic; 433 | COMBINE_HIDPI_IMAGES = YES; 434 | DEVELOPMENT_TEAM = SRVW87Y85X; 435 | INFOPLIST_FILE = QMCDecodeTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = ( 437 | "$(inherited)", 438 | "@executable_path/../Frameworks", 439 | "@loader_path/../Frameworks", 440 | ); 441 | PRODUCT_BUNDLE_IDENTIFIER = cxylg.app.macos.QMCDecodeTests; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | SWIFT_VERSION = 5.0; 444 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QMCDecode.app/Contents/MacOS/QMCDecode"; 445 | }; 446 | name = Debug; 447 | }; 448 | FE8BFD2B2313DC4A0072B33B /* Release */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 452 | BUNDLE_LOADER = "$(TEST_HOST)"; 453 | CODE_SIGN_STYLE = Automatic; 454 | COMBINE_HIDPI_IMAGES = YES; 455 | DEVELOPMENT_TEAM = SRVW87Y85X; 456 | INFOPLIST_FILE = QMCDecodeTests/Info.plist; 457 | LD_RUNPATH_SEARCH_PATHS = ( 458 | "$(inherited)", 459 | "@executable_path/../Frameworks", 460 | "@loader_path/../Frameworks", 461 | ); 462 | PRODUCT_BUNDLE_IDENTIFIER = cxylg.app.macos.QMCDecodeTests; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | SWIFT_VERSION = 5.0; 465 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QMCDecode.app/Contents/MacOS/QMCDecode"; 466 | }; 467 | name = Release; 468 | }; 469 | /* End XCBuildConfiguration section */ 470 | 471 | /* Begin XCConfigurationList section */ 472 | FE8BFD062313DC490072B33B /* Build configuration list for PBXProject "QMCDecode" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | FE8BFD242313DC4A0072B33B /* Debug */, 476 | FE8BFD252313DC4A0072B33B /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | FE8BFD262313DC4A0072B33B /* Build configuration list for PBXNativeTarget "QMCDecode" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | FE8BFD272313DC4A0072B33B /* Debug */, 485 | FE8BFD282313DC4A0072B33B /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | FE8BFD292313DC4A0072B33B /* Build configuration list for PBXNativeTarget "QMCDecodeTests" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | FE8BFD2A2313DC4A0072B33B /* Debug */, 494 | FE8BFD2B2313DC4A0072B33B /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | /* End XCConfigurationList section */ 500 | }; 501 | rootObject = FE8BFD032313DC490072B33B /* Project object */; 502 | } 503 | -------------------------------------------------------------------------------- /QMCDecode/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | Default 482 | 483 | 484 | 485 | 486 | 487 | 488 | Left to Right 489 | 490 | 491 | 492 | 493 | 494 | 495 | Right to Left 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Default 507 | 508 | 509 | 510 | 511 | 512 | 513 | Left to Right 514 | 515 | 516 | 517 | 518 | 519 | 520 | Right to Left 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 676 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 732 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | --------------------------------------------------------------------------------