diff --git a/src/decrypt/common.js b/src/decrypt/common.js index 31cbf7f..b8f1bb4 100644 --- a/src/decrypt/common.js +++ b/src/decrypt/common.js @@ -1,4 +1,5 @@ const NcmDecrypt = require("./ncm"); +const KwmDecrypt = require("./kwm"); const QmcDecrypt = require("./qmc"); const RawDecrypt = require("./raw"); const TmDecrypt = require("./tm"); @@ -11,6 +12,9 @@ export async function CommonDecrypt(file) { case "ncm":// Netease Mp3/Flac rt_data = await NcmDecrypt.Decrypt(file.raw, raw_filename, raw_ext); break; + case "kwm"://Kuwo Mp3/Flac + rt_data = await KwmDecrypt.Decrypt(file.raw, raw_filename, raw_ext); + break case "mp3":// Raw Mp3 case "flac"://Raw Flac case "m4a":// Raw M4a diff --git a/src/decrypt/kwm.js b/src/decrypt/kwm.js new file mode 100644 index 0000000..2ca6a9a --- /dev/null +++ b/src/decrypt/kwm.js @@ -0,0 +1,89 @@ +import { + AudioMimeType, + DetectAudioExt, FLAC_HEADER, + GetArrayBuffer, + GetFileInfo, + GetMetaCoverURL, + IsBytesEqual, + RequestJsonp +} from "./util"; +import {QmcMaskCreate58, QmcMaskDetectMflac, QmcMaskDetectMgg, QmcMaskGetDefault} from "./qmcMask"; + +import {decode} from "iconv-lite" + +const musicMetadata = require("music-metadata-browser"); +const MagicHeader = [ + 0x79, 0x65, 0x65, 0x6C, 0x69, 0x6F, 0x6E, 0x2D, + 0x6B, 0x75, 0x77, 0x6F, 0x2D, 0x74, 0x6D, 0x65, +] +const PreDefinedKey = "MoOtOiTvINGwd2E6n0E1i7L5t2IoOoNk" + +export async function Decrypt(file, raw_filename, raw_ext) { + const oriData = new Uint8Array(await GetArrayBuffer(file)); + if (!IsBytesEqual(MagicHeader, oriData.slice(0, 0x10))) + return {status: false, message: "Not a valid kwm file!"} + + let fileKey = oriData.slice(0x18, 0x20) + let mask = createMaskFromKey(fileKey) + + function Uint8ArrayToString(fileData) { + var dataString = ""; + for (var i = 0; i < fileData.length; i++) { + dataString += String.fromCharCode(fileData[i]); + } + + return dataString + } + + console.log(Uint8ArrayToString(oriData.slice(0x30, 0x38))) + + let audioData = oriData.slice(0x400); + let lenAudioData = audioData.length; + for (let cur = 0; cur < lenAudioData; ++cur) + audioData[cur] ^= mask[cur % 0x20]; + + + const ext = DetectAudioExt(audioData, "mp3"); + const mime = AudioMimeType[ext]; + let musicBlob = new Blob([audioData], {type: mime}); + + const musicMeta = await musicMetadata.parseBlob(musicBlob); + const info = GetFileInfo(musicMeta.common.artist, musicMeta.common.title, raw_filename); + + const imgUrl = GetMetaCoverURL(musicMeta); + + return { + status: true, + title: info.title, + artist: info.artist, + ext: ext, + album: musicMeta.common.album, + picture: imgUrl, + file: URL.createObjectURL(musicBlob), + mime: mime + } +} + + +function createMaskFromKey(keyBytes) { + let keyView = new DataView(keyBytes.buffer) + let keyStr = keyView.getBigUint64(0, true).toString() + let keyStrTrim = trimKey(keyStr) + let key = new Uint8Array(32) + for (let i = 0; i < 32; i++) { + key[i] = PreDefinedKey[i].charCodeAt() ^ keyStrTrim[i].charCodeAt() + } + return key +} + + +function trimKey(keyRaw) { + let lenRaw = keyRaw.length; + let out = keyRaw; + if (lenRaw > 32) { + out = keyRaw.slice(0, 32) + } else if (lenRaw < 32) { + out = keyRaw.padEnd(32, keyRaw) + } + return out +} diff --git a/src/decrypt/util.js b/src/decrypt/util.js index ab6f404..12fb426 100644 --- a/src/decrypt/util.js +++ b/src/decrypt/util.js @@ -3,6 +3,10 @@ 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 WMA_HEADER = [ + 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, + 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C, +] export const AudioMimeType = { mp3: "audio/mpeg", flac: "audio/flac", @@ -63,6 +67,7 @@ export function DetectAudioExt(data, fallbackExt) { 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, 4 + M4A_HEADER.length))) return "m4a"; + if (IsBytesEqual(WMA_HEADER, data.slice(0, WMA_HEADER.length))) return "wma"; return fallbackExt; }