From 2dff0c1f376f6cc1afde4f30a80d449d5f5d54ea Mon Sep 17 00:00:00 2001 From: Jixun Wu Date: Mon, 21 Nov 2022 18:14:44 +0000 Subject: [PATCH 1/2] test: added test cases for qmc cache. --- src/decrypt/__test__/QmcCache.test.ts | 20 ++++ .../__test__/fixture/qmc_cache_expected.bin | Bin 0 -> 256 bytes src/decrypt/qmccache.ts | 104 +++++++++--------- 3 files changed, 74 insertions(+), 50 deletions(-) create mode 100644 src/decrypt/__test__/QmcCache.test.ts create mode 100644 src/decrypt/__test__/fixture/qmc_cache_expected.bin diff --git a/src/decrypt/__test__/QmcCache.test.ts b/src/decrypt/__test__/QmcCache.test.ts new file mode 100644 index 0000000..2cbc45d --- /dev/null +++ b/src/decrypt/__test__/QmcCache.test.ts @@ -0,0 +1,20 @@ +import { DecryptBuffer as DecryptQmcCacheBuffer } from '../qmccache'; +import fs from 'fs'; + +const expectedBuffer = fs.readFileSync(__dirname + '/fixture/qmc_cache_expected.bin'); + +const createInputBuffer = () => { + const buffer = Buffer.alloc(256); + for (let i = buffer.byteLength; i >= 0; i--) { + buffer[i] = i; + } + return buffer; +}; + +describe('decrypt/qmccache', () => { + it('should decrypt specified buffer correctly', () => { + const input = createInputBuffer(); + DecryptQmcCacheBuffer(input); + expect(input).toEqual(expectedBuffer); + }); +}); diff --git a/src/decrypt/__test__/fixture/qmc_cache_expected.bin b/src/decrypt/__test__/fixture/qmc_cache_expected.bin new file mode 100644 index 0000000000000000000000000000000000000000..a1a0f6de2e3c114841f8068a189ecd11f7416535 GIT binary patch literal 256 zcmV+b0ssEf*W2I2$IH+2_xu0j=j-p2mz$r1hl`K1x4XZir>n11S6g30M@vt0cYA+h zXKQa07aJb~2MZ4~H#&p}_WJ(f=IZW}mYSY|hKi1|wz|HermC(| zR$5*{MoLa{c6xqdW@>H{78)J`1_}-`Hab2cCMqt`)!E&_#mUX__4)nb<>~E_m6@G^ zg^7)^wYj~arKzn^Rasp@MM+I@b$NYZWoc~@6&W1?1qlr?H90*YB`Gb?)Y#m>#K_F> z^!WVXl$e}=gouo=w79&Wq^PV=R9IX { - const buffer = new Uint8Array(await GetArrayBuffer(file)); - let length = buffer.length; - for (let i = 0; i < length; i++) { - buffer[i] ^= 0xf4; - if (buffer[i] <= 0x3f) buffer[i] = buffer[i] * 4; - else if (buffer[i] <= 0x7f) buffer[i] = (buffer[i] - 0x40) * 4 + 1; - else if (buffer[i] <= 0xbf) buffer[i] = (buffer[i] - 0x80) * 4 + 2; - else buffer[i] = (buffer[i] - 0xc0) * 4 + 3; - } - let ext = SniffAudioExt(buffer, ''); - const newName = SplitFilename(raw_filename); - let audioBlob: Blob; - if (ext !== '' || newName.ext === 'mp3') { - audioBlob = new Blob([buffer], { type: AudioMimeType[ext] }); - } else if (newName.ext in HandlerMap) { - audioBlob = new Blob([buffer], { type: 'application/octet-stream' }); - return QmcDecrypt(audioBlob, newName.name, newName.ext); - } else { - throw '不支持的QQ音乐缓存格式'; - } - const tag = await metaParseBlob(audioBlob); - const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, tag.common.artist); - - return { - title, - artist, - ext, - album: tag.common.album, - picture: GetCoverFromFile(tag), - file: URL.createObjectURL(audioBlob), - blob: audioBlob, - mime: AudioMimeType[ext], - }; -} +import { + AudioMimeType, + GetArrayBuffer, + GetCoverFromFile, + GetMetaFromFile, + SniffAudioExt, + SplitFilename, +} from '@/decrypt/utils'; + +import { Decrypt as QmcDecrypt, HandlerMap } from '@/decrypt/qmc'; + +import { DecryptResult } from '@/decrypt/entity'; + +import { parseBlob as metaParseBlob } from 'music-metadata-browser'; + +export function DecryptBuffer(buffer: Uint8Array | Buffer) { + let length = buffer.byteLength; + for (let i = 0; i < length; i++) { + buffer[i] ^= 0xf4; + if (buffer[i] <= 0x3f) buffer[i] = buffer[i] * 4; + else if (buffer[i] <= 0x7f) buffer[i] = (buffer[i] - 0x40) * 4 + 1; + else if (buffer[i] <= 0xbf) buffer[i] = (buffer[i] - 0x80) * 4 + 2; + else buffer[i] = (buffer[i] - 0xc0) * 4 + 3; + } +} + +export async function Decrypt(file: Blob, raw_filename: string, _: string): Promise { + const buffer = new Uint8Array(await GetArrayBuffer(file)); + DecryptBuffer(buffer); + let ext = SniffAudioExt(buffer, ''); + const newName = SplitFilename(raw_filename); + let audioBlob: Blob; + if (ext !== '' || newName.ext === 'mp3') { + audioBlob = new Blob([buffer], { type: AudioMimeType[ext] }); + } else if (newName.ext in HandlerMap) { + audioBlob = new Blob([buffer], { type: 'application/octet-stream' }); + return QmcDecrypt(audioBlob, newName.name, newName.ext); + } else { + throw '不支持的QQ音乐缓存格式'; + } + const tag = await metaParseBlob(audioBlob); + const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, tag.common.artist); + + return { + title, + artist, + ext, + album: tag.common.album, + picture: GetCoverFromFile(tag), + file: URL.createObjectURL(audioBlob), + blob: audioBlob, + mime: AudioMimeType[ext], + }; +} From 8b2834b06aad9ed92c6d7653454b40d9d4de1d1c Mon Sep 17 00:00:00 2001 From: Jixun Wu Date: Mon, 21 Nov 2022 18:15:16 +0000 Subject: [PATCH 2/2] refactor: implement a more efficient buffer decryption algorithm for qmc cache. --- src/decrypt/qmccache.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/decrypt/qmccache.ts b/src/decrypt/qmccache.ts index 7d66ed5..023cd73 100644 --- a/src/decrypt/qmccache.ts +++ b/src/decrypt/qmccache.ts @@ -16,11 +16,9 @@ import { parseBlob as metaParseBlob } from 'music-metadata-browser'; export function DecryptBuffer(buffer: Uint8Array | Buffer) { let length = buffer.byteLength; for (let i = 0; i < length; i++) { - buffer[i] ^= 0xf4; - if (buffer[i] <= 0x3f) buffer[i] = buffer[i] * 4; - else if (buffer[i] <= 0x7f) buffer[i] = (buffer[i] - 0x40) * 4 + 1; - else if (buffer[i] <= 0xbf) buffer[i] = (buffer[i] - 0x80) * 4 + 2; - else buffer[i] = (buffer[i] - 0xc0) * 4 + 3; + let byte = buffer[i] ^ 0xf4; // xor 0xf4 + byte = ((byte & 0b0011_1111) << 2) | (byte >> 6); // rol 2 + buffer[i] = byte; } }