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 0000000..a1a0f6d Binary files /dev/null and b/src/decrypt/__test__/fixture/qmc_cache_expected.bin differ diff --git a/src/decrypt/qmccache.ts b/src/decrypt/qmccache.ts index dceb7cc..023cd73 100644 --- a/src/decrypt/qmccache.ts +++ b/src/decrypt/qmccache.ts @@ -1,50 +1,52 @@ -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 async function Decrypt(file: Blob, raw_filename: string, _: string): Promise { - 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++) { + let byte = buffer[i] ^ 0xf4; // xor 0xf4 + byte = ((byte & 0b0011_1111) << 2) | (byte >> 6); // rol 2 + buffer[i] = byte; + } +} + +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], + }; +}