diff --git a/src/decrypt/common.js b/src/decrypt/common.js index ed38341..cef10f9 100644 --- a/src/decrypt/common.js +++ b/src/decrypt/common.js @@ -3,7 +3,6 @@ const QmcDecrypt = require("./qmc"); const RawDecrypt = require("./raw"); const TmDecrypt = require("./tm"); - export async function CommonDecrypt(file) { let raw_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase(); let raw_filename = file.name.substring(0, file.name.lastIndexOf(".")); diff --git a/src/decrypt/ncm.js b/src/decrypt/ncm.js index 01fd23e..a28808f 100644 --- a/src/decrypt/ncm.js +++ b/src/decrypt/ncm.js @@ -2,7 +2,7 @@ const CryptoJS = require("crypto-js"); const ID3Writer = require("browser-id3-writer"); const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857"); const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728"); -import {AudioMimeType, GetArrayBuffer} from "./util" +import {AudioMimeType, DetectAudioExt, GetArrayBuffer} from "./util" export async function Decrypt(file) { @@ -23,27 +23,16 @@ export async function Decrypt(file) { let audioOffset = musicMetaObj.offset + dataView.getUint32(musicMetaObj.offset + 5, true) + 13; let audioData = new Uint8Array(fileBuffer, audioOffset); - for (let cur = 0; cur < audioData.length; ++cur) { - audioData[cur] ^= keyBox[cur & 0xff]; - } + for (let cur = 0; cur < audioData.length; ++cur) audioData[cur] ^= keyBox[cur & 0xff]; + + if (musicMeta.format === undefined) musicMeta.format = DetectAudioExt(audioData, "mp3"); - if (musicMeta.format === undefined) { - const [f, L, a, C] = audioData; - if (f === 0x66 && L === 0x4c && a === 0x61 && C === 0x43) { - musicMeta.format = "flac"; - } else { - musicMeta.format = "mp3"; - } - } const mime = AudioMimeType[musicMeta.format]; const artists = []; - musicMeta.artist.forEach(arr => { - artists.push(arr[0]); - }); - if (musicMeta.format === "mp3") { + musicMeta.artist.forEach(arr => artists.push(arr[0])); + if (musicMeta.format === "mp3") audioData = await writeID3(audioData, artists, musicMeta.musicName, musicMeta.album, musicMeta.albumPic) - } const musicData = new Blob([audioData], {type: mime}); return { diff --git a/src/decrypt/qmc.js b/src/decrypt/qmc.js index d1db442..b0da496 100644 --- a/src/decrypt/qmc.js +++ b/src/decrypt/qmc.js @@ -1,4 +1,4 @@ -import {AudioMimeType, GetArrayBuffer, GetCoverURL, GetFileInfo} from "./util"; +import {AudioMimeType, DetectAudioExt, GetArrayBuffer, GetCoverURL, GetFileInfo} from "./util"; import {QmcMaskCreate58, QmcMaskGetDefault, QmcMaskDetectMgg, QmcMaskDetectMflac} from "./qmcMask"; const musicMetadata = require("music-metadata-browser"); @@ -15,7 +15,6 @@ const HandlerMap = { "tkm": {handler: 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 HandlerMap)) return {status: false, message: "File type is incorrect!"}; const handler = HandlerMap[raw_ext]; @@ -27,14 +26,16 @@ export async function Decrypt(file, raw_filename, raw_ext) { 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: "此格式仅提供实验性支持!"}; + if (seed === undefined) return {status: false, message: raw_ext + "格式仅提供实验性支持!"}; } else { audioData = fileData; seed = handler.handler(audioData); } const dec = seed.Decrypt(audioData); - const mime = AudioMimeType[handler.ext]; + const ext = DetectAudioExt(dec, handler.ext); + const mime = AudioMimeType[ext]; + const musicData = new Blob([dec], {type: mime}); const tag = await musicMetadata.parseBlob(musicData); @@ -45,7 +46,7 @@ export async function Decrypt(file, raw_filename, raw_ext) { status: true, title: info.title, artist: info.artist, - ext: handler.ext, + ext: ext, album: tag.common.album, picture: GetCoverURL(tag), file: URL.createObjectURL(musicData), diff --git a/src/decrypt/qmcMask.js b/src/decrypt/qmcMask.js index 2a88f4f..7403fc1 100644 --- a/src/decrypt/qmcMask.js +++ b/src/decrypt/qmcMask.js @@ -110,10 +110,7 @@ export function QmcMaskDetectMflac(data) { for (let block_idx = 0; block_idx < search_len; block_idx += 128) { try { mask = new QmcMask(data.slice(block_idx, block_idx + 128)); - if (!mask.Decrypt(data.slice(0, FLAC_HEADER.length)) - .every((val, idx) => { - return val === FLAC_HEADER[idx]; - })) break; + if (!IsBytesEqual(FLAC_HEADER, mask.Decrypt(data.slice(0, FLAC_HEADER.length)))) break; } catch (e) { } } diff --git a/src/decrypt/raw.js b/src/decrypt/raw.js index b36482b..d6788ed 100644 --- a/src/decrypt/raw.js +++ b/src/decrypt/raw.js @@ -1,17 +1,23 @@ const musicMetadata = require("music-metadata-browser"); -import {GetCoverURL, GetFileInfo, AudioMimeType} from "./util"; +import {GetCoverURL, GetFileInfo, AudioMimeType, DetectAudioExt, GetArrayBuffer} from "./util"; -export async function Decrypt(file, raw_filename, raw_ext) { +export async function Decrypt(file, raw_filename, raw_ext, detect = true) { + let ext = raw_ext; + if (detect) { + const buffer = new Uint8Array(await GetArrayBuffer(file)); + ext = DetectAudioExt(buffer, raw_ext); + if (ext !== raw_ext) file = new Blob([buffer], {type: AudioMimeType[ext]}) + } const tag = await musicMetadata.parseBlob(file); const info = GetFileInfo(tag.common.artist, tag.common.title, raw_filename); return { status: true, title: info.title, artist: info.artist, - ext: raw_ext, + ext: ext, album: tag.common.album, picture: GetCoverURL(tag), file: URL.createObjectURL(file), - mime: AudioMimeType[raw_ext] + mime: AudioMimeType[ext] } } diff --git a/src/decrypt/tm.js b/src/decrypt/tm.js index 9affb57..172db5f 100644 --- a/src/decrypt/tm.js +++ b/src/decrypt/tm.js @@ -10,5 +10,5 @@ export async function Decrypt(file, raw_filename) { audioData[cur] = TM_HEADER[cur]; } const musicData = new Blob([audioData], {type: "audio/mp4"}); - return await RawDecrypt(musicData, raw_filename, "m4a") + return await RawDecrypt(musicData, raw_filename, "m4a", false) } diff --git a/src/decrypt/util.js b/src/decrypt/util.js index c2a24a6..a19348f 100644 --- a/src/decrypt/util.js +++ b/src/decrypt/util.js @@ -1,4 +1,7 @@ -export const FLAC_HEADER = [0x66, 0x4C, 0x61, 0x43, 0x00]; +export const FLAC_HEADER = [0x66, 0x4C, 0x61, 0x43]; +export const MP3_HEADER = [0x49, 0x44, 0x33]; +export const OGG_HEADER = [0x4F, 0x67, 0x67, 0x53]; +export const M4A_HEADER = [0x66, 0x74, 0x79, 0x70]; export const AudioMimeType = { mp3: "audio/mpeg", flac: "audio/flac", @@ -45,7 +48,19 @@ export function GetCoverURL(metadata) { } export function IsBytesEqual(first, second) { + // if want wholly check, should length first>=second return first.every((val, idx) => { return val === second[idx]; }) } + +/** + * @return {string} + */ +export function DetectAudioExt(data, fallbackExt) { + if (IsBytesEqual(MP3_HEADER, data.slice(0, MP3_HEADER.length))) return "mp3"; + if (IsBytesEqual(FLAC_HEADER, data.slice(0, FLAC_HEADER.length))) return "flac"; + if (IsBytesEqual(OGG_HEADER, data.slice(0, OGG_HEADER.length))) return "ogg"; + if (IsBytesEqual(M4A_HEADER, data.slice(4, 8))) return "m4a"; + return fallbackExt; +}