From d53ee1f8606f12b47d657bc3f937ac4aff9f3aab Mon Sep 17 00:00:00 2001 From: Jixun Wu Date: Sun, 14 May 2023 23:15:59 +0100 Subject: [PATCH] feat: added support for QMCv2 (#11) --- package.json | 2 +- pnpm-lock.yaml | 8 +++--- src/decrypt-worker/crypto/qmc/qmc_v2.key.ts | 3 +++ src/decrypt-worker/crypto/qmc/qmc_v2.ts | 17 +++++++++++++ src/decrypt-worker/worker.ts | 28 ++++++++++++++------- 5 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 src/decrypt-worker/crypto/qmc/qmc_v2.key.ts create mode 100644 src/decrypt-worker/crypto/qmc/qmc_v2.ts diff --git a/package.json b/package.json index a0aa611..22f8fb7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@chakra-ui/react": "^2.6.1", "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", - "@jixun/libparakeet": "0.0.0-exp.15", + "@jixun/libparakeet": "0.0.0-exp.16", "@reduxjs/toolkit": "^1.9.5", "framer-motion": "^10.12.8", "nanoid": "^4.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92c4cf2..4267e4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,8 +14,8 @@ dependencies: specifier: ^11.11.0 version: 11.11.0(@emotion/react@11.11.0)(@types/react@18.0.28)(react@18.2.0) '@jixun/libparakeet': - specifier: 0.0.0-exp.15 - version: 0.0.0-exp.15 + specifier: 0.0.0-exp.16 + version: 0.0.0-exp.16 '@reduxjs/toolkit': specifier: ^1.9.5 version: 1.9.5(react-redux@8.0.5)(react@18.2.0) @@ -1780,8 +1780,8 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@jixun/libparakeet@0.0.0-exp.15: - resolution: {integrity: sha512-Le2Gl/2V6BsrK8NcFTtaelUkjqewCmDT3xZgUxOKjOo2lK1qvj1AtxarhfDtsOQVjvItiibuRPaRFPpLmWJe9g==} + /@jixun/libparakeet@0.0.0-exp.16: + resolution: {integrity: sha512-jDj9kju0tCJyesV+yi4xcOGClrFgdarkcG/aGP3tRfonf1l8y11A8lVwbp3cWXJcrgHAn7683rTEUYitg9TfpA==} dev: false /@jridgewell/gen-mapping@0.3.3: diff --git a/src/decrypt-worker/crypto/qmc/qmc_v2.key.ts b/src/decrypt-worker/crypto/qmc/qmc_v2.key.ts new file mode 100644 index 0000000..a169f15 --- /dev/null +++ b/src/decrypt-worker/crypto/qmc/qmc_v2.key.ts @@ -0,0 +1,3 @@ +export const SEED = 106; +export const ENC_V2_KEY_1 = '386ZJY!@#*$%^&)('; +export const ENC_V2_KEY_2 = '**#!(#$%&^a1cZ,T'; diff --git a/src/decrypt-worker/crypto/qmc/qmc_v2.ts b/src/decrypt-worker/crypto/qmc/qmc_v2.ts new file mode 100644 index 0000000..04b31bc --- /dev/null +++ b/src/decrypt-worker/crypto/qmc/qmc_v2.ts @@ -0,0 +1,17 @@ +import { transformBlob } from '~/decrypt-worker/util/transformBlob'; +import type { CryptoBase } from '../CryptoBase'; +import { SEED, ENC_V2_KEY_1, ENC_V2_KEY_2 } from './qmc_v2.key.ts'; + +export class QMC2Crypto implements CryptoBase { + hasSignature(): boolean { + return false; + } + + async isSupported(): Promise { + return true; + } + + async decrypt(blob: Blob): Promise { + return transformBlob(blob, (p) => p.make.QMCv2(p.make.QMCv2FooterParser(SEED, ENC_V2_KEY_1, ENC_V2_KEY_2))); + } +} diff --git a/src/decrypt-worker/worker.ts b/src/decrypt-worker/worker.ts index 572090e..57f90c3 100644 --- a/src/decrypt-worker/worker.ts +++ b/src/decrypt-worker/worker.ts @@ -2,9 +2,11 @@ import { WorkerServerBus } from '~/util/WorkerEventBus'; import { DECRYPTION_WORKER_ACTION_NAME } from './constants'; import type { CryptoFactory } from './crypto/CryptoBase'; +import { fetchParakeet } from '@jixun/libparakeet'; + import { XiamiCrypto } from './crypto/xiami/xiami'; import { QMC1Crypto } from './crypto/qmc/qmc_v1'; -import { fetchParakeet } from '@jixun/libparakeet'; +import { QMC2Crypto } from './crypto/qmc/qmc_v2'; const bus = new WorkerServerBus(); onmessage = bus.onmessage; @@ -15,6 +17,9 @@ const decryptorFactories: CryptoFactory[] = [ // QMCv1 (*.qmcflac) () => new QMC1Crypto(), + + // QMCv2 (*.mflac) + () => new QMC2Crypto(), ]; // Use first 4MiB of the file to perform check. @@ -27,17 +32,22 @@ bus.addEventHandler(DECRYPTION_WORKER_ACTION_NAME.DECRYPT, async (blobURI) => { for (const factory of decryptorFactories) { const decryptor = factory(); if (await decryptor.isSupported(blob)) { - const decryptedBlob = await decryptor.decrypt(blob); + try { + const decryptedBlob = await decryptor.decrypt(blob); - // Check if we had a successful decryption - const header = await decryptedBlob.slice(0, TEST_FILE_HEADER_LEN).arrayBuffer(); - const audioExt = parakeet.detectAudioExtension(header); - if (!decryptor.hasSignature() && audioExt === 'bin') { - // skip this decryptor result + // Check if we had a successful decryption + const header = await decryptedBlob.slice(0, TEST_FILE_HEADER_LEN).arrayBuffer(); + const audioExt = parakeet.detectAudioExtension(header); + if (!decryptor.hasSignature() && audioExt === 'bin') { + // skip this decryptor result + continue; + } + + return { decrypted: URL.createObjectURL(decryptedBlob), ext: audioExt }; + } catch (error) { + console.error('decrypt failed: ', error); continue; } - - return { decrypted: URL.createObjectURL(decryptedBlob), ext: audioExt }; } }