尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

QMCDecode解码引擎全解析:从文件末尾的密钥到一键转出无损音频

QMCDecode解码引擎全解析:从文件末尾的密钥到一键转出无损音频 QMCDecode解码引擎全解析从文件末尾的密钥到一键转出无损音频【免费下载链接】QMCDecodeQQ音乐QMC格式转换为普通格式(qmcflac转flacqmc0,qmc3转mp3, mflac,mflac0等转flac)仅支持macOS可自动识别到QQ音乐下载目录默认转换结果存储到~/Music/QMCConvertOutput,可自定义需要转换的文件和输出路径项目地址: https://gitcode.com/gh_mirrors/qm/QMCDecode从QQ音乐下载的歌曲拿到手却是.qmcflac、.mflac这类无法直接播放的 QMC 加密格式相信不少 macOS 用户都卡在这一步。QMCDecode 正是为解决这个问题而生的 macOS 专用 QMC 格式解码引擎它能自动扫描 QQ 音乐客户端下载目录把 qmcflac 转 flac、qmc0/qmc3 转 mp3、mflac/mflac0 转 flac并默认把结果输出到~/Music/QMCConvertOutput。本文不按功能清单罗列而是沿着密钥藏在哪 → 密钥怎么复原 → 音频怎么解密这条主线把整个解码引擎拆给你看最后带你亲手编译和跑通它。一个看似简单的需求为什么不能直接改后缀名有人会问QMC 加密音频无非是改了个壳把扩展名改成.flac不就行了如果真这么简单这个项目就没有存在的必要了。你只要在终端里试一下mv song.qmcflac song.flac再用播放器打开大概率会收到报错。原因在于QMC 文件的音频正文被逐字节做了异或运算且加密所依赖的密钥以特定结构挂在文件尾部。只有拿到密钥、恢复出正确的字节流文件才真正能播。这也引出了 QMCDecode 解码引擎的第一道工序从文件尾部把密钥找出来。第一站密钥到底藏在文件哪里打开QMDecoder.swift你会发现整个解码流程的起点不是解密而是找钥匙。看searchKey()的核心逻辑// QMDecoder.swift 片段从文件尾部提取密钥 try fileHandle.seek(toOffset: UInt64(self.originFileLength - 4)) guard let lastFourBytes try fileHandle.readToEnd() else { throw DecoderError.canNotReadFile } // 移动端下载的文件以 QTag 四个字节结尾 if String(bytes: lastFourBytes, encoding: .utf8) QTag { // 紧邻 QTag 前 4 字节是 key 的长度 let keySize sizeBuffer.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian } self.realAudioSize self.originFileLength - Int(keySize) - 8 // 在 realAudioSize 偏移处读取原始 key try fileHandle.seek(toOffset: UInt64(self.realAudioSize)) guard let rawKey try fileHandle.read(upToCount: Int(keySize)) else { throw DecoderError.canNotReadRawKeyBuffer } // 以逗号作为 key 的结束标记截取有效部分 guard let keyEndIndex rawKey.firstIndex(of: commaASCIICode) else { throw DecoderError.searchRawKeyFailed } try setCipher(keyBuffer: UInt8[0..keyEndIndex])) }这里有一个很关键的工程细节密钥并不总是在固定位置。QMCDecode 通过文件尾标记做了分流你可以在下表中直观看到两种存放方式的差异文件来源尾部特征密钥定位方式音频实际长度计算移动端下载以QTag结尾读取前 4 字节大端长度再按长度截取 key文件总长 - keySize - 8PC/macOS 端下载尾 4 字节为小端长度keySize 小于0x300时按长度截取文件总长 - keySize - 4PC/macOS 端下载同上keySize 大于等于0x300时使用内置privateKey256静态解密等于文件总长直接整段处理值得注意的是第三种情况当尾部的长度值超过0x300768时QMCDecode 不再尝试提取 key而是直接使用Constants.swift中内置的 256 字节固定密钥privateKey256走静态解密分支。这意味着它兼容了 QQ 音乐不同时期、不同端产生的多种封装。第二站Base64 之外的 TEA 密钥复原从文件里拿到的 rawKey 并不是最终用于解密的密钥它只是 Base64 编码后的中间产物。QMCKeyDecoder.swift负责完成还原这一步核心是一个定制化的 TEA 解密流程。我们拆开看// QMCKeyDecoder.swift 片段生成 simpleKey 的巧妙手法 fileprivate func simpleMakeKey(seed: UInt8, length: Int) - [UInt8] { var result UInt8 for index in 0..length { // 用正切函数 放大 100 倍产出看似随机的种子字节 result[index] UInt8(fabs(tan(Double(seed) Double(index) * 0.1)) * 100.0) } return result }simpleMakeKey(seed: 106, length: 8)用三角函数做伪随机源生成 8 字节种子随后把种子与 Base64 解码后的前 8 字节交替穿插凑成 16 字节的 TEA 密钥let simpleKey simpleMakeKey(seed: 106, length: 8) var teaKey UInt8 for index in 0..8 { teaKey[index 1] simpleKey[index] // 偶数位放种子 teaKey[(index 1) 1] base64DecodedKey[index] // 奇数位放解码数据 }拼接好密钥后decryptTencentTea会调用TeaCipher做 32 轮 TEA 解密并手工实现 CBC 模式逐块解出内容。TeaCipher.swift的实现也很直白就是标准的 TEA 分组算法// TeaCipher.swift 片段32 位整型溢出下的一轮解密 var sum: UInt32 delta * (self.rounds / 2) // delta 0x9e3779b9 for _ in 0..self.rounds/2 { v1 v1 - (((v04) key2) ^ (v0 sum) ^ ((v05) key3)) v0 v0 - (((v14) key0) ^ (v1 sum) ^ ((v15) key1)) sum sum - delta }源码里甚至留了一句开发者的自嘲注释——这个算法整个在 32 位的框框内运行所以所有运算都用-、、*这类溢出安全运算符。最终解密结果的前 8 字节与 Base64 解码后的前 8 字节重新拼装才得到真正的音频密钥。第三站三种解密器三种加密代际密钥到手后QMDecoder.setCipher会根据密钥长度选择解密器长度大于 300 用 RC4否则用 Map。如果你继续往里看QMCipher.swift会发现整个解码引擎的加密体系由统一协议QMCipher收口public protocol QMCipher { func qmDecrypt(data: Data, offset: Int) - Data init(originKey: [UInt8]) throws }三个实现类各有性格对比如下解密器掩码生成方式复杂度典型适用场景特点QMStaticCipher(offset² 27) 0xFF直接查表极低固定密钥的整段文件逻辑最简单一次异或即完成QMMapCipher(offset² 71214) 0xFF后做 8 位循环位移低解密后 key 长度 ≤ 300 的文件比 Static 多一步 rotate 位移QMRC4Cipher基于 key 初始化的 S-box 分段流式处理高解密后 key 长度 300 的文件分 128 字节首段与 5120 字节常规段静态与映射两类实现的核心都是生成掩码 → 与字节异或区别只在掩码公式的常数与是否加旋转。看 QMMapCipher 的这一小段// QMCipher.swift 片段Map 掩码带 8 位循环旋转 public func getMask(offset: Int) - UInt8 { let temp offset 0x7FFF ? (offset % 0x7FFF) : offset let index (temp * temp 71_214) 0xFF return rotate(value: key[index], bits: index 0x7) } func rotate(value: UInt8, bits: Int) - UInt8 { let rotate (bits 4) % 8 return ((value rotate) | (value rotate)) 0xff }而 QMRC4Cipher 则完全是另一个量级初始化阶段先对 key 做 KSA 置换生成seedBox再用hashValue与取模运算推导每个分段的段密钥解密时把数据切成首段 128 字节 对齐段 批量 5120 字节段分别处理其中encodeAllSegment内部对每一段都重新拷贝一份 S-box 再执行 PRGA 流式异或。这种按段初始化、流式处理的设计让 RC4 分支在保证兼容性的同时不会因为偏移计算复杂而拖慢大文件的转换速度。动手跑起来三分钟完成编译与首次转换纸上谈兵不如上手。整个过程只需四步获取源码并打开工程git clone https://gitcode.com/gh_mirrors/qm/QMCDecode cd QMCDecode open QMCDecode.xcodeproj在 Xcode 中把签名 Team 改为你自己的开发者账号或个人免签名调试。按Cmd B编译按Cmd R运行。点击打开文件夹按钮选择输入源。运行后的界面非常克制左侧表格列出待转换文件自动填充 QQ 音乐缓存目录~/Library/Containers/com.tencent.QQMusicMac/Data/Library/Application Support/QQMusicMac/iQmc/右侧显示输出目录底部一个开始转换按钮加进度条。界面下方同样支持手动选择任意目录或单文件输出路径可自定义默认仍是~/Music/QMCConvertOutput。点击转换后应用会按 CPU 物理核心数创建等量DispatchQueue把文件轮流分发到各队列并行处理——源码注释甚至调侃这设计是尽量跑死 CPU。// ViewController.swift 片段按 CPU 核心数构建并行队列 let coreCount ProcessInfo().processorCount for index in 0..coreCount { result.append(DispatchQueue(label: QMCDecode.Convert.Queue\(index), qos: .utility)) } // 文件按序号取模分发到不同队列实现多核并行转换 let queue queueArray[index % coreCount]进度与结果统计通过DispatchQueue.main.async回主线程刷新转换结束后弹出成功/失败汇总弹窗配合Success.png图标给出明确的完成反馈。支持格式一览一张表看懂映射规则Constants.swift中的encryptExtDictionary就是全项目的格式户口本输入扩展名、输出扩展名、加密代际一一对应。下表为完整映射输入格式输出格式加密代际典型场景.qmcflac / .qmflac / .bkcflac.flacv2 / v1无损音质优先.mflac / .mflac0.flacv2RC4 加密的无损文件.qmc0 / .qmc3 / .bkcmp3.mp3v1常见流媒体音质.qmc2 / .mgg / .mgg1 / .qmcogg.oggv2压缩格式.tkm / .6d3461.m4av1苹果生态兼容.6d7033 / .776176.mp3 / .wavv1十六进制扩展名变体注意最后几行666c6163、6d7033、6f6767、6d3461、776176是十六进制字符对应的扩展名编码分别代表 flac、mp3、ogg、m4a、wav。这意味着某些文件的扩展名本身就是加密后的十六进制字符串QMCDecode 同样能识别并还原覆盖面比 README 列出的还要广。高频报错的一键排查法实测中你大概率会遇到下面三类问题按顺序排查即可文件列表为空先确认文件扩展名是否在支持清单内再确认文件是否下载完整手动选择目录时注意应用只识别大小写不敏感的标准扩展名。转换失败率高优先检查输出目录是否可写、磁盘是否充足大量文件同时转换时可减少文件数量分批进行避免内存与 IO 打满。转换成功但标签错乱这是 QMC 转换的通病。QMCDecode 只负责还原音频数据不负责修复元数据README 给出的方案是用 kid3 这类标签工具批量重写 TITLE、ARTIST 等信息与解码引擎配合即可得到完整可用的音乐库。总结与可延伸的方向回头看这个项目它的设计并不复杂却处处体现工程取舍用统一协议QMCipher收拢三类算法用尾部标记分流不同端的密钥结构用固定密钥兜底兼容旧版本用 CPU 核心数并行队列榨干多核性能。核心代码量很小几乎没有任何过度设计这正是它值得阅读的地方。如果你打算在此基础上继续折腾可以尝试的方向包括为QMCipher协议新增新一代算法实现并注册到encryptExtDictionary把QMDecoder从 Cocoa 层解耦成独立的命令行工具或 Swift Package供其他 macOS 应用集成以及补全QMCDecodeTests中的测试用例——目前测试文件还只是 Xcode 默认模板给searchKey和三种解密器各写一组数据驱动的单元测试会是一个极佳的学习起点。需要提醒的是本项目仅供学习与技术交流请务必只转换个人合法获得的音频文件尊重版权与相关法律法规。【免费下载链接】QMCDecodeQQ音乐QMC格式转换为普通格式(qmcflac转flacqmc0,qmc3转mp3, mflac,mflac0等转flac)仅支持macOS可自动识别到QQ音乐下载目录默认转换结果存储到~/Music/QMCConvertOutput,可自定义需要转换的文件和输出路径项目地址: https://gitcode.com/gh_mirrors/qm/QMCDecode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表