├── 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 | 
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 |
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 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
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 |
--------------------------------------------------------------------------------