Browse Source

Unlock Kuwo .kwm Files!

20230320
MengYX 5 years ago
parent
commit
71a8d9fab7
No known key found for this signature in database GPG Key ID: E63F9C7303E8F604
  1. 4
      src/decrypt/common.js
  2. 89
      src/decrypt/kwm.js
  3. 5
      src/decrypt/util.js

4
src/decrypt/common.js

@ -1,4 +1,5 @@
const NcmDecrypt = require("./ncm"); const NcmDecrypt = require("./ncm");
const KwmDecrypt = require("./kwm");
const QmcDecrypt = require("./qmc"); const QmcDecrypt = require("./qmc");
const RawDecrypt = require("./raw"); const RawDecrypt = require("./raw");
const TmDecrypt = require("./tm"); const TmDecrypt = require("./tm");
@ -11,6 +12,9 @@ export async function CommonDecrypt(file) {
case "ncm":// Netease Mp3/Flac case "ncm":// Netease Mp3/Flac
rt_data = await NcmDecrypt.Decrypt(file.raw, raw_filename, raw_ext); rt_data = await NcmDecrypt.Decrypt(file.raw, raw_filename, raw_ext);
break; break;
case "kwm"://Kuwo Mp3/Flac
rt_data = await KwmDecrypt.Decrypt(file.raw, raw_filename, raw_ext);
break
case "mp3":// Raw Mp3 case "mp3":// Raw Mp3
case "flac"://Raw Flac case "flac"://Raw Flac
case "m4a":// Raw M4a case "m4a":// Raw M4a

89
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
}

5
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 MP3_HEADER = [0x49, 0x44, 0x33];
export const OGG_HEADER = [0x4F, 0x67, 0x67, 0x53]; export const OGG_HEADER = [0x4F, 0x67, 0x67, 0x53];
export const M4A_HEADER = [0x66, 0x74, 0x79, 0x70]; 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 = { export const AudioMimeType = {
mp3: "audio/mpeg", mp3: "audio/mpeg",
flac: "audio/flac", 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(FLAC_HEADER, data.slice(0, FLAC_HEADER.length))) return "flac";
if (IsBytesEqual(OGG_HEADER, data.slice(0, OGG_HEADER.length))) return "ogg"; 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(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; return fallbackExt;
} }

Loading…
Cancel
Save