From 41a45176beabf23bc97b0736087b73ba59e82709 Mon Sep 17 00:00:00 2001 From: MengYX Date: Tue, 11 Feb 2020 14:33:45 +0800 Subject: [PATCH] Use Universal Decoder for Qmc,Mgg,Mflac --- src/decrypt/common.js | 10 +-- src/decrypt/mflac.js | 55 --------------- src/decrypt/mgg.js | 60 ---------------- src/decrypt/qmc.js | 94 +++++++++++++++++--------- src/decrypt/{qmcmask.js => qmcMask.js} | 0 5 files changed, 65 insertions(+), 154 deletions(-) delete mode 100644 src/decrypt/mflac.js delete mode 100644 src/decrypt/mgg.js rename src/decrypt/{qmcmask.js => qmcMask.js} (100%) diff --git a/src/decrypt/common.js b/src/decrypt/common.js index 29fc926..ed38341 100644 --- a/src/decrypt/common.js +++ b/src/decrypt/common.js @@ -1,8 +1,6 @@ const NcmDecrypt = require("./ncm"); const QmcDecrypt = require("./qmc"); const RawDecrypt = require("./raw"); -const MFlacDecrypt = require("./mflac"); -const MggDecrypt = require("./mgg"); const TmDecrypt = require("./tm"); @@ -31,13 +29,9 @@ export async function CommonDecrypt(file) { case "tkm"://QQ Music Accompaniment M4a case "bkcmp3"://Moo Music Mp3 case "bkcflac"://Moo Music Flac - rt_data = await QmcDecrypt.Decrypt(file.raw, raw_filename, raw_ext); - break; case "mflac"://QQ Music Desktop Flac - rt_data = await MFlacDecrypt.Decrypt(file.raw, raw_filename, raw_ext); - break; - case "mgg": - rt_data = await MggDecrypt.Decrypt(file.raw, raw_filename, raw_ext); + case "mgg": //QQ Music Desktop Ogg + rt_data = await QmcDecrypt.Decrypt(file.raw, raw_filename, raw_ext); break; case "tm2":// QQ Music IOS M4a case "tm6":// QQ Music IOS M4a diff --git a/src/decrypt/mflac.js b/src/decrypt/mflac.js deleted file mode 100644 index d012f03..0000000 --- a/src/decrypt/mflac.js +++ /dev/null @@ -1,55 +0,0 @@ -const musicMetadata = require("music-metadata-browser"); -import {GetArrayBuffer, GetCoverURL, GetFileInfo} from "./util" - -import * as mask from "./qmcmask" - -export async function Decrypt(file, raw_filename, raw_ext) { - // 获取扩展名 - if (raw_ext !== "mflac") return { - status: false, - message: "File type is incorrect!", - }; - // 读取文件 - const fileBuffer = await GetArrayBuffer(file); - const audioData = new Uint8Array(fileBuffer.slice(0, -0x170)); - //const audioDataLen = audioData.length; - - // 转换数据 - const seed = mask.QmcMaskDetectMflac(audioData); - if (seed === undefined) return { - status: false, - message: "此音乐无法解锁,目前mflac格式不提供完整支持", - }; - const dec = seed.Decrypt(audioData); - // 导出 - const musicData = new Blob([dec], {type: "audio/flac"}); - - // 读取Meta - let tag = await musicMetadata.parseBlob(musicData); - const info = GetFileInfo(tag.common.artist, tag.common.title, raw_filename); - //reportKeyInfo(new Uint8Array(fileBuffer.slice(-0x170)), seed.mask128, - // info.artist, info.title, tag.common.album, raw_filename); - - // 返回 - return { - status: true, - title: info.title, - artist: info.artist, - ext: 'flac', - album: tag.common.album, - picture: GetCoverURL(tag), - file: URL.createObjectURL(musicData), - mime: "audio/flac" - } -} - -function reportKeyInfo(keyData, maskData, artist, title, album, filename) { - fetch("https://stats.ixarea.com/collect/mflac/mask", { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - Mask: Array.from(maskData), Key: Array.from(keyData), - Artist: artist, Title: title, Album: album, Filename: filename - }), - }).then().catch() -} diff --git a/src/decrypt/mgg.js b/src/decrypt/mgg.js deleted file mode 100644 index 7de353a..0000000 --- a/src/decrypt/mgg.js +++ /dev/null @@ -1,60 +0,0 @@ -const musicMetadata = require("music-metadata-browser"); -const util = require("./util"); - -import * as mask from "./qmcmask" - -//todo: combine qmc mflac mgg -export async function Decrypt(file, raw_filename, raw_ext) { - // 获取扩展名 - if (raw_ext !== "mgg") return { - status: false, - message: "File type is incorrect!", - }; - // 读取文件 - const fileBuffer = await util.GetArrayBuffer(file); - const audioData = new Uint8Array(fileBuffer.slice(0, -0x170)); - const audioDataLen = audioData.length; - const keyData = new Uint8Array(fileBuffer.slice(-0x170)); - const headData = new Uint8Array(fileBuffer.slice(0, 170)); - let seed = mask.QmcMaskDetectMgg(headData); - if (seed === undefined) { - return { - status: false, - message: "此音乐无法解锁,目前mgg格式仅提供试验性支持", - }; - /*try { - let resp = await queryKeyInfo(keyData, headData, raw_filename); - let data = await resp.json(); - seed = mask.QmcMaskCreate128(data.Mask); - } catch (e) {}*/ - } - const dec = seed.Decrypt(audioData); - // 导出 - const musicData = new Blob([dec], {type: "audio/ogg"}); - - // 读取Meta - let tag = await musicMetadata.parseBlob(musicData); - const info = util.GetFileInfo(tag.common.artist, tag.common.title, raw_filename); - - // 返回 - return { - status: true, - title: info.title, - artist: info.artist, - ext: 'ogg', - album: tag.common.album, - picture: util.GetCoverURL(tag), - file: URL.createObjectURL(musicData), - mime: "audio/ogg" - } - -} - - -function queryKeyInfo(keyData, headData, filename) { - return fetch("http://127.0.0.1:6580/mgg/query", { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({Key: Array.from(keyData), Head: Array.from(headData), Filename: filename}), - }) -} diff --git a/src/decrypt/qmc.js b/src/decrypt/qmc.js index 673d59e..b2377af 100644 --- a/src/decrypt/qmc.js +++ b/src/decrypt/qmc.js @@ -1,46 +1,78 @@ -const musicMetadata = require("music-metadata-browser"); -const util = require("./util"); -import * as mask from "./qmcmask" +import {AudioMimeType, GetArrayBuffer, GetCoverURL, GetFileInfo} from "./util"; +import * as mask from "./qmcMask" -const OriginalExtMap = { - "qmc0": "mp3", - "qmc3": "mp3", - "qmcogg": "ogg", - "qmcflac": "flac", - "bkcmp3": "mp3", - "bkcflac": "flac", - "tkm": "m4a" +const musicMetadata = require("music-metadata-browser"); + +const HandlerMap = { + "mgg": {handler: mask.QmcMaskDetectMgg, ext: "ogg", detect: true}, + "mflac": {handler: mask.QmcMaskDetectMflac, ext: "flac", detect: true}, + "qmc0": {handler: mask.QmcMaskGetDefault, ext: "mp3", detect: false}, + "qmc3": {handler: mask.QmcMaskGetDefault, ext: "mp3", detect: false}, + "qmcogg": {handler: mask.QmcMaskGetDefault, ext: "ogg", detect: false}, + "qmcflac": {handler: mask.QmcMaskGetDefault, ext: "flac", detect: false}, + "bkcmp3": {handler: mask.QmcMaskGetDefault, ext: "mp3", detect: false}, + "bkcflac": {handler: mask.QmcMaskGetDefault, ext: "flac", detect: false}, + "tkm": {handler: mask.QmcMaskGetDefault, ext: "m4a", detect: false} }; //todo: use header to detect media type export async function Decrypt(file, raw_filename, raw_ext) { - // 获取扩展名 - if (!(raw_ext in OriginalExtMap)) { - return {status: false, message: "File type is incorrect!"} - } - const new_ext = OriginalExtMap[raw_ext]; - const mime = util.AudioMimeType[new_ext]; - // 读取文件 - const fileBuffer = await util.GetArrayBuffer(file); - const audioData = new Uint8Array(fileBuffer); - // 转换数据 - const seed = mask.QmcMaskGetDefault(); - const dec = seed.Decrypt(audioData); - // 导出 - const musicData = new Blob([dec], {type: mime}); - // 读取Meta - const tag = await musicMetadata.parseBlob(musicData); - const info = util.GetFileInfo(tag.common.artist, tag.common.title, raw_filename); + if (!(raw_ext in HandlerMap)) return {status: false, message: "File type is incorrect!"}; + const handler = HandlerMap[raw_ext]; - // 返回 + const fileData = new Uint8Array(await GetArrayBuffer(file)); + let audioData, seed, keyData; + if (handler.detect) { + audioData = fileData.slice(0, -0x170); + seed = handler.handler(audioData); + keyData = fileData.slice(-0x170); + if (seed === undefined) seed = await queryKeyInfo(keyData, raw_filename, raw_ext); + if (seed === undefined) return {status: false, message: "此格式仅提供实验性支持!"}; + } else { + audioData = fileData; + seed = handler.handler(audioData); + } + const dec = seed.Decrypt(audioData); + + const mime = AudioMimeType[handler.ext]; + const musicData = new Blob([dec], {type: mime}); + + const tag = await musicMetadata.parseBlob(musicData); + const info = GetFileInfo(tag.common.artist, tag.common.title, raw_filename); + if (handler.detect) reportKeyUsage(keyData, seed.Matrix128, + info.artist, info.title, tag.common.album, raw_filename, raw_ext); return { status: true, title: info.title, artist: info.artist, - ext: new_ext, + ext: handler.ext, album: tag.common.album, - picture: util.GetCoverURL(tag), + picture: GetCoverURL(tag), file: URL.createObjectURL(musicData), mime: mime } } + +function reportKeyUsage(keyData, maskData, artist, title, album, filename, format) { + fetch("https://stats.ixarea.com/collect/qmcmask/usage", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + Mask: Array.from(maskData), Key: Array.from(keyData), + Artist: artist, Title: title, Album: album, Filename: filename, Format: format + }), + }).then().catch() +} + +async function queryKeyInfo(keyData, filename, format) { + try { + const resp = await fetch("https://stats.ixarea.com/collect/qmcmask/query", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({Format: format, Key: Array.from(keyData), Filename: filename}), + }); + let data = await resp.json(); + return mask.QmcMaskCreate58(data.Matrix58, data.Super58A, data.Super58B); + } catch (e) { + } +} diff --git a/src/decrypt/qmcmask.js b/src/decrypt/qmcMask.js similarity index 100% rename from src/decrypt/qmcmask.js rename to src/decrypt/qmcMask.js